I’ve been experimenting with getting Codex and Claude Code to actually collaborate on the same coding task.
The obvious approach is something like:
Claude output → Codex prompt
Codex output → Claude prompt
Basically, capture one agent’s output and inject it into the other.
I tried thinking about the problem this way at first, but it quickly gets messy.
What if the other agent is busy?
What if its current turn has already finished?
How do you resume the right session?
What happens to messages while the agent is doing something else?
Eventually I ended up with a different design in CleanCode:
Agent identity + mailbox + native wake-up.
1. CleanCode owns the agent identity
Instead of exposing Claude sessions or Codex threads directly to the collaboration layer, CleanCode gives each agent its own identity.
Each provider then maps that identity to its native runtime:
CleanCode Agent A
↓
Claude Provider
↓
Claude session
CleanCode Agent B
↓
Codex Provider
↓
Codex thread
So the collaboration layer doesn’t really care whether the target is Claude Code or Codex.
Conceptually, it just needs to do things like:
send(to: agentB)
wake(agentB)
The provider handles the runtime-specific details.
2. Messages go into a mailbox
This was probably the most important design decision.
When Claude wants to send something to Codex, I don’t inject the actual message directly into Codex’s current prompt.
Instead, it goes into Codex’s collaboration inbox.
Claude
│
│ message
▼
Codex Inbox
That separates communication from execution.
Codex can be working, idle, or between turns. The message is still sitting there waiting for it.
The same thing works in the opposite direction.
3. Wake the agent using its native mechanism
Now there’s another problem:
How does Codex know that something arrived?
I didn’t want CleanCode constantly polling the inbox or pretending to be the agent runtime.
Instead, each provider uses the agent’s own native lifecycle to wake it up.
For Claude Code, I use its FileChanged hook and async re-wake behavior.
For Codex, I use its native message/thread mechanism.
So underneath, they are quite different:
CleanCode
│
wake(agent)
│
┌──────────┴──────────┐
│ │
Claude Provider Codex Provider
│ │
FileChanged hook native message
+ async re-wake + thread
│ │
▼ ▼
Claude Codex
But to the collaboration layer, both simply implement:
“Wake this agent up.”
4. The wake-up message does NOT contain the actual peer message
This is a small detail that ended up mattering a lot.
I deliberately keep the wake-up channel separate from the actual communication channel.
When Claude sends something to Codex, the actual message stays in Codex’s inbox.
The native wake-up mechanism only sends a fixed notification:
CleanCode: check your collaboration inbox.
Codex wakes up, checks its inbox, reads Claude’s message, does the work, and can reply through Claude’s inbox.
So a full round trip looks roughly like this:
Claude
│
│ 1. send message
▼
Codex Inbox
│
│ 2. native wake-up
▼
Codex
│
│ 3. read inbox
▼
Do the work
│
│ 4. reply
▼
Claude Inbox
│
│ 5. native wake-up
▼
Claude
The mental model I ended up liking is:
The inbox is the data plane.
The native wake-up mechanism is the control plane.
One carries the actual agent-to-agent communication.
The other just says:
Hey, there’s something waiting for you.
What I learned
The biggest lesson for me was that multi-agent collaboration doesn’t necessarily require building another agent runtime on top of existing agents.
Claude Code already has a lifecycle.
Codex already has a lifecycle.
They have different ideas of sessions, threads, wake-ups, and what happens while an agent is busy.
Rather than hiding all of that by piping stdout into stdin, I found it cleaner to preserve their native behavior and put a thin collaboration layer above them:
Provider abstraction
+
Persistent mailbox
+
Native wake-up
=
Agent-to-agent collaboration
It also makes adding another coding agent much less invasive.
A new provider mainly needs to answer:
What is your session identity?
How do I find/resume that session?
How do I wake you up?
Can notifications queue while you're busy?
The mailbox and the higher-level collaboration protocol don’t need to know much about the underlying agent.
I implemented this as part of CleanCode:
Still experimenting with the model, but I’d be interested to hear how others are approaching agent-to-agent communication — especially whether you prefer mailboxes/events like this or direct message injection.
Top comments (0)