Skip to content
agentFast

Agent teams

Fan out to sub-agents, each with its own context window, under one durable run.

In short: some questions are too broad for one agent to research in one context window. A team splits the work: a supervisor decides what to investigate, and a fixed roster of sub-agents each investigate one angle as a full run of their own — own context, own budget, own step tree. Their costs roll up into one number on the parent, so you can answer "what did this team cost" without adding up children by hand, and "which sub-agent spent it" by opening the tree.

What a team is

A team is a supervisor and a worker template, wired together as sdk: team:

  • The supervisor reads the task and emits a typed list of angles to investigate — list[SubAgentTask], each one an angle and a focus. That list is its entire output vocabulary.
  • The worker template is one fixed definition — a prompt, a tool list, a budget — reused for every child. The supervisor picks what to look into; the worker template decides how any one angle gets investigated, and that "how" is the same for all of them.

The split is deliberate. The supervisor is a model, and a model's output is the one thing in this system that varies at runtime — so it's confined to choosing angles from a fixed menu, nothing more. It cannot invent a new tool for a child to use, and it cannot raise a child's budget above what the worker template already grants. That's what keeps the parent budget ceiling meaningful: if the supervisor could grant itself more tools or more spend, "the team's budget is the run budget" would stop being true the first time a model decided it needed one more thing.

Each child is a full run, not a step

This is the part that's easy to get wrong by analogy with a single agent calling a sub-routine. A child is not a tool call and not a step in the parent's trace — it's a completely independent run, linked to the parent by parent_run_id:

  • its own context window, so one angle's research can't crowd out another's or push the parent toward compaction;
  • its own budget (worker_max_iterations, worker_max_tokens), sized from the worker template, not shared with siblings;
  • its own step tree — every LLM call, tool call and guardrail check a child makes is its own full trace, exactly like a single-agent run's;
  • its own crash recovery — a child checkpoints exactly the way any other run does.

Because a child isn't a step, it doesn't show up as a row in the parent's own trace either. The parent's trace holds only its own work — the supervisor's planning call and the synthesis call that follows, both real llm steps that go through the same guardrail checks and budget accounting as any other model call in agentFast. Here's the same run from the top: everything below is what actually lives in the parent's trace.

run trace
llmclaude-sonnet-4-5supervisor: 3 angles chosen from the brief$0.02
llmclaude-sonnet-4-5synthesis: merged 2 of 3 angles (one failed)$0.03
This parent's own trace, in full — two llm steps, nothing more. The three children run as separate, independent runs; each is nested under this one in the dashboard, with its own full step tree, not shown here.
ChildRunStatusStepsCost
pricing modelrun_8f2a1ccompleted11$0.41
competitor landscaperun_a91d07completed8$0.29
regulatory riskrun_c3d5e2failed — search API timeout2$0.03

Those three rows are what the dashboard nests under the parent, each expandable into its own step tree — not part of the table above. on_child_failure: continue (the default) is why synthesis ran at all here: one dead search API didn't stop the two angles that succeeded from being reported.

Cost rolls up, spend doesn't fan out ahead of it

Children run up to team.max_parallel at a time. Admission is checked lazily — as each slot frees, not all upfront — because a child's usage only reaches the parent once that child actually finishes. Deciding every child's admission in one upfront pass would judge the whole fan-out against a parent that had spent nothing yet, and the budget ceiling could never bite. Topping up slots as they free is what lets the parent's budget check see real accrued spend, including what the team has already paid its children.

The moment a child finishes, its real token counts and cost — read back from the persisted run record, not the in-memory result — are added to the parent's totals. That's the whole mechanism behind "one number answers what the team cost": nothing aggregates on its own, the parent has to be told, and it's told as each child actually completes rather than guessed upfront.

When a child fails

team.on_child_failure decides what a bad angle does to the rest of the team:

ValueBehaviour
continue (default)Synthesis runs from whatever succeeded, and the synthesis prompt is told which angles failed. One dead search API shouldn't waste the good work from three other sub-agents.
failAny child failure fails the whole team run.

Resume semantics

The parent writes its roster — the list of planned children and their status — into its own checkpoint before any child starts. That ordering is what makes resume correct: without it, a parent that dies mid-fan-out can't tell "never started" from "already finished," and a resume would either lose an angle's work or redo it.

On resume, completed children are read back from the store and never re-executed — the same exactly-once guarantee the runtime already applies to tool calls, lifted one level. A child that was still in flight when the process died keeps its run id in the roster, so resume re-attaches to that same run rather than minting a second one and paying twice for the same angle. Only children that were genuinely still pending get (re-)admitted.

A child that hits a HITL-gated tool pauses exactly like a single-agent run would — but the roster records that entry as paused rather than failed, so on_child_failure never sees it and the team stays resumable instead of ending. The approval id surfaces on the parent's result, in TeamOutcome.pending_approvals, so a caller watching the parent doesn't need to already know which child to go looking in. Resuming by that child's own run id re-attaches to it — not a fresh run — and continues from the approved tool call, the same way any paused run resumes.

Honest limits

NoteFlat, not recursive

Children cannot spawn grandchildren. The supervisor emits a flat list[SubAgentTask] once, up front — there's no mechanism for a child to plan further children of its own. This is what keeps the budget ceiling and the recursion depth both bounded; open-ended spawning is out of scope for v1.

NoteChildren don't talk to each other

Each child is an independent run by construction — no shared state, no message-passing between siblings while they work. If one angle's findings need to inform another's, that has to happen in synthesis, after both have finished, not during.

NoteOne server serves one agent

POST /api/chat, /api/chat/stream and /api/runs/{id}/resume all dispatch generically — sdk: team works over HTTP exactly like any other adapter, there's no team-specific gap in the API. The real limit is narrower: a running server is wired to exactly one agent from its own config, so a single server can't serve both a single-agent chat bot and a research team at the same time — that takes two servers, one per config. And the playground is a single-agent chat UI, so a minutes-long fan-out is easier to watch as it happens in the dashboard, where children nest under their parent, each expandable into its own step tree. See demo/research.sh for a working example driven directly against the runtime.

NoteThe budget check is a projection, not a metered cutoff

Before every model call, agentFast estimates the size of the request about to be sent (budget_would_exceed) and stops there if it would breach the ceiling — a large tool result from the previous turn can't silently carry a child past its budget on the next one. The estimate is a fast character-based approximation, not the provider's own tokenizer, so it can be off by a small margin either way; the authoritative numbers still come from the provider's response afterward. The one call that's deliberately exempt from the check is the post-budget wrap-up turn itself — but that call is trimmed to fit the same ceiling first, by truncating bulk tool-result text rather than the agent's own findings, so it can't reintroduce the overshoot it exists to close out.

Related