Chapter 13 · Systems
Agent communication
Agents pass information through ordinary channels such as return values, queues, and databases. A model can act only on the part that reaches its context.
Two layers: coordination and context
Two coding agents work in the same repository. Before one edits auth/session.py, it must make sure the other is not editing it. Later, a reviewer agent must tell the coder that a test still fails.
These are two kinds of communication. The first is coordination: who works on what, who owns a file, whether a task is done. Your code handles it with locks, queues, and status fields, and none of it needs to reach a model. The second is information a model must act on, such as the failing test. That has to enter the coder's context, because a model sees nothing else (chapter 4).
Everything in a context costs tokens and attention. It can also carry instructions, because a model treats all of its input as guidance. So send a reference instead of the content when you can: a file path, a task id, or a commit hash. For example, a research agent saves 40 KB of notes to a file and returns one line with the path. The parent's context grows by one line instead of 40 KB.
Parent and child: return values
A coding agent needs to know where expired tokens are accepted. It hands the question to a subagent, which searches the code in its own loop and returns five lines.
result = delegate(
"Find where expired tokens are accepted. "
"Return at most 5 lines: file:line and one sentence each.",
tools=["search_files", "read_file"],
)
# only `result` enters the parent's context
This is the simplest channel. The parent calls the child like a function, and the child's answer comes back as one tool result. It fits a clear question with a short answer, which is where most subagent designs start (chapter 7).
The parent waits, or checks back later if the child runs in the background. Anything the child leaves out is lost, and a long answer floods the parent's context. So ask for a fixed format and a length limit, and put the details in a file.
Messages
A reviewer agent sends the coder a message: the test still fails, and the details are in a file.
{"from": "reviewer", "to": "coder", "re": "task-412",
"kind": "change_request",
"note": "test_login_expired_token still fails",
"ref": "/work/review/412.md"}
With messages, one agent writes to another agent's inbox. The receiver's code decides when each message enters its model's context, usually as a new message on the next turn. Messages fit a few long-lived agents that go back and forth, such as a review loop.
Every message enters a context, so chatty agents get expensive, and two agents can keep replying to each other forever. Treat each message as untrusted input, too. If the sender read a hostile web page, its message can pass the attack along. So give messages fixed fields, and keep the free text short.
Event bus
The build system announces that a build failed. A triage agent starts a run, and a notifier posts in the team chat. The build system does not know that either of them exists.
bus.publish("build.failed", {
"repo": "web-api", "commit": "9f2c1e7",
"log": "ci/runs/5512/log.txt", # a reference, not the log itself
"event_id": "evt-77a1", # so repeats can be spotted
})
That is an event bus, also called publish and subscribe. A component publishes an event on a topic, and every subscriber to that topic reacts. You can add subscribers without changing the publisher, so a bus suits the triggers that start event-driven agents (chapter 1).
The costs are in tracing and delivery. When one event sets off a chain of reactions, it gets hard to answer "what started this run?". Many buses can deliver an event twice, so subscribers check event ids. And an agent that reacts to its own events can loop. To catch that, give each event an id that links it to its cause, and count the hops.
Let events carry facts, such as "build 5512 failed", not instructions, such as "please fix the build".
Task queue
A planner agent adds task 7 to a queue. A research worker claims it, writes the answer onto the task, and marks it done. Then the planner plans the next step.
{"id": 7, "kind": "research", "status": "ready",
"ask": "Which auth tests touch token expiry?",
"parent": 3, "result": null}
In a task queue, the message is the task record itself: the request, its status, its owner, and the result. Anyone can look at the board, including a person, who can also change a task. A task also survives when its worker crashes, because a lease hands it to another worker (chapter 12).
A queue fits durable work, many workers, and people who need to watch or steer. The costs are slower handoffs, more moving parts, and tasks that can run twice. A task that keeps failing usually moves to a dead-letter queue, a separate queue where a person can inspect it.
Blackboard
Three specialist agents debug the failing login test together. They share one board with sections for symptoms, possible causes, evidence, and ideas ruled out.
board = {
"symptoms": ["expired tokens still accepted",
"test_login_expired_token: 200 != 401"],
"hypotheses": [
{"id": "h1", "by": "code", "text": "10-minute grace period in auth/session.py"},
{"id": "h2", "by": "logs", "text": "token cache serves stale entries"},
],
"evidence": [{"for": "h1", "by": "test", "ref": "runs/882/test.log"}],
"ruled_out": ["clock skew on the test runner"],
}
next_agent = controller.pick(board) # a set of rules, or a model call
This is the blackboard architecture, an old idea from AI research. Specialists watch the board and add to it when they can help. A controller picks who acts next, based on the board. Here, the controller might see that h2 has no evidence yet and wake the test agent.
Unlike plain shared state, the board is organized like the problem, and nobody fixes the order of work in advance. That suits problems such as diagnosis, where the next step depends on what the specialists find.
Database
Two coding agents must never edit the same file at once. Each one claims a file by inserting a row into a table. The database refuses a second row for the same file:
-- one row per file being edited; a second claim fails
CREATE TABLE claims (path TEXT PRIMARY KEY, agent TEXT, run_id TEXT);
INSERT INTO claims VALUES ('auth/session.py', 'coder-2', 'run-77');
The second agent's insert fails, and its code tells the model "file taken". But the claim protects the file only if every way of writing checks it. The edit tool must refuse to write without a claim. A general shell can write any file and skip that check. So agents that share files either lose the shell or work in separate copies of the repository.
Claims also need an end. An agent deletes its claim when it finishes. A claim whose agent died expires after a timeout, like a lease (chapter 12).
A database is shared state that lives outside any program. It survives crashes, many programs can use it, and constraints keep its data consistent. Wrap it in narrow tools such as claim_file and get_task, rather than letting models write raw SQL against shared tables.
Tradeoffs
Each channel trades the same six properties. Coupling is how much each side depends on the other. Consistency is whether every agent sees the same data. Observability is how easily you can see what happened. The other three are how failures are handled, how much enters a context, and whether things arrive in order.
| Channel | Coupling | Consistency | Observability |
|---|---|---|---|
| Return values | Tight: the parent waits | Simple: one answer per call | Good, if child logs are kept |
| Shared state | Hidden: any writer affects every reader | Needs merge rules for writes that arrive together | Poor, unless writes are logged |
| Messages | Direct: the sender names the receiver | Each agent's view can drift | Good, if messages are logged |
| Event bus | Loose: publishers do not know subscribers | Subscribers catch up eventually | Hard: causes spread across subscribers |
| Task queue | Loose, through task records | The task's status is the truth | Good: the board shows every task |
| Blackboard | Loose, but tied to the board's layout | A controller settles conflicts | Good: the board is the record |
| Database | Loose, through a schema | Transactions and constraints | Good: the history can be queried |
| Channel | Failure handling | Context cost | Ordering |
|---|---|---|---|
| Return values | The parent sees the error and decides | The whole answer enters the parent | Call order |
| Shared state | A bad write spreads silently | None until read | Undefined when writers overlap |
| Messages | Lost or repeated messages need rules | Every message enters a context | Per sender, if the channel keeps it |
| Event bus | Events can arrive twice; check ids | Only what a subscriber passes on | Within one topic, at best |
| Task queue | Leases, retries, and a dead-letter queue | The task's request and references | Roughly queue order |
| Blackboard | A wrong entry misleads every reader | Grows with the board, unless split | Chosen by the controller |
| Database | Transactions undo failed writes; constraints refuse bad ones | Only the rows queried | Per transaction |
A sensible order to try them:
- Start with return values.
- Put large results in shared files, and return the paths.
- Use a task queue when work must survive crashes or be visible to people.
- Use an event bus for triggers, not for conversations.
- Use a database when many processes share facts that need rules.
- Use a blackboard when specialists must build one picture in an order you cannot plan.
Protocols: MCP and A2A
Say the team's ticket system runs an MCP server. Patch's code asks it which tools it offers, shows them to the model, and forwards the model's requests.
MCP, the Model Context Protocol, connects an agent's code to tools, data, and prompts offered by another program (chapter 2). It is not an agent loop, because your code still runs the loop. It is not a security boundary either. The server's tools run with the server's access, and the spec itself says MCP "cannot enforce these security principles at the protocol level".
A2A, the Agent2Agent protocol (version 1.0), covers the other pairing. In its own words, "MCP standardizes agent-to-tool communication; A2A standardizes agent-to-agent communication." Each agent publishes an Agent Card, a JSON document that lists its skills, its address, and how to sign in. Work travels as a Task with an id and a lifecycle, much like a record in the task queue above. The agents do not need to share their memory or tools.
Neither protocol decides what enters a context or who may do what. Those decisions stay with you.
Running example: Patch talks through files
Patch's subagents already return short reports (chapter 7). Now each subagent's final message has two parts: a summary of at most five lines, then its full notes. The runtime saves the notes in a shared workspace and passes the parent only the summary and the file path. Briefs work the same way, so the reviewer gets paths to the diff and the test output, and the subagents stay read-only.
# Patch v12: reports and briefs carry full contents
- report = delegate(explorer, task)
- review = delegate(reviewer, brief + diff_text + test_output)
# Patch v13: the final message is a summary (5 lines at most), then the full notes
+ report = delegate(explorer, task, format="summary, then full notes")
+ path = workspace.save(f"explore/{task.issue}.md", report.notes) # the runtime saves the notes
+ parent.add(tool_result(report.summary + "\nFull notes: " + path)) # the parent sees only this
+ review = delegate(reviewer, brief + "Diff: /work/412.diff Tests: /work/412-tests.txt")
What goes wrong
The parent's context floods
- Why it happens
- A child returns its whole transcript or a huge result, which buries the parent's earlier instructions.
- How to spot it
- Track the size of each tool result, and watch for jumps right after a delegation.
- What to do
- Set a length limit and a format for answers, and return file paths instead of contents.
Two agents overwrite each other
- Why it happens
- They share files or state, write at the same moment, and nobody owns anything.
- How to spot it
- Lost changes, merge conflicts, or reducer errors.
- What to do
- Give each file or field one writer, make every write tool check a claim first, or give each agent its own copy of the repository.
An attack travels between agents
- Why it happens
- A research agent reads a hostile page and passes its demand along in a message. The next agent treats it as a request.
- How to spot it
- Agent messages contain instructions that no user gave.
- What to do
- Treat other agents' output as data, give messages fixed fields, and enforce permissions in every agent's code (chapter 10).
You decide, AI builds
Decide yourself
- Which channel each pair of components uses, and why.
- What enters each model's context, in what format, and how big it may get.
- Who owns each file, record, and field, and how conflicts are settled.
- Which messages count as requests, and which are only data.
Delegate to AI
- Queue, bus, and database plumbing.
- Message formats and validation.
- Ids that link events to their causes, and logs across agents.
- Connections to MCP servers and other protocols.
Check yourself
How do agents communicate? (Q26)
Answer
Through ordinary channels: return values, shared state, messages, an event bus, a task queue, a blackboard, or a database. Coordination stays in code. Only what a model must act on enters its context, ideally as a short answer or a reference.
A subagent produced 30 KB of notes. What should the parent receive?
Answer
A short summary plus a path, so the parent's context stays small and the details are one read away.
Two agents must never edit the same file at once. Where should that rule live?
Answer
In an enforced claim. The database refuses a second claim, and the write tools refuse to edit without one. A prompt or a message can be ignored.
Takeaways
- Keep claims, locks, and task status in code or a database, where rules are enforced.
- Send a model only what it must act on, and prefer a path or an id to the full content.
- Protocols such as MCP and A2A standardize connections, not decisions about context, permissions, or retries.
Going deeper
- Model Context Protocol specification, including its security principles.
- A2A key concepts: Agent Cards, Tasks, and Artifacts.
- How we built our multi-agent research system (Anthropic, 2025), whose subagents pass back lightweight references.