The model in 60 seconds
There is no swarm engine. Multi-agent in NanoClaw is the same three things everything else is:- Agent groups are fully isolated. Each agent has its own container, workspace, and memory. Agents never share state — they can only message each other.
- Agent-to-agent messages are queue rows. A message to another agent travels through the same SQLite inbox/outbox pipeline as a WhatsApp message; only
channel_typediffers (agentinstead of a platform). - Destinations are the permission edges. A row in
agent_destinationsmeans “agent A may send to target B, and calls itlocal-name”. No row, no delivery — the host rejects unauthorized sends. Channels and agents share one namespace, so messaging an agent is justsend_messagewithtoset to its name.
agent_destinations rows. Each direction is a separate row — A having an edge to B does not give B an edge back.
1
Ask your agent to create a helper
Message the agent the setup wizard created (the one in your main chat):The agent calls the
create_agent MCP tool with a name and that role as instructions. The call is fire-and-forget — it returns immediately, and the host does the privileged work:- Inserts an
agent_groupsrow. The folder is the normalized name (researcher), with a numeric suffix if taken. - Scaffolds
groups/researcher/and seeds theinstructionstext into the new agent’sinstructions.prepend.md— its standing instructions, composed into the top of the child’sCLAUDE.mdat spawn (the same on every provider now). That’s the agent’s persistent role and personality, editable later like any agent’s. A defaultcontainer_configsrow comes with it, and the child inherits its creator’s provider. - Inserts two destination rows: the creator gets
researcher→ new agent, and the new agent getsparent→ creator. The edge is bidirectional from birth, so replies need no extra wiring. - Projects the new destination into the parent’s running container immediately, then notifies it:
Agent "researcher" created. You can now message it with <message to="researcher">...</message>.
2
The approval gate
Why did that just work without asking you? Authorization depends on the calling group’s
cli_scope in its container config. The wizard’s first agent is trusted (cli_scope: global), so it creates directly. Every other group — including Scout, since ncl groups create leaves the default group scope — is a potential prompt-injection victim, so the host queues an approval instead. Ask Scout to create an agent and an approval card lands in an admin DM:Create agent: researcher Agent “Scout” wants to create a new sub-agent “researcher” (a new agent group with its own workspace and container). Approve? [ Approve ] [ Reject ]The approver is picked in order: admins of that agent group → global admins → owners, preferring one reachable on the same channel the request came from. If no owner or admin with a reachable DM exists, the request fails and the agent is told why. To promote an agent you trust to create freely:
3
Watch them talk
Now delegate through the parent:What happens on the wire:
- The parent sends
send_message({ to: "researcher", text: ... })(or a<message to="researcher">block — same delivery). The host resolvesresearcheragainst the parent’s destinations, checks the ACL, and copies the message into researcher’s inbox. If researcher has never run, the host creates anagent-sharedsession for it and spawns its container. - Researcher sees
<message from="parent">…</message>— destination names are local, so the child knows its creator only asparent. Its base instructions say to reply to thefromdestination, so it answersto="parent". - The return path is precise. Every routed a2a message is stamped with the sender’s
source_session_id. When researcher replies, the router looks up which parent session started the exchange and delivers there — not to whichever parent session happens to be newest. Files survive the hop too:send_fileattachments are copied from the sender’s outbox into the receiver’s inbox.
4
Scale to worker / manager / supervisor
The trio from the diagram is the same pattern with more edges. The conversational route: tell your trusted agent to create a
manager (instructions: track tasks in flight, delegate, chase stragglers) and have the manager create its own workers — create_agent works from any agent, subject to the same scope/approval rule, and each creator gets its own edge to its children.Sibling and cross-level edges don’t exist by default (workers can’t reach the supervisor) — add them by hand. Each ncl destinations add is one direction:ncl destinations add/remove project the change into running containers immediately — no restart needed.Two finishing touches:-
Supervisor pings you: wire the supervisor to your DM (
ncl wirings create --messaging-group-id <your-dm-id> --agent-group-id <supervisor-id> ...) so it receives your messages — and can reply, since an agent may always answer its origin chat. Butncl wirings createis a plain row insert: it does not create the channel destination (only the setup scripts’ wiring path auto-creates one), so a proactive send addressed by name fails the ACL. Add the edge explicitly:Now the supervisor can message you unprompted — and useask_user_questionto put actual buttons under a decision. -
One conversation per task: wire a worker to a thread-capable channel (Discord, Slack) with
--session-mode per-threadand every thread gets its own session — each task runs in a clean context instead of one ever-growing conversation. On server channels this is forced anyway; the flag matters for DMs.
5
Observe the topology
Everything is rows, so the whole team is inspectable:The destinations list reads as the manager’s address book:
parent, worker-1, worker-2, plus any channel it’s wired to.Gate a connection
A wired destination means messages flow with no further checks — fine for agents you trust, riskier when one agent relays anything attacker-influenced into another’s context. A message policy holds every message on one edge for human approval, without un-wiring it:- Directed — this gates
source → targetonly. Gate the reply path with a second policy. - Operator-only — agents can’t gate or un-gate their own edges; it’s a host-side
nclcommand, never reachable through prompt injection. - Tied to the connection — delete either agent group and its policies go with it, so no gate outlives the edge it guarded.
ncl policies remove --from <source-agent-id> --to <target-agent-id>. Full resource reference: ncl policies.
Limits
- No loop protection. There is no throttle, hop limit, or cycle detection on agent-to-agent messages — two agents told to “always reply” will ping-pong indefinitely, burning API calls. Give every agent’s instructions an explicit stop condition (“report back once, then wait”). A message policy on the edge also breaks a runaway loop, since every hop then waits for a human tap.
- No broadcast. Each message targets one destination. Fan-out means one
<message>block (orsend_messagecall) per recipient — the parent orchestrates, the queue doesn’t. - No concurrency cap. There’s no environment variable that limits simultaneous agent containers — the old
MAX_CONCURRENT_CONTAINERSwas removed from the code entirely, not just left unenforced. A wide team really does run one container per active agent, so size the host accordingly. - Edits to destinations outside
ncldon’t propagate live. A running container serves a projection of its destinations, refreshed on every wake and byncl destinations add/remove— but direct DB writes leave it stale until the next wake.
Cleanup
groups/<folder>/ and data/v2-sessions/<group-id>/ — stop the container and remove those by hand if you want the workspace gone.
Next steps
- Scheduled tasks — have the manager run its review on a timer
- MCP tools reference —
create_agent,send_message,ask_user_questionparameters - Entity model — sessions, wirings, and session modes in depth