subagent

package
v0.1.3 Latest Latest
Warning

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

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

README

subagent

Extension. Ejectable — the loop never names it. Without it the agent is solo: every step of every sub-task happens in its own context window.

What delegation buys the parent is context, not compute. A child explores in its own isolated history and returns only its final answer, so the tool churn, dead ends, and large intermediate results never enter the parent's window.

Model Experience

Delegation is available (run below MaxDepth)
What the model sees

One tool, spawn_subagent, with task and context parameters — plus an agent parameter listing named teammates when a roster is configured, and an optional output_schema (JSON Schema object) that turns the final answer into validated JSON.

Guidance belongs to the capability

The run extension owns the orchestration instructions in the spawn_subagent tool schema. Applications supply the authorized roster and execution callbacks; they do not need to append a second generic orchestrator system prompt.

The schema tells the model to handle simple work directly, delegate bounded tasks when useful, supply only authorized context, collect required evidence, reconcile gaps and return one coherent result. Partial results and uncertain effects are not proof of success. Delegation does not grant authority.

Additional guidance follows the actual surface:

  • Self-fork: fresh history, inherited capabilities subject to host restrictions.
  • Named roster: choose only a destination whose stated purpose covers the task/project/resource. Missing access is a concrete blocker, not a reason to probe unrelated targets. Each callback owns its target's identity, inputs, history and permissions; descriptions are selection hints, never grants.
  • Async enabled, ephemeral run: a job receipt is not completion; collect or cancel it with the run's job controls before reporting success.
  • Durable run: neither the async parameter nor its instructions are offered.
  • Permission denied or depth cap: the tool and all its guidance disappear through the existing policy/depth advertisement path.

This uses the existing ToolContributor extension; it adds no kernel hook, router model, orchestrator persona, system-prompt block or application concept. The schema is rebuilt for the current run and roster after checkpoint resume, so old destination hints are not accumulated in the definition. Tool guidance is present on native model requests alongside the advertised capability, including following context compaction. Selection guidance improves model behavior; only the host's grants and callbacks enforce resource access.

Host-specific boundaries (for example original-input-only forwarding, data isolation and publication) belong in the consuming application. Domain workflows and expertise belong in its skills or definition, not this plugin.

output_schema — typed final answers

When output_schema is set, the child's task gains an instruction to end with a single bare JSON value matching the schema. The plugin validates the final answer itself — a forked child cannot carry the provider's structured-output seam — and on a violation re-opens the child exactly once with the error. The parent receives the validated JSON; if the retry also fails it receives the raw answer suffixed with [validation failed: …], so a bad shape is visible rather than silent.

An ephemeral correction resumes the child's native checkpoint with a new validation-error message. For durable execution, the host runs the correction at <childSession>/retry, seeded from the original native checkpoint. A completed original child remains immutable. The deterministic id keeps the spawn replay-safe — a re-issued spawn reattaches to whichever child log completed. A delegate has no transcript to re-open (Delegate.Run is an opaque closure), so its retry is a single re-invocation carrying the error and the rejected answer in the task.

For native durable runs, a human question from either the original child or its corrective retry parks the parent. Answering the parent forwards to the child's recorded question. Resume reattaches the same child and re-applies schema validation and output limits before delivering its answer. Additional questions keep the same delegation and receive distinct workflow IDs.

Token effect

Fixed schema overhead plus isolated child context. The common orchestration guidance is attached once to the advertised tool; named/async guidance appears only with those capabilities. The child's entire run — every tool call, every intermediate result — is replaced in the parent's context by one answer capped at MaxOutputBytes (default 48 KB). This reduces parent-context pressure for substantial child work; it does not promise fewer total model tokens or lower latency for small tasks.

KV cache effect

Append-only for the parent. The child runs against its own prefix entirely.

The run is already at MaxDepth
What the model sees

Nothing. The plugin declines the run, so the tool is never advertised.

Enforcing the cap by absence rather than by refusal matters: a tool that is offered and then always refuses is something the model must read, reason about, and work around.

Token effect

Zero-direct.

Impact on the agent

  • The tool is gated. Unlike read_skill / read_spill / job_* / session_query, spawning reaches capability the agent has not already exercised, so the consumer must permit spawn_subagent in its policy.
  • A child is built by Agent.Fork, which lives in core precisely so scope can only narrow: the child inherits every capability-bearing field verbatim — provider, ladder, tools, policy (including the installed permission gate), memory, definition, limits, env, compaction, retry, caching, extensions — and drops the run-control seams (durable session by default, steering, follow-up, step gate, PrepareNextTurn). A child is one bounded task, not a conversation.
  • Depth rides the context, not a field, so an A → B → A cycle is bounded by the same MaxDepth as a straight chain, even when the two agents are composed with different plugin sets.
  • A durable parent gives the child a durable session at parentSession + "/" + invocationKey. The key identifies the recorded physical invocation, even when the provider repeats a tool-call ID. With a native session host installed, self-delegation is retry-safe: a replayed spawn reattaches — a completed child returns its recorded answer without re-running (no duplicate spend or side effects), an interrupted one resumes from its own log.
  • A delegate-routed spawn is not retry-safe. Delegate.Run is an opaque closure under the target agent's identity with no reattach wiring, so replaying it would re-run the teammate's entire task; those calls are left dangling for the model to decide.
  • Child usage — including a failed child's — is folded into the parent via AddChildUsage, so the parent's budget gate accounts for what its children spent.
  • Spawns are Parallel, so a fan-out turn ("spawn three, then synthesize") runs them concurrently.
  • A cancelled child fails the spawn; it does not answer it. A cancelled run is not an error to its caller — the loop stops between turns and returns what it has with StopReason: "aborted" and a nil error, which is right for a viewer that walked away and wrong here. res.Final is then whatever the child last happened to say, mid-task, and returning it hands the parent a killed child's partial state as its answer: the shard is recorded as reconciled, the batch looks complete, and the interrupted work is never redone. The spawn fails instead, which puts something the model can act on in the transcript and leaves the call replayable.

When a fan-out is interrupted

The native session host owns child journals and recovery. Completed children reattach without new model calls or usage; interrupted children resume only from recorded native state and settled tool receipts. Missing or ambiguous completion receipts fail closed instead of repeating effects or treating a partial answer as completion. Cancellation reaches all in-flight children.

internal/runtime/pi_children_integration_test.go exercises native isolation, reattachment, interrupted-child recovery, corrective retry reattachment, cancellation, parallel sessions and parked questions. Plugin tests separately verify the fork callback receives the inherited agent, invocation identity and original native checkpoint.

Known limitations and deferred work

  • The parent sees only the final answer. output_schema can make that answer validated JSON, but there are no partial results and no way for a child to hand back an artifact reference instead of prose.
  • No per-child budget. MaxPerRun counts spawns, not tokens or cost; one expensive child can consume the whole run's budget.
  • A child cannot be steered. No steering queue, no step gate — once started it runs to completion or cancellation.
  • Delegate is fully opaque. agentcore cannot verify that a delegate's scope is narrower than the caller's; that guarantee is the consumer's.
  • Depth is the only recursion bound. There is no detection of a semantic cycle below MaxDepth (A asks B the same question A was asked).

Native execution

Async children publish tool-name and lifecycle progress through the run-owned agentcore.WithRunProgress observer, automatically bound by RunNative. They never retain the spawn tool's streaming callback after it returns. Native delivery is serialized and fenced when the run ends; cancelled jobs suppress further notes. Synchronous children still use the spawn tool's emitter. These notes are display activity, not partial answers or child transcript content. Other native session hosts may bind the same run-owned observer while preserving their own cancellation, serialization and end-of-run fence.

Plugin.RunFork is an optional consumer hook for self-delegation. The plugin still creates the child with Agent.Fork, applies its depth and spawn limits, validates output, and folds usage into the parent. Without the hook, ephemeral children use RunNative directly. Durable children require the hook and a recorded invocation key; there is no legacy driver or provider-call-ID fallback.

The hook receives ForkRequest with the durable child session ID, prompt, recall task, and (for a corrective attempt) the previous RunResult. Native adapters must seed retries from Previous.NativeState, never from the display projection in Previous.Messages. The adapter owns native persistence, completion verification, reattachment, and cancellation. A completed reattach must report zero new usage. Native self-forks derive session IDs from the persisted invocation key so reused provider call IDs remain distinct.

The native adapter distinguishes a parked child from a completed child. Its structured error identifies the durable child question, and reattaching while that question is pending performs no model work. Answering the child session allows that same fork request to resume. The parent plugin still returns this as a delegation error; forwarding the question and answer through the parent's human-input workflow requires consumer integration.

Documentation

Overview

Package subagent installs spawn_subagent: self-forking and cross-agent delegation under shared depth and budget caps.

What it buys the parent is CONTEXT, not compute: a child explores in its own isolated history and returns only its final answer, so the tool churn, dead ends, and large intermediate results never enter the parent's window.

A spawned child is an ordinary governed tool call — same policy gate, same credential boundary, same trace — not a privileged side channel. Depth rides the context, so an A -> B -> A cycle is bounded by the same MaxDepth as a straight chain, and the plugin declines a run already at the cap rather than advertising a tool that would only refuse.

Index

Constants

View Source
const ToolSpawnSubagent = "spawn_subagent"

ToolSpawnSubagent is the stable name of the built-in delegation tool (ARCHITECT-AGENT-TEAM P1): the model forks an ephemeral child agent for one self-contained task and receives only its final answer, keeping the child's exploration (tool churn, dead ends, large intermediate results) out of the parent's context window.

Variables

This section is empty.

Functions

This section is empty.

Types

type Delegate

type Delegate struct {
	// Name is the stable identifier the model selects with (the target agent's
	// human name). Matched case-insensitively.
	Name string
	// Description is a one-line hint helping the model pick the right teammate.
	Description string
	// Run executes the delegated task on the target agent.
	Run func(ctx context.Context, task string, sink agentcore.StreamSink) (string, agentcore.Usage, error)
}

Delegate is one named other agent this agent may hand a task to (cross-agent delegation). agentcore never builds the target itself: Run is an opaque closure the consumer injects, executing the target agent under its own identity — its persona, tools, policy, and secrets, not the caller's. The closure receives the caller's ctx (so cancelling the parent cancels the delegate, and the delegation depth carried on ctx caps recursion across agents) and an optional sink for live tool-activity notes; it returns the target's final answer plus its token usage for parent-run accounting.

type ForkRequest

type ForkRequest struct {
	SessionID string
	Prompt    string
	Task      string
	Previous  *agentcore.RunResult
}

ForkRequest describes one isolated task or one corrective retry. Previous is the original result, including native state; adapters must not reconstruct a native transcript from its display-only Messages projection.

type Plugin

type Plugin struct {
	// AllowAsync offers run-owned jobs for ephemeral forks. Durable delegation
	// must continue using its host journal and reattachment protocol.
	AllowAsync bool
	// MaxDepth is how many nesting levels may spawn: 1 (the default) lets the
	// top-level agent spawn children but forbids grandchildren.
	MaxDepth int
	// MaxPerRun caps how many children one run may spawn in total. Zero derives
	// it from the run's own tool budget (a third of Limits.MaxToolCalls, floor
	// 8), so a long run gets a proportionate delegation budget without the
	// consumer having to restate one.
	MaxPerRun int
	// MaxOutputBytes caps the child answer surfaced to the parent model.
	MaxOutputBytes int
	// Delegates names the other agents this one may hand a task to. Each Run is
	// an opaque closure the consumer injects — agentcore never loads another
	// agent itself. Empty leaves only self-delegation.
	Delegates []Delegate
	// RunFork selects the consumer's runtime for self-delegation. The child is
	// always created by Agent.Fork first, so runtime selection cannot widen its
	// inherited capabilities. Nil uses the native engine with consumer checkpoints
	// for ephemeral runs; durable self-forks require a native session host.
	RunFork ForkRunner
}

Plugin caps the delegation surface. A child inherits the parent's provider, model ladder, tools, policy, hooks, memory, and definition — it can never widen access — and runs with isolated history, so only its final answer (truncated to MaxOutputBytes) returns to the parent.

func SelfOnly

func SelfOnly() Plugin

SelfOnly enables delegation to ephemeral forks of this agent, with no cross-agent roster.

func To

func To(delegates ...Delegate) Plugin

To enables cross-agent delegation to the named teammates, plus self-forking.

func (Plugin) BeginRun

BeginRun installs the tool for a run still above the nesting cap, and DECLINES one that is already at it.

Declining is how delegation bottoms out structurally: a run at MaxDepth is never offered the tool, so the cap is enforced by absence rather than by a refusal the model would have to read, reason about, and work around. The depth rides the context, so it survives crossing into another agent's run.

func (Plugin) Name

func (Plugin) Name() string

Name identifies the plugin and the extension it installs.

func (Plugin) Register

func (p Plugin) Register(r *agentcore.Registry) error

Register adds the plugin as a run extension.

Jump to

Keyboard shortcuts

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