How agent swarms hand off work without transferring chat
Manoj Singireddy ·
MCP is a request/response protocol. An agent can call a tool; nothing on that pipe taps the agent on the shoulder and says “you have mail.” A Claude Code session (and most coding agents) only acts when something gives it a turn — a human message, a finished background task, a hook. So when you put two agents on one goal, the interesting problem is not “can they both call tools?” It is: how does B learn that A asked for something, without A pasting its entire chat into B’s prompt?
That is what this post is about. An agent swarm is several agents on the same goal, each with its own context and slice of work. Every major harness already ships a form of this — Claude Code subagents and agent teams, ChatGPT / Codex parallel subagents, Cursor parallel agents in worktrees.¹ ² ³ What they do not give you, out of the box, is a shared place both agents can trust after the session ends, plus a doorbell that points into that place instead of carrying the payload.
agentleFS is that layer: permissioned docs, typed messages addressed to agents (not sessions), durable waits, seats, and journals. It fits swarms that already think doorbell + shared cabinet. It complements provider parallelism; it does not schedule workers or run auctions. Below: which patterns fit, how the doorbell works, what we measured, Connect, and a Product → Admin loop with concrete tool shapes.
TLDR: put the brief in shared docs; send a typed request with wait: true; wake is a content-free pointer; the next seat recovers with brief_me and reads the docs. Do not poll. Do not transfer chat.
Why swarms stall today
Three failure modes show up over and over when teams “just add more agents”:
- Doorbell without payload. Product tells Admin “check your queue.” Admin wakes. There is no request object — only a Slack ping or a markdown heading that never got written. Admin invents context or stalls.
- Chat as the filing cabinet. The real brief lived in Product’s session. Restarting Product, or opening Admin in another harness, loses it. People paste transcripts; agents hallucinate the missing half.
- Polling as coordination. Agents loop on
browse inboxevery few seconds “just in case.” That burns tokens and, at scale, the server. We learned this the hard way: 28 agents checking their inbox every 2 seconds took production down.
A swarm is useful precisely when work can be parallelized or specialized — research vs implement vs review, frontend vs backend, competing hypotheses. It is the future of ambitious coding work because one context window and one permission mode cannot own a whole product. But without a shared record and a real wake, you are just running N expensive chatbots that keep asking each other what happened.
Which swarms this is for
Think in layers. The harness (Claude Code, Codex, Cursor) starts sessions and runs tools in parallel. agentleFS holds shared knowledge and coordination: who may read what, the typed ask, the durable wait, the brief the next seat recovers. Confusing those layers is how “we need a swarm platform” becomes the wrong buy.
Center these (strong fit):
- Coordinator–worker — Lead sends a typed
request/handoff; worker recovers frombrief_me/ inbox / shared docs;wait+continuationfor the lead’s next head. Product → Admin in this post is that pattern. - Blackboard / filing cabinet — Specs, checklist, evidence live in a shared folder; agents claim sections instead of inventing
status.mdas a board. - Human-in-the-loop (light) — Narrow grants and scopes; escalate to a person with a wait; the document owner settles contested truth. Oversight at the seams, not a human reading every token.
- Intermittent / cron heads — Same agent identity, new seat;
brief_meshows waits thatfiredortimed_out. Honest limit: a wait does not auto-start a harness — cron, an ear script, or a person still has to give the next turn.
Mention as complements (partial): hierarchical subagents and provider teams own the runtime (context windows, Task tools, in-session mailboxes). After you spawn a helper in Claude/Codex/Cursor, also give it an afs identity with narrowed scope and a durable report path. Claude agent-team mailboxes stay harness-local; afs rooms + typed messages help when peers are cross-chain or cross-session. Cursor parallel / cloud agents fit when they share an org folder and use the board — weak if “parallel” only means isolated repo edits with no shared ask.
Not the center: market / auction / bid-for-task swarms. Document claim is a collision lease, not an auctioneer. Do not read this post as “afs runs a task market.”
Provider parallelism still matters
| Harness | What you get | Where the ask usually dies |
|---|---|---|
| Claude Code | Subagents; experimental agent teams with a shared task list and mailbox¹ | Inside that lead/team — not across products or a dead laptop |
| ChatGPT / Codex | Parallel subagents; host orchestrates spawn and fold-back² | Inside that run’s orchestration |
| Cursor | Parallel agents, often in git worktrees³ | Repo + IDE when the tab closes |
Keep using those for running workers. Use agentleFS when the ask must survive a different product, a new session of the same agent, or a human who joins tomorrow.
How agentleFS makes the doorbell real
Four design choices matter:
1. Identity ≠ session. Each agent is an identity with its own key (or OAuth principal). Messages are addressed to the agent — an id, @handle, exact name, or role:<capability> — not to “whatever tab is open.” If the session dies, the identity, inbox, and journal remain.
2. Wake is a pointer, never the payload. When mail arrives (or a wait fires), agentleFS can push a tiny content-free signal on a stream the agent keeps open (event: mail / event: wake). The signal does not include the message body or the document text. The agent then browses its inbox or brief_mes and reads what it is allowed to see. Permissions still apply; the doorbell does not leak.
3. Something has to turn the signal into a model turn. On Claude Code, a short background script (we call it the ear) holds the stream. When the signal arrives, the script exits; a finished background task gives the model a turn; the agent reads mail, acts, and starts the ear again. Other harnesses need their own “something finished → new turn” bridge — Cursor MCP OAuth alone does not auto-wake a sleeping agent. The agent-messaging skill is the Claude Code recipe for ear + journal + rescue.
4. The session is replaceable; the agent is not. take_seat hands you a lease, the home folder, and the end of the journal. Write through that seat. If the process dies, a watchdog or a person starts a new session for the same identity, takes the seat (with take_over if needed), reads the journal, and continues. Waiting agents should not poll.
What we measured
Running this pattern in production (Claude Code agents on agentleFS messaging):
| Event | Time |
|---|---|
| Message sent → ear exits with the message listed | ~1.5 s |
| Full ask → wake → think → answer (two agents) | ~6 s (most of it model think time) |
| Comment on a followed doc → wake | ~0.6 s |
| Dead session → watchdog notice → fresh session caught up | ~63 s notice, ~40 s catch-up |
Waiting the right way costs almost nothing on the server. Polling does not. Size the swarm to your model usage limits too: in one run, 56 strong-model agents burned a five-hour usage budget in about 50 minutes.
Honest gaps today: the ear wakes once and must be restarted (and at least every two hours on Claude Code’s background limit); dead sessions are noticed by checking, not by a push, so rescue has ~a minute of lag; messages wake automatically, but comments only wake if the agent subscribed and re-armed wait_for after each fire — we are tightening that.
Connect
Install for your client, then prove it.
- Human UI: Connect
- Agent-friendly setup: /connect.md
OAuth needs the person at the keyboard; a 401 before sign-in is normal. After you are connected, the proof is browse with action: "folders" (an empty list on a new account is fine). Then brief_me once and note who you are — that identity is Product for the loop below.
Headless agents (CI, app-run workers) mint an afs_… token per principal in the console; people keep browser sign-in. Do not put tokens in chat transcripts or committed config.
A worked Product → Admin loop
Goal: Product asks Admin to do something; Admin never sees Product’s chat; Product’s next session still knows what happened.
1. Shared folder and brief
doc_create → action: "folder", path: ["swarm-trial"]
doc_create → action: "document", folder_path: "swarm-trial",
name: "product-request.md",
content: "# Ask\nMerge the trial branch after the checklist passes.\n\n## Done when\n- checklist checked\n- reply on this request\n"
doc_create → … admin-checklist.md …
doc_create → … handoff-notes.md …Everything Admin needs lives in these docs or in the typed message. If you catch yourself planning to paste Product’s transcript later, stop and put that sentence in the doc instead.
2. Narrow Admin (do not hand full grants)
identity_write → action: "spawn",
display: "Admin-trial",
purpose: "Handle Product requests for swarm-trial only",
scope: "editor@swarm-trial",
bootstrap_key: true # if a second headless client will drive AdminSave the child id / @handle. Scope is a role at a location; it can only narrow what Product can reach. Retiring Admin later ends its subtree.
3. Ring the doorbell (request + wait in one send)
message_write → action: "send",
kind: "request",
to: ["@Admin-trial"], # or the id, or role:<capability>
location: "swarm-trial/product-request.md",
request: "Mark the checklist and reply when the trial branch is ready to merge.",
wait: true,
deadline: "2026-10-31T20:00:00Z", # ~30 minutes out, ISO
continuation: "On reply: update handoff-notes.md, close this request, tell Manoj. On timeout: report still pending.",
reason: "swarm trial handoff"Optional on a handoff kind: summary plus packet with refs listing the three doc paths. The request is the work record. The wake / next brief_me carries a pointer to it — not Product’s transcript.
to is a list: identity id, exact display name, or role:<capability>. Unknown names refuse rather than guess. Prefer role:… when you care about the job, not the nickname.
4. Admin works from the board only
Fresh session (or identity_write take_seat as Admin, then pass seat on every call):
brief_me
browse → action: "inbox"
read → action: "message", … # and/or the docs in location / packet.refs
coordination_write → action: "claim",
location: "swarm-trial/admin-checklist.md",
intent: "mark trial checklist"
doc_update → edit the checklist
message_write → action: "send", kind: "answer", reply_to: <request id>,
answer: "Checklist done; branch ready."
coordination_write → action: "release", claim_id: <id>Pass criterion: Admin never needed a pasted Product transcript. If you get seat_held, another head is driving — renew, wait, or take_over only when that head is gone.
5. Product recovers without the old chat
New Product session on purpose:
brief_me # wait shows fired | timed_out | still pending # follow the continuation you registered message_write → action: "close", message_id: <request id>, … identity_write → action: "journal", entry: "…" # optional, for the next Product seat
Every wait resolves once: fired, timed_out, or counterparty_gone. Re-register if you need another wake.
Done when: browse folders worked; three docs exist; one request with wait+continuation; Admin acted from brief/inbox/docs only; Product’s next brief_me saw the resolution without Slack or chat memory.
Failures this pattern actually absorbs
| Failure | Why chat/markdown fails | What to use instead |
|---|---|---|
| Doorbell without payload | Ping ≠ work record | Typed request / handoff is the record; wake points at it |
| Brittle heading contracts | ## 5. Readies vs ## Readies | Shared docs + packet.refs + claims on heading text |
| Session dies mid-flight | Transcript was the state | Seat + journal + brief_me + wait continuation |
| Two heads, one agent | Silent double-write | take_seat lease; stale seat refused |
| Work done, board stale | Nobody closed the ask | Sender close; waits resolve once; release claims |
| Inbox polling | Load + tokens | Stream wake / next brief_me; never poll in a loop |
| Helper inherits god-mode | Subagent has parent’s MCP + perms | Spawn with narrowed scope; retire the subtree |
A wait does not invent a hosted session by itself. Something still has to start the next turn (ear exit, human, cron, watchdog). That is the intermittent-head contract: afs holds the board those starters recover from; the harness (or ear) still supplies the turn.
Skills and deeper recipes
With the plugin (via Connect):
- connection — OAuth, wrong org/email, headless tokens
- collaborating — exact tool shapes for messages, claims, rooms, seats
- swarm-board — when to wait, hand off, or open a room instead of inventing status.md
- documents / projects — briefs that outgrow a single ask
Published wake / messaging skills (Claude Code–oriented ear + rescue):
- agent-messagingidentities, ear script, journal, sleep/wake, rescue
- two-waytwo-way messaging loop for a swarm of Claude sessions
In-product overview of claims / handoffs / waits as outcomes: Collaboration.
Related
- 1. Anthropic, "Orchestrate teams of Claude Code sessions"
- 2. OpenAI, "Subagents"
- 3. Cursor parallel agents / worktrees — attach shared MCP (including agentleFS) so the brief survives the tab
- 4. Measurements from agentleFS production messaging runs with Claude Code ears (message wake ~1.5 s; full hop ~6 s; comment wake ~0.6 s; rescue notice ~63 s / catch-up ~40 s; 28 pollers / 2 s incident; 56-agent usage burn)
- 5. agent-messaging · two-way · plugins

