Skip to main content

Agent Spawning

Agent spawning lets the LLM instantiate lightweight helper agents on-demand during a run() call. When spawn is configured on a BaseAgent, two tools are automatically injected: spawn_agent (single) and spawn_agents_parallel. The LLM calls these tools to hand off focused sub-tasks, collects the results, and continues — all within a single top-level conversation turn.

Spawned agents are ephemeral: no channels, no registry entry, discarded after their task completes.

Contents


When to use spawning

Use spawning when:

  • A single agent needs to fan out work to specialised sub-agents (e.g. a coordinator that spawns a researcher, a coder, and a reviewer)
  • Sub-tasks are independent enough to run concurrently (spawn_agents_parallel)
  • You want the LLM itself to decide which specialist to call, rather than hard-coding delegation logic

Spawning is different from delegation:

DelegationSpawning
Agent lifecyclePersistent, registeredEphemeral, discarded after use
Who decidesCode (delegateAndWait())The LLM (tool call)
Requires registryYesNo
ConcurrencySequential or fire-and-forgetParallel built-in
ChannelsOwn channelsNone

Basic setup

import { BaseAgent } from '@toolpack-sdk/agents';
import type { AgentSpawnConfig } from '@toolpack-sdk/agents';

class OrchestratorAgent extends BaseAgent {
name = 'orchestrator';
description = 'Coordinates research and coding sub-tasks';
mode = 'agent';

spawn: AgentSpawnConfig = {
enabled: true,
templates: [
{
name: 'researcher',
description: 'Searches the web and summarises findings on a specific topic.',
systemPrompt: (task) =>
`You are a focused research agent. Your task: ${task}\n` +
`Search the web, gather relevant information, and return a concise summary.`,
},
{
name: 'coder',
description: 'Writes, refactors, or reviews code for a specific request.',
systemPrompt: (task) =>
`You are a senior software engineer. Your task: ${task}\n` +
`Write clean, well-structured code. Return only the code and a brief explanation.`,
model: 'claude-opus-4-8', // override model for this template
},
],
maxDepth: 3, // default
};

async invokeAgent(input) {
return this.run(input.message);
}
}

When run() is called, the LLM sees two additional tools:

  • spawn_agent — spawn one helper agent for a single task
  • spawn_agents_parallel — spawn multiple agents simultaneously

The LLM picks the right template based on its name and description, invokes the tool, and gets the result back as a tool response.


Templates

Each template is an AgentSpawnTemplate — a plain config object, no subclassing required.

interface AgentSpawnTemplate {
name: string; // unique identifier shown to the LLM
description: string; // purpose — LLM reads this to pick the right template
systemPrompt: (task: string) => string; // factory called at spawn time with the task string
model?: string; // model override — inherits parent when omitted
allowPromptAddition?: boolean; // allow LLM to append extra instructions (default: false)
}

The systemPrompt factory receives the task string the LLM passed to spawn_agent. This lets you embed the task cleanly into the system prompt rather than relying on the message:

{
name: 'summariser',
description: 'Summarises a piece of text into bullet points.',
systemPrompt: (task) => `Summarise the following into 5 bullet points:\n\n${task}`,
}

Parallel spawning

Use spawn_agents_parallel when sub-tasks are independent. The LLM passes an array of { template, task } objects and all agents run concurrently via Promise.all.

Example — the LLM might call:

{
"tasks": [
{ "template": "researcher", "task": "Find the top 3 AI chip manufacturers by revenue in 2025" },
{ "template": "researcher", "task": "Summarise recent TSMC earnings reports" },
{ "template": "coder", "task": "Write a Python script to parse the attached CSV" }
]
}

All three run simultaneously. The tool returns:

{
"results": [
{ "output": "...", "spawnedTemplate": "researcher", "depth": 1 },
{ "output": "...", "spawnedTemplate": "researcher", "depth": 1 },
{ "output": "...", "spawnedTemplate": "coder", "depth": 1 }
]
}

Self-replication

Add a template named 'self' to allow the LLM to spawn a replica of the current agent. The replica inherits the parent's mode and system prompt, with the template's systemPrompt(task) result appended:

spawn: {
enabled: true,
templates: [
{
name: 'self',
description: 'Spawn a copy of this agent to handle a parallel sub-task independently.',
systemPrompt: (task) => `Focus exclusively on this sub-task: ${task}`,
},
],
}

Self-replicas have the same mode, tools, and model as the parent. They do not share conversation history — each gets a fresh conversationId.


Depth control

Spawned agents can themselves spawn (they inherit the parent's spawn config). The maxDepth option caps the recursion:

spawn: {
enabled: true,
templates: [...],
maxDepth: 2, // parent can spawn → spawned can spawn → but no further
}

When a spawned agent reaches maxDepth, the spawn_agent and spawn_agents_parallel tools are not injected into its run() call. The chain stops silently — no error is thrown.

Default maxDepth is 3.


Prompt addition

Set allowPromptAddition: true on a template to let the LLM append custom instructions at spawn time via a systemPromptAddition parameter on the tool:

{
name: 'analyst',
description: 'Analyses data and produces structured reports.',
systemPrompt: (task) => `You are a data analyst. Task: ${task}`,
allowPromptAddition: true,
}

When enabled, the LLM can call:

{
"template": "analyst",
"task": "Analyse Q3 revenue by region",
"systemPromptAddition": "Output must be in JSON. Use ISO 3166 country codes."
}

The systemPromptAddition is appended after systemPrompt(task). Only enable this for templates where LLM-driven prompt customisation is safe — it gives the model significant control over the spawned agent's behaviour.

Templates without allowPromptAddition: true silently ignore any systemPromptAddition value even if the LLM tries to pass one.


EphemeralAgent directly

EphemeralAgent is exported and can be instantiated directly for testing or advanced orchestration without the full spawn machinery.

Standalone use — pass apiKey and call start() to initialise the Toolpack client:

import { EphemeralAgent } from '@toolpack-sdk/agents';
import { AGENT_MODE } from 'toolpack-sdk';

const ephemeral = new EphemeralAgent(
'helper',
'One-off helper agent',
{
...AGENT_MODE,
systemPrompt: 'You are a concise summariser. Return bullet points only.',
},
{ apiKey: process.env.ANTHROPIC_API_KEY! },
);

await ephemeral.start(); // initialises the Toolpack client

const result = await ephemeral.invokeAgent({
message: 'Summarise the key points of this article: ...',
conversationId: `helper-${Date.now()}`,
});

console.log(result.output);

Inside a BaseAgent subclass — pass { toolpack: this.toolpack } to share the parent's already-initialised client (no start() needed since the toolpack is already set):

class MyAgent extends BaseAgent {
async invokeAgent(input) {
const ephemeral = new EphemeralAgent(
'helper', 'desc', mode,
{ toolpack: this.toolpack }, // protected, accessible here
);
return ephemeral.invokeAgent({ message: input.message });
}
}

EphemeralAgent has no channels, no interceptors, and no registry entry.


API reference

AgentSpawnConfig

interface AgentSpawnConfig {
enabled: boolean; // must be true for tools to be injected
templates: AgentSpawnTemplate[]; // available spawn targets shown to the LLM
maxDepth?: number; // max recursive spawn depth (default: 3)
}

AgentSpawnTemplate

interface AgentSpawnTemplate {
name: string;
description: string;
systemPrompt: (task: string) => string;
model?: string;
allowPromptAddition?: boolean; // default: false
}

spawn_agent tool (injected)

ParameterTypeDescription
templatestringTemplate name to spawn. Use 'self' for a self-replica (if that template is listed).
taskstringTask message passed to the spawned agent.
systemPromptAdditionstring(Only present in schema when at least one template has allowPromptAddition: true.) Extra instructions appended to the system prompt. Ignored by templates without allowPromptAddition: true.

Returns: { output: string; spawnedTemplate: string; depth: number; metadata?: Record<string, unknown> }

spawn_agents_parallel tool (injected)

ParameterTypeDescription
tasksArray<{ template, task, systemPromptAddition? }>List of agents to spawn simultaneously. systemPromptAddition only appears in the schema when at least one template has allowPromptAddition: true.

Returns: { results: Array<{ output, spawnedTemplate, depth, metadata? }> }

EphemeralAgent

class EphemeralAgent extends BaseAgent {
constructor(
name: string,
description: string,
mode: ModeConfig | string,
options: BaseAgentOptions, // pass { toolpack } to share parent's API client
);
}