Overview
Section titled “Overview”Sub-agents allow a parent agent to delegate subtasks to specialized child agents. Rather than handling every step itself, a parent agent can spawn a focused child agent with its own tools, persona, and iteration budget.
Two delegation modes exist:
- Static sub-agents — configured at build time via
.withAgentTool(). The sub-agent is always available as a named tool. - Dynamic sub-agents — spawned at runtime via the
spawn-agenttool. The parent LLM decides when to spawn and what configuration to use.
Both modes run fully within the parent’s execution context: as of v0.14, a child forks into the parent’s fiber tree rather than running as a detached worker. The child agent executes, produces a result, and that result is returned to the parent as a tool call observation. See Lifecycle, cancellation, and supervision for what that buys you.
Static vs Dynamic Sub-Agents
Section titled “Static vs Dynamic Sub-Agents”Static Sub-Agents (build-time)
Section titled “Static Sub-Agents (build-time)”Register a sub-agent as a named tool when its purpose is known at build time:
const agent = await ReactiveAgents.create() .withProvider("anthropic") .withAgentTool("data-analyst", { name: "Data Analyst", description: "Analyzes data and produces summaries", provider: "anthropic", maxIterations: 5, tools: ["file-read", "web-search"], persona: { role: "Data Analyst", instructions: "Focus on statistical patterns" }, }) .build();The parent LLM can call data-analyst as a tool, passing a task description. The sub-agent executes with the configured tools and persona, then returns its result.
Use static sub-agents when:
- The sub-agent’s purpose is fixed and known at build time
- You want consistent, optimized behavior for a specific task type
- You need tight control over which tools the sub-agent can access
Dynamic Sub-Agents (runtime via spawn-agent)
Section titled “Dynamic Sub-Agents (runtime via spawn-agent)”Enable the spawn-agent tool to let the parent LLM create specialized agents on demand:
const agent = await ReactiveAgents.create() .withProvider("anthropic") .withDynamicSubAgents() // enables spawn-agent tool .build();// Parent LLM decides to spawn and configures at runtimeThe parent LLM generates the sub-agent’s configuration (tools, persona, task) dynamically based on what the current task requires. This is useful when the type of sub-agent needed cannot be known in advance.
Use dynamic sub-agents when:
- The sub-agent’s purpose depends on runtime task content
- The parent needs to create differently-specialized agents for different subtasks
- You want the parent to have full flexibility in delegation
Decision Tree
Section titled “Decision Tree”| Question | Answer | Mode |
|---|---|---|
| Is the sub-agent’s purpose known at build time? | Yes | Static (.withAgentTool()) |
| Does the parent need to create agents dynamically? | Yes | Dynamic (.withDynamicSubAgents()) |
| Do you need consistent, repeatable sub-agent behavior? | Yes | Static |
| Does the sub-agent’s role depend on the task at hand? | Yes | Dynamic |
Lifecycle, Cancellation, and Supervision
Section titled “Lifecycle, Cancellation, and Supervision”Sub-agents are part of the parent run, not fire-and-forget workers (v0.14):
- Structured concurrency — each child’s execution is forked into the parent’s fiber tree.
agent.terminate()interrupts in-flight sub-agents along with the parent; no orphaned workers keep burning tokens after the run is killed. - Truthful failure — a child that fails returns a
success: falseobservation to the parent instead of a fabricated success. The parent LLM sees the failure and can retry, re-scope, or route around it. - Observable on the parent’s bus — child lifecycle events are published on the parent’s shared EventBus tagged with
parentAgentId, so subscriptions and the trace bridge attribute them to the delegating parent. Traces correlate across the delegation via a shared root run id plus the child’s depth. - Recursion cap — every delegation carries a depth counter (0 at the root). By default delegation is flat: children get no spawn tools, so they cannot sub-delegate. Nesting is opt-in — set
.withDynamicSubAgents({ maxRecursionDepth })explicitly, and children can spawn only while their depth stays below the cap. A spawn at the cap is refused with a tool-result observation the model can route around — never a thrown error.
const agent = await ReactiveAgents.create() .withProvider("anthropic") .withDynamicSubAgents({ maxIterations: 5, maxRecursionDepth: 2 }) // opt in to one level of nesting .build();Context Forwarding — What Is Forwarded
Section titled “Context Forwarding — What Is Forwarded”When a parent delegates to a sub-agent, the framework automatically forwards context to help the child agent understand the broader task:
- Parent tool results — extracted from the parent’s recent tool results / working context (agents persist notes via the
recallmeta-tool) - Parent working memory — recent entries from the parent’s working memory store
- Combined prefix — the above is composed into a
systemPromptprefix injected into the sub-agent, capped at 2000 characters (truncated oldest-first when over limit)
For the spawn-agent tool, the parent LLM can also pass:
tools— a whitelist of tool names the sub-agent is allowed to userole,instructions,tone— persona steering applied to the spawned agent
Implementation reference: buildParentContextPrefix(), MAX_PARENT_CONTEXT_CHARS = 2000, and ALWAYS_INCLUDE_TOOLS in packages/tools/src/adapters/agent-tool-adapter.ts.
Context Forwarding — Known Limitations
Section titled “Context Forwarding — Known Limitations”The current context forwarding mechanism has constraints to be aware of when designing sub-agent workflows:
- 2000 character cap — forwarded context exceeding 2000 characters is truncated. Oldest entries are dropped first.
- No full parent thread — sub-agents receive extracted tool results and a short forwarded slice, not the parent’s full message history or everything stored through
recall. - No memory inheritance — sub-agents start with fresh memory. They do not inherit the parent’s episodic or semantic memory stores.
- Sub-agents re-fetch data — if the parent fetched a URL or file, the sub-agent will re-fetch that resource unless the data is explicitly included in the forwarded context.
Workarounds for Context Limitations
Section titled “Workarounds for Context Limitations”When context forwarding falls short, use these patterns:
- Embed context in instructions — pass critical data directly in the
instructionsfield ofspawn-agent. The parent LLM can summarize key findings inline before delegating. - Keep sub-agent tasks narrow — design sub-agents for single-purpose tasks that do not require parent history. The less context a sub-agent needs, the less forwarding matters.
- Use the
toolswhitelist — constrain the sub-agent to only the tools it needs. This reduces token usage and prevents the sub-agent from taking actions outside its scope. - Summarize before delegating — instruct the parent agent (via system prompt or persona) to produce a concise summary of relevant findings in its thought step before spawning a sub-agent.
Persona Control
Section titled “Persona Control”Personas give sub-agents a defined role, background, and behavioral style. This is especially useful for specialized sub-agents where you want consistent behavior.
Static Persona
Section titled “Static Persona”Configure a persona at build time with .withAgentTool():
const agent = await ReactiveAgents.create() .withProvider("anthropic") .withAgentTool("security-auditor", { name: "Security Auditor", description: "Reviews code for security vulnerabilities", provider: "anthropic", maxIterations: 6, tools: ["file-read"], persona: { role: "Security Auditor", background: "Expert in OWASP top 10 and common injection vulnerabilities", instructions: "Flag any potential injection vulnerabilities. Be thorough and cite specific lines.", tone: "formal", }, }) .build();Dynamic Persona via spawn-agent
Section titled “Dynamic Persona via spawn-agent”When using dynamic sub-agents, the parent LLM generates persona parameters at runtime based on the task:
| Parameter | Description | Example value |
|---|---|---|
role | The sub-agent’s functional role | "Data Analyst", "Code Reviewer" |
instructions | Task-specific guidance for this invocation | "Summarize the error patterns in this log" |
tone | Behavioral style | "formal", "concise", "detailed" |
background | Domain expertise context | "Expert in distributed systems" |
The parent LLM selects these values based on the subtask it is delegating. For example, a research agent might spawn a "Citation Verifier" sub-agent with instructions specific to the sources it found.
Performance Considerations
Section titled “Performance Considerations”Sub-agent delegation adds overhead. Understand the costs before adopting this pattern:
- Delegation overhead — delegate mode runs approximately 4x more expensive than a solo agent for simple tasks. Each delegation involves additional LLM calls for spawning and a full sub-agent execution cycle.
- Small model limitations — models smaller than ~8B parameters often struggle with sub-agent tasks. They tend to hallucinate results or fail tool calls when operating as a sub-agent. Use capable models (7B+ instruction-tuned, or hosted providers) for sub-agent roles.
maxIterationsfor sub-agents — defaults to3when not set; the configured value is fully honored with no internal cap. Recommended range is 3–7: sub-agent tasks should be narrow and focused. A high iteration count on a sub-agent signals the task scope is too broad.- When not to use sub-agents:
- Single-step lookups (one tool call is sufficient)
- Tasks where the parent already has all required context
- Cost-sensitive scenarios where the 4x overhead is not justified
- Simple transformations or calculations that a tool handles directly