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

subagent

package
v0.4.6 Latest Latest
Warning

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

Go to latest
Published: Oct 20, 2026 License: MIT Imports: 27 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.

View Source
const InstructionSuffix = "" /* 148-byte string literal not displayed */

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 asking about it.

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) BeginMkdir added in v0.4.6

func (j *Journal) BeginMkdir(target, ancestor string) (declared bool, lifecycle *fsutil.DirLifecycle, err error)

A non-nil lifecycle leaves a durable pending creation that the caller must pass to ResolveMkdir whether or not the create succeeds.

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. It never releases a writer lock, so a retained journal stays visibly owned until this process exits.

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) ResolveMkdir added in v0.4.6

func (j *Journal) ResolveMkdir(target string) error

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
	DeclaredDirs   []string
	Parent         *Journal
}

Declared, DeclaredDelete, DeclaredMoves, and DeclaredDirs 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
	RetainedDirs []string
	// contains filtered or unexported fields
}

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 Refusal added in v0.4.6

type Refusal struct {
	Operation string
	DeniedBy  string
	Reason    string
	// contains filtered or unexported fields
}

Operation shows the child's arguments as given, never a resolved or authorized target.

func (Refusal) Line added in v0.4.6

func (r Refusal) Line() string

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 lock another process holds is skipped without reading its record, and so is every journal linked above it. ask decides each held move group whose undo has not begun, and then the paths that match neither the state the child left nor the state captured before it ran; a nil ask defers all of them, while answers recorded earlier are still carried out.

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
	Refusals        []Refusal
	RefusalTotal    int
}

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
	Dirs  []string
}

type Unresolved

type Unresolved struct {
	Path           string
	Moves          []UnresolvedMove
	Choices        []Choice
	RestoreRefusal string
}

A group question leaves Path empty and lists its moves in journal order.

type UnresolvedMove added in v0.4.6

type UnresolvedMove struct {
	ID          moveID
	Source      string
	Destination string
	Pending     bool
}

Jump to

Keyboard shortcuts

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