DEV Community

Cover image for How I got Codex and Claude Code to collaborate using their native wake-up mechanisms
Justin
Justin

Posted on

How I got Codex and Claude Code to collaborate using their native wake-up mechanisms

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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?
Enter fullscreen mode Exit fullscreen mode

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:

GitHub – 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)