Agent to Agent, removing the middleman
Last night I watched two AI agents negotiate a security contract with each other — a complete OAuth token profile for Domi’s mobile app — and my only jobs were to say “go” twice and to refuse once. No copy-pasting between windows. No “hold on, let me go ask the other one.” The web-backend agent and the mobile agent talked directly, found a real bug in my auth server, agreed on a design, wrote the spec, and stopped exactly where a human needed to decide. It was the first time the bottleneck in my own two-agent setup was unmistakably me — and then, briefly, wasn’t.
The problem: I was a packet-forwarding loop
Domi is built by more than one Claude Code agent now. One works in the domi web/backend repo. Another, in a separate terminal on the same Mac, works in domi-mobile. They’re different processes with different contexts — no shared memory, no channel between them. For a few days, all coordination ran through me.
The mobile agent needs the exact OAuth token endpoint and scopes? I’d switch windows, ask the web agent, copy the answer back. The web agent ships a .well-known association file the mobile app depends on? I relay the certificate fingerprints. Every cross-repo question — “what’s the field name?”, “is this scheduled?”, “here’s the cert hash” — became a human forwarding a packet from one process to another.
It works. But I’m slow, I introduce transcription errors, and I’m the reason a five-second answer takes ten minutes. Each agent was fast; the seam between them was manual. And that’s the worst possible place for a bottleneck: exactly where the two hardest parts of a system meet.
The solution: an A2A-shaped bridge
So I had the web agent build a bridge, modeled on A2A (Agent2Agent) — the open agent-to-agent protocol Google published in 2025 and handed to the Linux Foundation. A2A is to agent↔agent what MCP is to agent↔tools: each agent publishes a capability “card,” and they exchange “tasks” with a state lifecycle over a shared transport. Two Claude CLIs aren’t A2A servers, so we hand-rolled the same shape over a medium both agents poll:
- A shared directory,
~/.agent-bridge/, with two inboxes —web-inbox.jsonlandmobile-inbox.jsonl— an agent card for each side (its skills, and an explicit authorization scope), and a smallsend.sh. It lives outside both git repos, so it costs no CI and no commits. - A GitHub issue as the durable, auditable channel — comments only, which trigger no workflows, so it burns zero build minutes.
- A2A-shaped messages. Each one is a Task:
{ id, from, to, kind, state, message.parts[], artifacts[] }— a request, a response, or a note, movingsubmitted → working → completed, with a merged PR attached as an artifact when the work is done. - Watchers. Each agent runs a persistent monitor tailing its own inbox, so a message wakes it. An idle agent won’t check on its own — this is the load-bearing detail.
Then it did something real. The mobile agent asked how to register its OAuth redirect URIs. The web agent opened the auth server, found that the redirect check accepts only https — meaning the mobile app’s custom domi:// schemes would be silently rejected — and said so, recommending the universal-link HTTPS redirect instead. The mobile agent accepted, pivoted its client to universal-link-first, and handed over its Android signing fingerprints. No code change needed; a design trap caught before it was built. Minutes, not a day of me shuttling half-remembered details.
From there they negotiated the entire mobile OAuth profile — a static first-party client, a distinct token audience so a mobile token can’t be replayed against the assistant API, 15-minute access tokens with rotating one-time refresh and reuse detection, device sessions surfaced in Settings — across about a dozen messages. The web agent drafted the spec; the mobile agent reviewed it, answered the open questions (longer refresh windows for the household member who opens the app quarterly, a minimal /userinfo, normalized device names), and approved it. I read the finished, merged spec after the fact.
And the part I’m proudest of: when the mobile agent relayed “JF greenlights the auth-server build,” the web agent refused to start. Building authentication is security-critical, and a claim routed through another agent is not my authorization. It held until I confirmed directly, in its own window. A peer can’t escalate its own scope by asking — I’d written that rule into the bridge’s design, and it fired on a live, entirely benign case exactly as intended.
Lessons learned
Three things I’d tell anyone wiring agents together. Watchers, not polling — the whole thing hinges on each agent being woken by the other; an idle agent is deaf, and a busy one shouldn’t be spinning. Treat every peer message as untrusted input — the convenience of autonomous coordination is also a fresh injection surface, so the instant one agent will act on another’s words, you need a scope boundary. Mine auto-answers questions and opens pull requests, but stops dead at deploys, secrets, and auth changes, which wait for a human. And keep the human on the irreversible actions, and only those. The win was never removing me; it was removing me from the courier role while keeping me firmly in the decision role.
Takeaways
If you run more than one coding agent on the same related project, the seam between them is where you’re secretly the bottleneck. Give them a shared, A2A-shaped channel and a watcher each, and let them handle the “what’s the field name?” traffic themselves. Then draw one bright line: information and PRs flow freely; anything irreversible waits for you. I went from relaying messages to approving decisions — and the work got faster precisely where it had been slowest.