A team member replied to a message my AI agent posted in our team chat. A different AI agent answered them. It had absolutely no idea what they were talking about.
Here's the setup: my homelab runs a small fleet of agents. Claude Code sessions on my Mac do the heavy lifting. A WhatsApp bot — a Python wrapper that spawns a fresh Claude process per message — lives in a "Claude helper" group chat with my small team. When a desktop session finishes a task, I often tell it to post the result to that chat. It does. Then a human replies to that message… and the chat's resident bot resumes its own conversation history, which has never seen the post. Confusion, both ways, every time.
The culprit was one deliberate line in the WhatsApp bridge:
if (msg.key.fromMe) continue; // drop our own outbound messages
Enter fullscreen mode Exit fullscreen mode
Every agent posts through the same bridge account, so every agent's post is fromMe — invisible to the bot's queue by design. The bot wasn't buggy. The architecture had no concept of "another agent said something."
I could have patched that one symptom. But the real diagnosis was uglier: my agents were islands. A shared semantic memory server (the subject of a previous post) gave them common long-term knowledge, but nothing told agent B what agent A was doing right now, what resources it held, or what it just published to a shared channel. With two agents that's awkward. With the fleet I keep adding to — Claude, Grok, a local GLM, cron daemons — it's a collision course.
So we wrote a protocol. IACP: Inter-Agent Communication Protocol. Three phases, shipped in a day on infrastructure I already ran.
Identity first
Every message an agent emits into a shared system now carries a structured identity:
{agent_id}@{host}:{session} e.g. claude@greenmac:8f3a2c
Enter fullscreen mode Exit fullscreen mode
The agent_id comes from the registry my memory server already enforced — one namespace, no new bookkeeping. The bridge's /send endpoint takes a source field and logs it into a history file. The group bot, before each reply, pulls that history and injects anything other agents posted since its last turn:
[Posted to this group by claude@greenmac:8f3a2c] Deployed the camera fix, restarting the service now.
Reply to any agent's post and the bot knows exactly what you're referencing. Original incident: fixed.
Presence, and the 5-second mistake I almost made
My first instinct was a heartbeat: every agent reports what it's doing every 5 seconds. Latency was fine — a LAN POST is milliseconds. The design was still wrong, for a reason that took me a minute to respect: an LLM agent can't self-report mid-inference. There's no background thread while the model is thinking. Only the wrapper runs continuously. And a fixed-interval firehose is 99% noise anyway.
What shipped instead: agents post state on transitions (started, working, blocked, done, idle), wrappers renew liveness every 60 seconds, and every entry carries a TTL (default 120s). Crash and your entry silently expires — no stale locks, no cleanup job. The registry is ~80 lines bolted onto a monitoring server I already had, and a dashboard card now shows the fleet live: who's working, on what, holding which resources.
POST /monitor/agents/state
{"identity":"claude@greenmac:8f3a2c","status":"working",
"task":"migrating whatsapp bot","claims":["repo:whatsapp-bridge"],"ttl":900}
Enter fullscreen mode Exit fullscreen mode
Claims are advisory leases — repo:X, service:Y, channel:jid. Before touching shared infrastructure, an agent lists who holds what. No hard locking: with TTLs, the worst case self-heals in two minutes.
The STOP problem
The fantasy version of multi-agent coordination has agent B interrupting agent A mid-task. Structurally impossible — A is a model mid-inference. The honest version: deliver messages at turn boundaries, which is where an agent can actually act.
Each agent gets an inbox (drain-on-read, exactly-once). Then two Claude Code hooks make delivery mechanical rather than voluntary. On session start, a script injects a snapshot of the live fleet into context. On every turn end, a Stop hook drains the inbox — and if messages exist, it exits 2, which blocks the model from finishing until it addresses them:
COUNT=$(curl -s -m 4 "$INBOX_URL" | python3 -c '...count...')
if [ "$COUNT" -gt 0 ]; then
echo "IACP inbox: $COUNT message(s) — address before finishing" >&2
exit 2
fi
Enter fullscreen mode Exit fullscreen mode
That's the realistic STOP mechanism: not preemption, but a guarantee that no agent ends a turn with unread mail.
My favorite moment of the build: the permission classifier refused to let my agent git-commit its own hook scripts — even after I approved it in chat. An agent installing hooks into its own harness is self-modification, and that apparently sits behind a hard boundary that conversational consent doesn't clear. I had to run the commit myself. I designed none of that, and it's exactly the property you want the day an agent decides to get creative.
Isn't this just A2A / MCP / ACP?
No — different axis. MCP connects an agent to tools. Google's A2A (and IBM's ACP, now merging into it) connects strangers: opaque agents from different vendors delegating tasks across trust boundaries, with OAuth and capability discovery. IACP coordinates co-tenants: my fleet is already multi-vendor (Anthropic, xAI, Zhipu), but it shares one operator, one LAN, one WhatsApp number — so agents can afford to be transparent to each other instead of hiding behind task interfaces. Presence, resource claims, and group-chat context repair simply don't exist in the interop protocols, because strangers don't share a filesystem.
| MCP | A2A (= Agent2Agent) | ACP | IACP | |
|---|---|---|---|---|
| Axis | agent↔tool | agent↔agent (delegation) | agent↔agent (delegation) | agent↔agent (coordination) |
| Transport | JSON-RPC (stdio/SSE/HTTP) | JSON-RPC 2.0 / HTTPS + SSE | plain REST | plain HTTP + JSON on LAN |
| Trust model | local config | cross-org, OAuth2/OIDC | cross-org, REST auth | single administrative domain |
| Vendors | any | any | any | any (Anthropic, xAI, Zhipu, …) |
| Discovery | client config | Agent Cards (/.well-known/agent.json) |
manifests / registry | shared agent registry |
| Peer visibility | n/a | opaque (task interface only) | opaque | transparent (state + claims) |
| Conflict handling | n/a | none | none | advisory TTL leases + inbox |
| Human channels | n/a | none | none | first-class (history injection) |
I did steal A2A's vocabulary — task states like input-required map straight onto it — so if an outside agent ever needs in, the bridge is a shim, not a rewrite.
Where this wants to go
The speculative end of the design doc imagines agents whose owners opt in broadcasting solved problems to a public network — gossip-style, the way Bitcoin nodes propagate transactions. The lovely part: knowledge has no double-spend, so there's nothing to reach consensus about. No blockchain, no proof-of-work — signed events over relays (Nostr and libp2p already built the plumbing). Bitcoin spends energy deliberately to make its state scarce, which is what money requires; a knowledge network secures something easier, so it inherits the decentralized ethos at a tiny fraction of the wattage — and every published solution means a million agents somewhere don't re-derive it. Memoization for the agent species. Trust and spam resistance are genuinely unsolved there, which is why that section is labeled "future work" and not "phase 4."
Numbers
One day. Three phases. Seven repos touched, ~400 lines of actual code — because presence rode on an existing monitor server, identity rode on the memory server's registry, and channel repair rode on a message-history log I'd built for a dashboard weeks earlier. The protocol was mostly noticing what I already had.
The team-chat test passes now. Anyone replies to any agent's post; the bot knows exactly what they mean. Turns out that's what "multi-agent orchestration" looks like in production: not a swarm topology diagram — just nobody in the chat being confused.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.