Part 2 of 4 · Building a multi-agent team
Agent-to-Agent Communication: Inboxes, Turns, and Why Nobody Waits
How asynchronous agent-to-agent messaging works: an inbox per agent, one turn per wake, and the two class settings that decide where an agent's output goes — plus what blocking costs you.

Agyn has no orchestration layer that calls agents as functions. Agents communicate by messaging, and four concepts cover the entire mechanism — after which "fan out to three specialists" turns out to be one turn that sends three messages and stops.
Throughout this series we build one team: Iris Vale, a coordinator, and three panelists who never speak to each other — Tomas the founder, Ben the on-call engineer, and Viktor the head of operations. You bring a topic; the panel gives you a verdict each. The full source is public: agent-to-agent-example.
Classes, instances, and inboxes
What you configure — image, model, prompt, tools — is an agent class. It is a definition, not a running thing.
When a class is added to a conversation, the platform creates an instance of it: a specific running copy with its own identity, its own disk, and its own inbox. The inbox is the important part. It is an ordered log of every message addressed to that instance, from every conversation it participates in.
Sending a message to a thread does not call anybody. It writes one inbox item per agent participant, and returns. The sender learns nothing about when — or whether — the message was read.
One message in, one turn out
An inbox with unprocessed items means there is work to do. The platform notices, starts a workload for that instance, and lets the agent run. The agent reads its inbox, does one turn, and ends it. The workload can then shut down.
When a reply arrives later, it lands in the same inbox and wakes the same instance again. Because the instance has a disk, its state is still there — this is the same agent continuing, not a new one re-reading history from scratch.
Two properties follow, and they are the reason this model is worth learning:
- No agent ever waits. "Fan out to three specialists" is one turn that sends three messages and stops. The waiting is not the agent's problem; it does not exist as far as the agent is concerned.
- Delivery survives failure. Items stay unacknowledged until a turn completes, so a workload that crashes mid-turn gets its messages again. This is at-least-once delivery, with the duplicate handling that implies.
Knowing who said what
Because one instance can be woken by any of its conversations, every message it receives is tagged with its origin:
thread: <thread id>from: @ben---<the message>The coordinator relies on this. It is holding four conversations — one with the user, three with specialists — and each time it wakes it needs to know which one just spoke.
Where an agent's output goes
An agent CLI ends its turn with plain text that names no destination. Two class-level settings decide what happens to that text, and what happens when an agent sends a message without naming a thread. They are the least obvious part of the configuration, and the most important to get right.
default_thread | final_message | What it means | |
|---|---|---|---|
| Specialist | origin | default_thread | It serves one conversation — the one that asked. Whatever it says at the end of its turn is automatically posted back there. It needs no send commands and no thread IDs in its prompt. |
| Coordinator | none | discard | It serves many conversations. It gets no default destination, and nothing it writes is posted on its behalf. Every message it sends must name its thread explicitly. |
default_thread = origin means "the conversation this instance was created to serve." It
is set once, when the instance is created, and never moves when the agent joins other
conversations later — it records who the agent owes an answer to, not where it last heard
something. That distinction matters as soon as agents delegate: if agent B is woken by
agent C on their shared thread, B's answer still belongs to A, who asked in the first
place.
For a coordinator, both defaults would be actively wrong. It is regularly woken by a
specialist while owing a report to the user, so it takes none (no fallback destination,
forcing every message to be explicit) and discard (its own thinking is not published
anywhere). A useful consequence: a turn in which the coordinator writes nothing at all is
free and invisible. Two of its four wakes are exactly that — a specialist answered, the
panel is not complete yet, so there is nothing to say.
Why not simply wait for the answer?
The synchronous option exists. agyn threads send --thread X --message "..." --wait 120
blocks until a reply arrives, and it does so efficiently — it subscribes to a
notification stream rather than polling. For a shell script or a CI job driving an agent,
that is the right tool.
Inside an agent, it costs more than it looks. Suppose each specialist takes 40–90 seconds to answer:
- Blocking, one at a time. The coordinator's workload stays up for the sum of all three, holding a shell open and a context window full of dead time. One slow specialist stalls the whole panel.
- Blocking, in parallel. You can background three calls and join them, but now the
prompt has to teach a language model shell job control, and you still hold the workload
until the slowest reply. The timeout is a hard ceiling too: past
--wait 120the call fails even though the answer did arrive and is sitting on the thread. - Asynchronous. Three sends, turn ends, workload shuts down. Each reply is a short wake with one new message in it. Elapsed time is the slowest specialist rather than the sum, billed compute is the work rather than the waiting, and an interrupted run replays instead of losing the answer.
The honest summary: the gain is not primarily speed, since blocking calls can be parallelized too. The gain is that no compute is pinned to a wait, there is no timeout ceiling to lose answers behind, delivery survives restarts, and the prompt stays a paragraph instead of a script.
Use synchronous calls when the caller truly cannot continue without the answer and the answer is seconds away, or when a script rather than an agent is driving. Use asynchronous messaging for anything that fans out, anything that waits on a human, and anything that runs longer than a workload should stay up.
Next in this series
That is the model. Part three provisions it — a model, an environment, three specialists and one coordinator, from either the Console or Terraform.
Newsletter
Get new agent engineering posts in your inbox
Occasional practical notes on secure agent runtimes, orchestration, and AI engineering.