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.
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 seedsCLAUDE.local.mdwith theinstructionstext — that’s the new agent’s persistent role and personality, editable later like any agent’s memory. A defaultcontainer_configsrow comes with it. - 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>.
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:
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.
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.
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”).
- 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.
MAX_CONCURRENT_CONTAINERS(default 5) is parsed at startup but not enforced anywhere as of v2.1.4 — 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