Disclosure up front: I build agentproto, one of the
tools in this market. The primitives in the back half are real and the fields
are checkable; the problem in the front half is everyone's. Corrections
welcome — file an issue.
Here's the multi-agent dream, and it's a good one: you hand one capable agent a
big task, and instead of grinding through it alone, it spins up a little team —
one sub-agent to research, two to implement in parallel, one to review — waits for
them all, and hands you the merged result. An orchestrator that manages
orchestrators. It works, and it's genuinely powerful.
Now read that sentence again with an engineer's paranoia. An orchestrator that
manages orchestrators. If an agent can spawn agents, then the agents it spawns
can spawn agents. And nothing in "you can spawn a sub-agent" says where that
stops.
You already know this shape. It's the oldest prank in the Unix book:
:(){ :|:& };: # a function that calls two copies of itself, forever
That's a fork bomb. It does nothing malicious — each copy just politely starts
two more, until the process table is full and the machine is on its knees. A
coding agent with an unbounded spawn tool is the same structure with a language
model in the middle and your API bill underneath.
The one idea, if you remember nothing else:
Spawning isn't a feature you turn on. It's a privilege you grant — and a
privilege with no depth, no breadth, and no ceiling is a fork bomb with good
intentions.
The privilege you're granting without noticing
When you give an agent a spawn_sub_agent tool, you're not just adding a
capability. You're granting it the right to grant itself the same capability,
recursively, as many times as its reasoning talks it into. Most setups make that
grant implicitly and totally: if the tool is in the toolset, spawning is
unlimited — any depth, any number of children, and each child just as privileged
as the parent.
Two things make that expensive fast, and neither is hypothetical. First, cost.
Anthropic's own guidance on sub-agents is that you isolate exploration in them
precisely because each one can burn ten thousand tokens to hand back a
five-line summary — cheap when it's one, a bonfire when a confused parent
spawns twelve and each of those spawns twelve. Second, the multi-agent reflex
itself is usually wrong:
The receipt that should slow you down. The sharpest orchestration writers
converge on one rule: the number of agents a job spawns is the **output* of
the orchestration decision, not the input.* You don't pick a multi-agent
workflow because you can — you pick it because the work's shape demands it. An
agent that spawns because spawning is available has skipped the only decision
that mattered.
So the problem isn't multi-agent orchestration. It's ungoverned spawning —
handing an LLM a recursive, self-replicating capability and trusting its judgment
to not run it off a cliff. The fix isn't "don't spawn." It's "make spawning a
metered privilege."
How does your setup bound spawning today?
Place yourself honestly before you build. Four postures, each with a ceiling.
Unbounded. Any agent holding the spawn tool spawns freely — any depth, any
count. Powerful, and one confused reasoning loop from a runaway tree and a bill
with a comma you didn't expect. Ceiling: the first agent that decides the answer
to "this is hard" is "spawn more agents."
Concurrency-capped by hand. You cap how many agents run at once, in your own
harness code, watching a dashboard. Better — but it's your attention doing the
capping, and it doesn't stop depth, just width at one instant. Ceiling: you,
watching.
Worktree-isolated only. You give each parallel agent its own git worktree so
they don't stomp each other's files — the near-universal isolation trick, and a
good one. But a worktree isolates the filesystem, not the right to spawn. A
tidily-isolated agent can still fork-bomb. Ceiling: clean files, unbounded tree.
Privilege-gated. Spawning is a role, capped by depth and breadth, and a child
can never grant itself more than the parent had. This is the one that can't run
away, and it's the one you can declare instead of police.
Where are you? If your honest answer is "any agent with the tool can spawn
as much as it wants," you don't have orchestration — you have a fork bomb that
hasn't gone off yet. The rest of this is four bounds that make sure it never
does.
Bound 1: spawning is a role, and most agents shouldn't have it
The first bound is the bluntest: most agents in a tree should be leaves that
physically cannot delegate. In agentproto that's a spawn-time role.
// this child does the work and CANNOT spawn — the spawn tools are removed
agent_start({
adapter: "claude-code",
role: "executor", // leaf: agent_start / agent_prompt are stripped
cwd: "/abs/host/path",
prompt: "Implement the retry cap in src/payments/retry.ts. Do not delegate."
})
An executor is a leaf by construction: the agent_start and agent_prompt
tools are stripped from its toolset, and asking it to become an orchestrator
is simply ignored — no amount of clever prompting gives it back the ability to
spawn, because the capability isn't in the room. A supervisor may delegate.
That single distinction turns "every agent is a potential parent" into "spawning
is a named privilege that most of the tree doesn't hold."
This is the same lesson the OWASP Agentic Top 10 files under least agency:
give an agent only the autonomy its task needs. A worker implementing a function
doesn't need the right to raise an army. Take it away, and a whole class of
runaway trees becomes unrepresentable.
Bound 2: depth — the recursion has a floor it can't dig past
Leaves can't spawn, but supervisors can, and a chain of supervisors is still a
recursion. So the second bound caps how deep the tree can go, and makes the cap
one-directional.
agent_start({
role: "supervisor",
orchestrator: { maxDepth: 3, maxChildren: 8 }, // this subtree, bounded
// …
})
maxDepth defaults to 3 and has a hard ceiling of 8 — a spawn that would
exceed it is rejected outright, not queued. The load-bearing detail is the
direction: a recursive spawn can only lower the inherited cap, never raise
it. A parent at depth 3 can hand a child a budget of 2; that child cannot hand
its child a budget of 5. The fork bomb's whole trick is unbounded recursion;
capping depth with a monotonically-shrinking budget removes the trick. The tree
has a floor, and nothing inside the tree can dig past it.
Bound 3: breadth — no single agent fans out into a swarm
Depth stops the tree going infinitely down; breadth stops any one node
exploding sideways. maxChildren defaults to 8 concurrently-alive
sub-agents per parent and, like depth, a recursive spawn can only lower the
inherited quota — never raise it.
That's the direct antidote to the :|: in the fork bomb — the part where one
process becomes two becomes four. Cap the branching factor and the geometric
explosion can't start. Combined with depth, your worst case is now a known
number of agents (maxChildren ^ maxDepth, and shrinking as it descends), not an
open-ended one. You can look at a spawn config and say exactly how bad a confused
run gets. That sentence is impossible in the unbounded world.
Cost gets its own hard stop. Independently of the tree shape, each session
takesmaxCostUsd— a cumulative dollar ceiling; the session is stopped at the
next turn-end once it's exceeded. Depth and breadth bound the count; this
bounds the spend directly, so even a legal-sized tree of expensive turns has a
number it can't cross.
Bound 4: a child can't out-privilege its parent
The last bound is the subtle one, and it's what makes the other three trustworthy:
a child can never end up more powerful than the parent that made it. The daemon
enforces a privilege lattice — a spawn made through an orchestrator may only
create a role at or below the caller's own level, never something more privileged,
and an explicit tools allowlist can only ever narrow the toolset, never widen
it. A child can never grant itself scope its parent didn't have.
And the parent's view is boxed, too. The daemon mints a per-child scope-token,
injects a scoped sub-gateway into that child's session, and revokes the token
when the session exits. A parent's session_tree shows only its own subtree;
it can't see, prompt, or kill sessions outside its branch. Shell, filesystem,
remote, and import tools are never handed to an orchestrator child at all. So
the tree isn't just size-bounded — every node is capability-bounded to a shrinking
box, and the boxes evaporate when the work is done.
Four bounds, one property: you can hand an agent the genuinely powerful ability to
build and run a team, and still be able to state — before it runs — the maximum
depth, the maximum breadth, the maximum spend, and the ceiling on privilege. The
capability stays; the fork bomb becomes unrepresentable.
The honest edges (what bit me building this)
Real infrastructure, real sharp corners. Four I hit live:
- The parent has to be Claude Code. A hermes parent silently ignores the injected orchestration gateway — it never mounts the spawn tools, so nothing happens. Children can be any adapter (cheap Haiku for trivial work); the parent that does the orchestrating must be Claude Code. If a parent reports "the orchestration tools aren't mounted," that's the tell.
-
Fan-in races fast children. A trivial child can finish its turn before the
parent has wired up its wait — and
turn-endis a transient event, so the wait times out on a child that already succeeded. Teach the parent the fallback: if the wait times out, read the child's output to confirm, or take an event cursor before spawning. -
Killing the parent doesn't kill the children. The scope-token is revoked on
parent exit, but the child processes are full sessions of their own. Kill the
parent and each child (their ids are in
session_tree), or you'll leak running agents. -
cwdmust be a real host path for every child — the daemon runs on your machine, and a child with no resolvable working directory just errors out.
And the fair comparison. Vendors do ship sub-agents — Claude Code's Agent Teams
will run a team for you, and inside one vendor's stack it's smooth and needs none
of this wiring. Two things a built-in team doesn't give you: the spawn tree stays
inside that vendor (your cheap open-model executors aren't invited), and the
governance is theirs, not a set of depth/breadth/role/cost bounds you declare and
own. If you live entirely in one vendor and never want a mixed fleet, take the
built-in — it's less work. If you want to spawn across vendors and sleep at
night, the bounds have to be yours.
Give it a team. Keep the blast radius finite.
The instinct to stop agents from spawning is the wrong lesson — spawning a team
is exactly the leverage that makes agents worth orchestrating. The right lesson is
that a self-replicating capability handed to a probabilistic reasoner needs the
same thing every other powerful capability needs: limits it can't rewrite.
So grant the privilege deliberately. Make most agents leaves that can't delegate.
Cap the depth so the recursion has a floor. Cap the breadth so no node fans into a
swarm. Cap the spend and the privilege so a child is always smaller than its
parent. Do that and "an agent that spawns agents" stops being a fork bomb and
starts being what you actually wanted: a team with a foreman, and a foreman who
can't accidentally hire the whole city.
Let the agent build its team. Just make sure the tree has a floor, the branches
have a limit, and the whole thing dies when the job's done.
If your orchestrator bounds spawning a cleaner way — or you've found a runaway
tree these four bounds wouldn't have caught — tell me where. I'll fix the piece.
Written by the maintainer of agentproto (Apache-2.0,
source) — a local, cross-vendor daemon for
running and governing coding agents. Same contract as our
/compare page: dated facts, named strengths,
corrections by issue. Got something wrong?
File an issue.
Building agentproto in the open — follow @theagentproto and @agentik_ai on X.
Top comments (2)
The privilege-grant framing is the right one, and I'd add one practical failure mode we ran into that isn't just about depth: a spawned agent that inherits the full toolset of its parent, including the spawn tool, creates the recursive structure you describe even when the orchestrator has a depth limit, because the child can spawn laterally at its own level rather than going deeper. We fixed this by having each sub-agent receive a stripped toolset with spawn explicitly removed unless the task plan explicitly scoped it in, treating tool inheritance like capability dropping rather than capability forwarding.
Thanks for your comment in input @hannune !
Yes definitely, the proprioception of an agent is very weak and prompted guardrails are not sufficient.
The possibility to manage the tools/MCP of a subagent is necessary. Do you use a single provider sub agent or can you spawn from multiple like claude/hermes/codex ?
In the agentproto case, one of the challenge was to have inheritance working on multiple different harnesses. We did the same thing for skills, as some are only relevant to specific types of operator. I'll be happy to get more information about your work.