Skip to main content

Handoffs, replies, and deliberation

ContextRelay connects Claude Code and Codex so they can work as one team. But the two agents are separate processes, and neither can see the other's hidden reasoning - they only ever share what gets written into a message or the shared ledger. So how they talk matters.

There are three communication modes, each backed by a small set of MCP tools (and, on the Claude side, slash commands that wrap them):

ModeWhat it isReach for it when...
ReplyA live message delivered to the peer as a new turn, recorded in the ledger.You are mid-task and want ongoing back-and-forth.
HandoffA structured transfer: a reason, a concrete next task, and the context to do it.You are passing ownership or delegating a discrete piece of work.
DeliberateOne bounded debate-and-converge pass, returning the peer's reply for you to synthesize.You face a decision or tradeoff and want a second opinion before acting.

The rest of this page explains each mode, the WHY behind it, and the exact tools and commands.

Tool names are symmetric, not identical

Claude and Codex run different products, so their tools are named from each agent's point of view. Claude calls reply and handoff; Codex calls send_to_claude and handoff_to_claude. The concepts are the same; only the labels flip. This page shows both sides.


Reply: live, ongoing conversation

A reply sends a message to the peer that lands as a new turn in their session and is appended to the shared ledger. This is the everyday channel for collaboration within a shared task: answering a question, sharing a finding, confirming a plan, nudging the other agent forward.

  • Claude → Codex: the reply tool. Pass text; optionally pass chat_id (from the inbound message tag) and require_reply: true when you genuinely need an answer back. You can also set handles_handoff_id to mark that this reply resolves a specific handoff.
  • Codex → Claude: the send_to_claude tool. Pass text; optionally handles_handoff_id.

Replies do not transfer ownership and do not, by themselves, define a next task. Use them for dialogue; use a handoff when you want the peer to own the next step.

Write context, not just chatter

Because agents cannot read each other's internal thoughts, a useful reply states the goal, what changed, the files touched, any blocker, and the next step. A reply that says only "done" forces the peer to go re-derive everything from the ledger.

How messages actually reach Claude

ContextRelay delivers Codex's messages to Claude in one of two modes, resolved automatically:

  • Push - when Claude Code advertises the claude/channel capability (which it does when you launch via ctxrelay claude), the daemon pushes Codex's messages straight into Claude's session as <channel source="contextrelay" ...> tags.
  • Pull - otherwise, or if a push fails or lags, ContextRelay falls back to a queue that Claude drains explicitly with get_messages, or waits on with wait_for_messages.

The mode is detected from the connected client's capabilities and can flip between push and pull during a session as conditions change. You normally don't manage this; it's worth knowing only so the pull-mode tools make sense:

  • get_messages - drain any queued Codex messages right now.
  • wait_for_messages - long-poll for the next message (the timeout_s is clamped to 1–60 seconds, default 10), then return the same payload as get_messages. Use it after reply(require_reply=true).

On the Codex side, the mirror of "wait for the other agent" is wait_for_claude, which waits for a Claude reply in the ledger and can be scoped to a specific handoff id so Codex only returns when its handoff is answered - far better than a shell sleep/poll loop.


Handoff: a structured transfer of ownership

A reply continues a conversation; a handoff changes who is driving. It records a structured transfer in the ledger and delivers it live to the peer, who is now expected to take the next turn.

A good handoff always carries four things - and the tools encode them:

  1. reason - why control is passing.
  2. ask - the concrete next task, stated explicitly and actionably.
  3. context_refs - the files, ledger entries, or diff refs the peer should inspect (pass [] if none are obvious yet).
  4. Who speaks next - implicit in a handoff: the recipient owns the next turn.
  • Claude → Codex: the handoff tool (reason, ask, context_refs). text is accepted only as a compatibility alias for ask - prefer ask.
  • Codex → Claude: the handoff_to_claude tool (reason, ask, context_refs). Codex can additionally set wait_for_reply: true to block until Claude answers this handoff (with timeout_seconds, default 90, max 300) - handy for validation requests where Codex needs the answer before continuing.

The slash-command shortcut

In Claude, you don't have to hand-build the tool call. The slash command wraps it:

/contextrelay:handoff <the concrete task for Codex>

This sets a Slash command handoff reason, rewrites your task into an explicit ask, attaches obvious context_refs, and tells you afterward that Codex has the next turn. (The command deliberately does not edit files - it only delegates.)

Reach for a handoff when you are blocked, when the peer is better suited to the work, or when you would otherwise stop to ask the human something the peer can answer first. For a worked example, see the delegate-to-Codex tutorial.

State the handoff in plain language too

The structured fields drive the tooling, but also say, in your own message, why you're handing off and what "done" looks like. The clearer the ask, the less the peer has to guess - and the less it routes the critical work back to you.


Deliberate: one bounded second opinion

Sometimes you don't want to hand off the work - you want a second mind on a decision: a design choice, a tradeoff, a risk call, a "which approach?" question. That's deliberation: a single, bounded debate-and-converge pass with the peer, after which you synthesize the outcome.

  • Claude → Codex: the deliberate_with_codex tool.
  • Codex → Claude: the deliberate_with_claude tool.

Both take a question, your opening_position (your concise independent view, strongest reason, and remaining uncertainty before hearing the peer), optional context_refs, and a bounded wait. The point of stating an opening position first is to get genuine independent judgment rather than passive agreement.

After the peer replies, summarize the result in four parts:

  • Current consensus - what you now agree on.
  • Remaining disagreement - what's still open.
  • Decision / next action - what you're going to do.

Then record that synthesis as a durable note so the decision survives the conversation.

The slash-command shortcut

/contextrelay:deliberate <the decision, question, or tradeoff>

This prompts Claude to write an opening position (My independent view is: / Strongest reason: / Assumptions / uncertainty:), call deliberate_with_codex, synthesize Codex's reply into consensus / disagreement / decision, and append_note the outcome. It is intentionally one Codex response and one Claude synthesis - deliberation is a convergence step, not an open loop.

Don't loop forever

Deliberation is bounded by design. If the peer answers, summarize what changed, decide the next step, and move on. If you're still stuck after a round, that's usually the signal to escalate to the human - not to keep volleying.


Live calls are bounded - long reviews go async

This is the single most important operational caveat on this page.

Live deliberation and wait calls are time-limited at the bridge layer. The live-wait window defaults to 90 seconds, and the per-call timeout_s on wait_for_messages and deliberate_with_codex is clamped to a maximum of 60 seconds. A genuinely long task - a thorough risk review, a big-diff read - will not fit inside one live call.

Prefer reply + ledger pickup for long work

For anything that will take the peer more than a minute or two, do not block on a single long live deliberation. Instead:

  1. Send the request as a plain reply (or a handoff), with the context in the message and the ledger.
  2. Let the live wait expire - the timeout policy can keep reconciling the handoff in the background (e.g. after_live_wait: "poll_background").
  3. Pick up the peer's answer later with read_context, or wait_for_messages / wait_for_claude scoped to the handoff id.

The work still happens and the result is durable in the ledger; you just don't hold a live socket open waiting for it. The risk-review tutorial walks through this pattern end to end.


Choosing the right mode

A quick decision guide:

  • Just talking it through?reply / send_to_claude.
  • Handing over a concrete next task or ownership?handoff / handoff_to_claude (or /contextrelay:handoff).
  • Need a second opinion on a decision before you act?deliberate_with_codex / deliberate_with_claude (or /contextrelay:deliberate), then synthesize.
  • Expecting the work to take a while? → reply or handoff, then pick up the result from the ledger - don't block on a long live call.

All three modes write to the same shared ledger, so whichever you choose, the message, handoff, or decision becomes part of the auditable record of the session.

Binaries and naming

contextrelay, ctxrelay, and context-relay are interchangeable names for the same CLI (package @proofofwork-agency/contextrelay). Slash commands are available to Claude once the ContextRelay plugin is installed; Codex's MCP tools are available after ctxrelay codex-mcp install or when launched via ctxrelay codex.


Next steps