How Do Claude Code Subagents Communicate? They Don't
There is no channel between subagents. If two of them have to agree on anything, the agreement lives in a file with exactly one owner per field, or it does not exist. Here is the file, and the race that eats it.
>This is the state file the rest of the system is built on. Claude Code In Parallel adds the meter, the verifier, the router and the rollback on top of it.

Hello builders,
If you’re asking how do Claude Code subagents communicate, the honest answer is that they do not. Each one runs in its own context window. Agent A cannot see what agent B is doing, has done, or has decided, and no amount of prompting fixes it, because there is no channel for the prompt to travel down. So if we want two of them to agree on anything, that agreement has to exist somewhere both can read. That somewhere is a file on our disk, or it’s nowhere.

Before we decide a JSON file is a workaround for not having a real system, look at what the people who build the models do when they run agents at scale. Anthropic put sixteen agents on writing a C compiler in Rust capable of compiling the Linux kernel. Not a hundred and forty-four. Sixteen. Their coordination mechanism, in their own words, was that Claude takes a “lock” on a task by writing a text file to current_tasks/, then pulls from upstream, merges changes from other agents, pushes, and removes the lock. Nearly two thousand Claude Code sessions across two weeks, two billion input tokens, a total cost just under twenty thousand dollars, and the shared state was files and git.
The same pattern shows up in the product. Claude Code’s agent teams keep everything on disk in paths you can cat:
# Team config
~/.claude/teams/{team-name}/config.json
# Shared task list
~/.claude/tasks/{team-name}/
# Per-agent mailbox
~/.claude/teams/{team-name}/inboxes/{agent-name}.json
And on the race condition we’re already worrying about, the documentation is direct:
Task claiming uses file locking to prevent race conditions when multiple teammates try to claim the same task simultaneously.
Two teammates editing the same file leads to overwrites. Break the work so each teammate owns a different set of files.
(Claude Code documentation, Run agent teams)
So the answer to “should I use a database for this” is that the vendor did not, at sixteen agents and two thousand sessions. Start where they started and move later with real numbers, which beats guessing at a schema now.
One writer per field
Here is the only structural rule, and everything below is detail around it.
Every field has exactly one owner, and only its owner writes it.
Not “agents coordinate on the file”, and not a mutex either. Ownership, assigned once and enforced by the layout. Look at the diagram again: every edge is labelled read or write, and never both. Node count_files writes nodes.count_files.* and reads whatever it likes. Node report writes nodes.report.* and reads everything. If two nodes can write one key, we do not have shared state, we have a race with a nice filename.
The shape is deliberately boring:
{
"run_id": "1785343267",
"nodes": {
"count_files": { "owner": "count_files", "status": "pending", "result": null },
"count_lines": { "owner": "count_lines", "status": "pending", "result": null },
"report": { "owner": "report", "status": "pending", "result": null }
}
}
Four decisions worth defending:
run_idis epoch seconds, not a formatted timestamp, so it sorts correctly and never gets ambiguous across time zones.statusis a small closed set ofpending,running,completeandfailed, and you should resist adding a fifth, because every status you add becomes a branch somewhere in the resume logic.resultis a path. Do not inline the content: node outputs get large, and a state file you cannot read in a terminal is a state file nobody ever opens.ownerrepeats the node’s own name, which looks redundant right up until a node writes a sibling’s key by accident and the mismatch shows up in onejqexpression before it costs you an afternoon.
The bug you get the first time two nodes write at once
Run those nodes one after another and nothing races. Run them at the same time and the obvious read-modify-write is a bug. Here it is stripped to nothing:
#!/usr/bin/env bash
# w.sh <node>
n="$1"
tmp=$(mktemp)
jq --arg n "$n" '.nodes[$n].status="complete"' state.json > "$tmp"
sleep 0.3 # the window: real work happens between read and write
mv "$tmp" state.json
Two nodes, at once, on a file where both start pending:
$ ./w.sh a & ./w.sh b & wait
$ cat state.json
{ "nodes": { "a": { "status": "pending" },
"b": { "status": "complete" } } }
Read that carefully. Node a finished. Node a wrote complete. Node a’s write is gone. Node b read the file before a wrote to it, held its copy for three tenths of a second, then replaced the whole file with a version in which a had never happened. The only reason it’s visible here is the sleep I put in the window. In a real graph that window is however long jq takes, which is small enough that this works ninety-nine times and then eats a node’s status on the run that mattered.
The reflex fix is a lock, and the reflex lock is flock. On the machine most of us are typing this on:
$ ./w2.sh a & ./w2.sh b & wait
./w2.sh: line 4: flock: command not found
macOS does not ship flock at all, which is a detail worth knowing before you copy a fix off a forum. Half the advice you will find on this problem does not run on your laptop.
So stop sharing the file for writes at all. One file per writer, and derive the state on read:
#!/usr/bin/env bash
# w3.sh <node> — no lock, no shared write.
n="$1"
printf 'complete' > "status/$n"
$ ./w3.sh a & ./w3.sh b & ./w3.sh c & wait
a=complete
b=complete
c=complete
Three concurrent writers, three surviving writes, no lock, no library, and it works on any machine with a filesystem. Two writers can never collide because they never touch the same path, which is the ownership rule enforced by the operating system and not by our discipline. It’s also, near enough, what those sixteen agents were doing with one lock file per task. If we genuinely need one JSON object at the end, we build it on read with jq -n 'reduce inputs as $i ({}; . + $i)' status/*.json, never on write.
Why owning the file matters more than it looks
There’s one more reason to keep this state yourself, and it’s in the runtime’s own documentation on resume:
Replay follows the order agents started. Cached results stop at the first agent that did not finish, and every agent that started after that one runs again, even if it completed.
Four agents, A through D. Stop it while B is running. On resume, A returns from cache, B runs again, and C and D run again too, because they started after B, even though both had finished. We already paid for them. And resume only works inside the same Claude Code session, so closing the terminal loses the run entirely.
None of that is a reason to avoid the runtime. It’s the reason to own the file. Once our nodes check our own state, a node we recorded as complete does not run again no matter what the replay rule thinks.
One last thing, because it is the crack the next problem goes through. Look at what that file actually says: "status": "complete". Who wrote it? The node did, about itself. Right now complete means the process exited zero and the script recorded it, which is a genuine improvement on “the model said so” and nowhere near enough. A node can exit zero having produced confident nonsense, and our state file will record that as success in a nice green word.
So build the file, then go and kill a run halfway through on purpose and resume it. Point at the line that made it skip. If any field in there has more than one node that could have written it, fix that before we add a fourth node, because the fix gets more expensive with every one we add.
Now go build something this weekend!
John Cook