Help us shape the future of Go! Take the 2026 Go Developer Survey

subagent

package
v0.4.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 16, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package subagent runs one bounded child agent conversation in a private, memory-only session and reports its outcome as a bounded Result.

Index

Constants

View Source
const DefaultStepCap = 12

DefaultStepCap bounds a child that no caller sized itself: enough rounds to read, change, and verify a small piece of work, and few enough that a looping child ends.

Variables

This section is empty.

Functions

This section is empty.

Types

type ActivitySink

type ActivitySink interface {
	ChildAgentStarted(start events.ChildAgentStart)
	ChildAgentCalls(invocation string, lines []string)
	ChildAgentEnded(end events.ChildAgentEnd)
}

ActivitySink observes one child agent while it is still running. A tool round is attributed by the invocation id passed beside it, never by having arrived between a start and an end, so children whose lifetimes overlap stay separable.

type Choice

type Choice string

Choice is the answer to one path recovery cannot put back on its own. Defer is the safe answer: nothing is written and the path is offered again at the next launch.

const (
	ChoiceDefer   Choice = "defer"
	ChoiceKeep    Choice = "keep"
	ChoiceRestore Choice = "restore"
)

type HeldMove

type HeldMove struct {
	Source      string
	Destination string
	Reason      string
	Resumes     bool
}

Resumes marks a move whose linked undo had already begun, so a later recovery continues it rather than leaving it to be fixed by hand.

type Journal

type Journal struct {
	// contains filtered or unexported fields
}

Journal is the durable all-or-nothing record of one child agent's declared scope. It holds the dedicated journal directory open for the whole transaction: every record rewrite, every rollback step, and the final cleanup unlink run relative to that descriptor, so a directory swapped in at the path after the child was dispatched can neither redirect an update nor suppress one.

func OpenJournal

func OpenJournal(cfg JournalConfig) (*Journal, error)

OpenJournal snapshots every declared path and writes the transaction's intent to disk before the caller dispatches the child, so a parent that dies without unwinding still leaves a recoverable record. Every refusal here — an unsafe journal location, a path of a kind its declaration cannot restore, or any of the fixed caps — happens before both the child and the journal storage exist.

func (*Journal) AbandonMutation

func (j *Journal) AbandonMutation(target string, parentDir *os.File)

AbandonMutation resolves a pending record whose publication never happened. The pending state is then a state no one published, so a file holding it is another writer's — content equality cannot stand in for ownership here the way it can after a real publish, and absorbing it would let the rollback restore over, or remove, a file this child never wrote. Only the durable last-known state clears the record; anything else is reported rather than claimed.

func (*Journal) Absorb

func (j *Journal) Absorb(child *Journal) error

Absorb folds a transfer-ready descendant's state into this journal and then deletes it. The parent takes over the child's last-known states and created directories so its own comparison reads the grandchild's writes as child-owned rather than as an external edit.

func (*Journal) BeginDelete

func (j *Journal) BeginDelete(target string, parentDir *os.File) error

func (*Journal) BeginMutation

func (j *Journal) BeginMutation(target string, observed, next filetools.JournalState) error

func (*Journal) Close

func (j *Journal) Close()

Close removes nothing: a journal is deleted only by a committed or fully rolled-back outcome.

func (*Journal) Covers

func (j *Journal) Covers(target string) bool

func (*Journal) DirLifecycle

func (j *Journal) DirLifecycle(target, ancestor string) fsutil.DirLifecycle

DirLifecycle records each segment against the path's snapshotted ancestor, which rollback descends from. EnsureDirAt names segments from ancestor, the directory the create started in, and that is deeper than the snapshotted one once an earlier act made or moved in a directory above the target.

func (*Journal) Finish

func (j *Journal) Finish(reason Reason) JournalOutcome

Finish resolves the transaction. A completed child's work stands only when every unresolved declared path still matches the durable last-known child state; every other terminal reason puts the declared scope back as the child found it. A path that matches neither is a third outcome: it is left alone and the journal survives naming it.

func (*Journal) MarkTransferReady

func (j *Journal) MarkTransferReady() error

MarkTransferReady hands a completed nested child's journal to its parent. The child never deletes its own journal on completion: until the parent has durably absorbed it, it is the only record of which writes belong to the child rather than to an outside writer.

func (*Journal) MaxStateBytes

func (j *Journal) MaxStateBytes() int64

func (*Journal) MoveLifecycle

func (j *Journal) MoveLifecycle(source, destination string) fsutil.MoveLifecycle

func (*Journal) Path

func (j *Journal) Path() string

func (*Journal) ReconcilePending

func (j *Journal) ReconcilePending()

ReconcilePending resolves every outstanding pending mutation without a live publish in hand. It is the entry point recovery must run before it compares any unresolved path: only this comparison can tell a crash before publication from a crash after it. Each path's captured target parent is reopened and identity-verified before it is synced and read.

func (*Journal) RemovePublished

func (j *Journal) RemovePublished(target string, parentDir *os.File, published filetools.JournalState)

func (*Journal) ResolveMutation

func (j *Journal) ResolveMutation(target string, parentDir *os.File, published filetools.JournalState)

ResolveMutation settles the pending record against what the publish actually left. published names the inode the caller put at the path, or is the zero value when it has none to name. A pending record is written before publication and so cannot name the inode it will create; adopting the caller's is what keeps a byte-identical file another writer put at the name in the publish-to- reconcile window from being read as this child's own work. Recovery, which arrives after a crash with no such identity to offer, still falls back to comparing content alone.

type JournalConfig

type JournalConfig struct {
	Home           string
	ProjectRoot    string
	Invocation     string
	Declared       []string
	DeclaredDelete []string
	DeclaredMoves  []permissions.MovePair
	Parent         *Journal
}

Declared, DeclaredDelete, and DeclaredMoves must be the canonical scopes resolved before dispatch, not the incomplete record of what a run turned out to touch.

type JournalOutcome

type JournalOutcome struct {
	Restored    []string
	Unrecovered []string
	JournalPath string
	HeldMoves   []HeldMove
}

JournalOutcome is what a terminal transaction did. Unrecovered is an outcome in its own right, distinct from both a clean commit and a clean rollback: the named paths are left as they are and the journal survives for recovery or the user to resolve.

type LeftAloneJournal

type LeftAloneJournal struct {
	Path   string
	Reason string
}

type Reason

type Reason string
const (
	ReasonCompleted Reason = "completed"
	ReasonStepCap   Reason = "step_cap"
	ReasonCancelled Reason = "cancelled"
	ReasonFailed    Reason = "failed"
)

type Report

type Report struct {
	Restored  []string
	Kept      []string
	Retained  []RetainedJournal
	LeftAlone []LeftAloneJournal
}

func Recover

func Recover(home, projectRoot string, ask func(Unresolved) Choice) Report

Recover puts back the declared writes of every child agent whose codemeld process died before it could unwind them, for the project folder being opened. A journal whose recorded process cannot be proved gone belongs to another possibly running codemeld and is read but never written. ask decides the paths that match neither the state the child left nor the state captured before it ran; a nil ask defers all of them.

func (Report) Empty

func (r Report) Empty() bool

type Request

type Request struct {
	Instruction     string
	Model           model.Selection
	ModelSource     model.Source
	SupportedEffort func(providerID, modelID string) []string
	SystemPrompt    string
	Client          provider.Client
	Tools           []provider.ToolDefinition
	Dispatcher      turn.ToolDispatcher
	StepCap         int
	// Journal is opened before the request is built, because a run that dies mid-dispatch must
	// already have its snapshot on disk.
	Journal *Journal
	// Activity and InvocationID are optional and belong together: the caller that minted the
	// id owns the sink, and validate requires neither.
	Activity     ActivitySink
	InvocationID string
}

Request carries snapshots the caller takes at delegation time. Dispatcher is the authorization seam: the caller builds it against the child's mode context, and this package never assembles authority of its own.

type Result

type Result struct {
	Reply          string
	ReplyTruncated bool
	// Requests started, not completed rounds: a request whose stream fails after it
	// opened is still counted.
	ProviderRequests int
	ToolNames        []string
	// WriteTargets are the files the child's successful edit and create calls wrote, in call
	// order. It is not a complete record of what the child changed on disk — a create may
	// also have made parent directories, and a call that changed a file before reporting a
	// failure is absent — so it cannot stand in for a rollback snapshot.
	WriteTargets  []string
	DeleteTargets []string
	MoveTargets   []turn.MoveTarget
	Reason        Reason
	RestoredPaths []string
	// Unrecovered is a third outcome alongside a clean rollback and a clean commit: the named paths
	// were left as they are and RetainedJournal survives for recovery or the user to resolve.
	Unrecovered     []string
	RetainedJournal string
}

func Run

func Run(ctx context.Context, req Request) (Result, error)

Run returns a usable Result and a nil error for a step cap, a cancellation, and a terminal user denial; only an unexpected loop failure returns the partial Result together with its error.

type RetainedJournal

type RetainedJournal struct {
	Path  string
	Notes []string
	Moves []HeldMove
}

type Unresolved

type Unresolved struct {
	Path string
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL