How the spawn-agents Tool Coordinates Parallel Agent Execution in Freebuff
The spawn_agents tool launches multiple child agents concurrently using Promise.allSettled, shares the parent's execution context, validates each child specification, creates isolated AgentState instances, aggregates results after all agents complete, and combines billing costs back into the parent agent.
The spawn_agents tool is a core orchestration primitive in the CodebuffAI/freebuff agent runtime that enables a parent agent to delegate work to multiple specialized sub-agents in parallel. Understanding how parallel agent execution is coordinated reveals the architectural decisions behind safe, deterministic multi-agent workflows.
Preparing the Shared Execution Context
Before any child agents launch, the tool extracts the parent's runtime dependencies once through extractSubagentContextParams. This function, defined in [spawn-agent-utils.ts](https://github.com/CodebuffAI/freebuff/blob/main/packages/agent-runtime/src/tools/handlers/tool/spawn-agent-utils.ts#L60-L68), captures:
- Environment configuration
- Billing/credit tracking state
- LLM client instances
- Other shared infrastructure
This single extraction ensures all children inherit consistent context without redundant setup overhead.
Validating Child Agent Specifications
Each child agent specification undergoes two-phase validation in validateAndGetAgentTemplate and validateAgentInput (same file, lines 71-98):
- Template validation — confirms the requested
agent_typeis allowed in the current runtime and loads its configuration template - Input validation — checks the supplied
promptandparamsfor type correctness and required fields
Failed validations reject that specific child without aborting the entire batch.
Creating Isolated Agent States
For every validated child, createAgentState (lines 51-63) constructs an independent AgentState instance. This isolation prevents cross-contamination: each sub-agent maintains its own message history, tool access, and execution lifecycle while still sharing the parent's underlying infrastructure.
Launching Agents Concurrently with Promise.allSettled
The core parallel coordination happens in [spawn-agents.ts](https://github.com/CodebuffAI/freebuff/blob/main/packages/agent-runtime/src/tools/handlers/tool/spawn-agents.ts#L89-L96):
const results = await Promise.allSettled(
agents.map(async ({ agent_type, prompt, params }) => { … })
);
Why Promise.allSettled instead of Promise.all? The settled variant guarantees the parent receives results from every child even when individual agents fail. This design choice supports partial success patterns common in multi-agent workflows—some children may hit LLM rate limits or encounter tool errors while others complete successfully.
Each mapped promise executes this sequence:
- Create sub-agent state
- Validate configuration
- Call
executeSubagentto run the core agent loop
Executing Individual Sub-agents
The executeSubagent function ([spawn-agent-utils.ts](https://github.com/CodebuffAI/freebuff/blob/main/packages/agent-runtime/src/tools/handlers/tool/spawn-agent-utils.ts#L66-L80)) invokes loopAgentSteps, the runtime's standard LLM-backed agent driver. During execution, it emits lifecycle events:
subagent_start— sent when a child beginssubagent_finish— sent on completion or failure
These events enable real-time monitoring of parallel agent execution from the parent or external observer.
Collecting and Aggregating Results
After all promises settle, the handler builds a structured report (lines 98-115 in [spawn-agents.ts](https://github.com/CodebuffAI/freebuff/blob/main/packages/agent-runtime/src/tools/handlers/tool/spawn-agents.ts#L98-L115)):
// Results array contains status, value/error, and metadata for each child
const report = results.map((result, index) => ({
agentName: agents[index].agent_type,
agentType: agents[index].agent_type,
value: result.status === 'fulfilled' ? result.value : result.reason,
}));
The tool then aggregates billing costs (lines 18-60), adding any credits consumed by successful or partially-failed sub-agents to the parent's creditsUsed field. This ensures accurate cost attribution across the entire parallel batch.
Practical Usage Examples
Spawning Multiple Agents Simultaneously
await runTool({
name: 'spawn_agents',
input: {
agents: [
{ agent_type: 'code-reviewer', prompt: 'Review this PR', params: {} },
{ agent_type: 'test-runner', prompt: 'Run tests', params: {} },
],
},
});
This call launches both agents in parallel. The code reviewer and test runner execute simultaneously, subject only to underlying LLM provider rate limits.
Processing the Returned Report
const { output } = await runTool(...);
const reports = output[0].value; // array of { agentName, agentType, value }
reports.forEach(r => {
if (r.value instanceof Error) {
console.error(`${r.agentName} failed:`, r.value.message);
} else {
console.log(`${r.agentName} (${r.agentType}):`, r.value);
}
});
The settled results preserve individual success/failure states while presenting them in a unified structure.
Comparison: spawn_agents vs spawn_agent_inline
| Tool | Use Case | Concurrency | Message History |
|---|---|---|---|
spawn_agents |
Multiple independent children | Parallel | Isolated per child |
spawn_agent_inline |
Single child sharing parent context | Sequential | Shared with parent |
The spawn_agent_inline tool ([spawn-agent-inline.ts](https://github.com/CodebuffAI/freebuff/blob/main/packages/agent-runtime/src/tools/handlers/tool/spawn-agent-inline.ts#L25-L34)) creates state with includeMessageHistory: true and runs synchronously—appropriate when the parent needs to continue a coherent conversation through a single delegate.
await runTool({
name: 'spawn_agent_inline',
input: {
agent_type: 'doc-generator',
prompt: 'Create API docs',
params: {},
},
});
Key Implementation Files
- [
spawn-agents.ts](https://github.com/CodebuffAI/freebuff/blob/main/packages/agent-runtime/src/tools/handlers/tool/spawn-agents.ts) — Core handler withPromise.allSettledcoordination, result aggregation, and cost accounting - [
spawn-agent-utils.ts](https://github.com/CodebuffAI/freebuff/blob/main/packages/agent-runtime/src/tools/handlers/tool/spawn-agent-utils.ts) — Context extraction, validation utilities, state creation, andexecuteSubagentimplementation - [
spawn-agent-inline.ts](https://github.com/CodebuffAI/freebuff/blob/main/packages/agent-runtime/src/tools/handlers/tool/spawn-agent-inline.ts) — Sequential single-child variant with shared history
Summary
- Parallel execution uses
Promise.allSettledto run all child agents concurrently while capturing individual success and failure states - Shared context is extracted once via
extractSubagentContextParamsand inherited by all children - Validation occurs per-child through
validateAndGetAgentTemplateandvalidateAgentInput, with failures isolated to that agent - State isolation via
createAgentStateensures independent execution lifecycles - Result aggregation combines outputs and costs only after every child settles, producing a deterministic final report
- Billing transparency adds sub-agent costs to the parent's
creditsUsedfor accurate accounting
The design prioritizes observability (lifecycle events), fault tolerance (allSettled semantics), and cost accountability—essential properties for production multi-agent systems.
Frequently Asked Questions
What happens if one spawned agent fails?
The failure is captured in the results array with status 'rejected' and the error reason. Other agents continue and complete normally. The parent receives a complete report showing which children succeeded and which failed, enabling targeted retry logic or graceful degradation.
How does parallel agent execution handle rate limits?
Actual concurrency is constrained by the underlying LLM provider's rate limits and the runtime's HTTP client configuration. The Promise.allSettled structure launches all promises simultaneously, but network-level queuing or provider throttling may serialize requests. The runtime does not implement additional concurrency limiting—this is left to infrastructure configuration.
Can spawned agents spawn their own children?
Yes. Since each child receives a full AgentState with tool access including spawn_agents, nested parallel execution is technically possible. However, the current implementation in CodebuffAI/freebuff does not include explicit depth limits, so application-level guardrails should prevent unbounded recursion.
What's the difference between shared and isolated message history?
Shared history (via spawn_agent_inline) preserves the entire conversation thread between parent and child, making it suitable for delegated single tasks that must return to the parent's context. Isolated history (default in spawn_agents) gives each child a clean slate, appropriate for independent work that should not pollute or leak into other agents' contexts.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →