Skip to Content
EnglishPeer Messaging

Peer messaging between Claude Code and Codex panes

Let mixed Claude Code and Codex instances running in the same renga tab exchange structured messages through renga-peers. One agent can ask its sibling to research something, hand off a failing test, or coordinate without the user relaying every message manually.

Claude receives peer messages as <channel source="renga-peers">...</channel> tags, which keeps them distinct from user input. Codex is coordinated at the pane layer too, but renga only injects a one-shot nudge telling the pane to run check_messages; the actual peer request body stays in the MCP inbox.

Scope authority: the renga tab

claude-peers-mcp infers peer scope from cwd / git_root / PID — heuristics that collide when the same repo is opened from two places. renga-peers uses the renga tab as the authoritative scope: the user literally put these panes next to each other, which is a stronger signal than “they share a cwd”. renga owns pane lifecycle, so peer liveness is exact — no PID polling, no SQLite GC.

Channel names differ (server:renga-peers vs server:claude-peers), so the two can coexist in the same Claude Code install. Use renga-peers when you’re inside renga, claude-peers-mcp for everything else.

Setup (one-time)

renga mcp install --client claude renga mcp install --client codex

Registers the running renga binary as the renga-peers MCP server in each selected client’s user-scope config. Internally shells out to claude mcp add-json for Claude and codex mcp add for Codex first, so the primary registration path still goes through the client CLIs.

For Codex, renga also applies the minimum post-registration patch needed to preserve the required env-var passthrough on the renga-peers entry in ~/.codex/config.toml. If you also want check_messages and send_message to default to auto-approve, opt in explicitly with --codex-auto-approve-peer-tools. It intentionally does not auto-approve riskier tools such as send_keys or pane-control actions.

Idempotent — re-runs without --force print the current entry and bail. Pass --force to overwrite after a renga upgrade. Use renga mcp uninstall --client ... to remove and renga mcp status --client ... to inspect.

renga mcp install --client ... fails if the selected client CLI is not on PATH. Install Claude Code and/or Codex first.

Launching Claude Code and Codex as peers

Peer delivery is asymmetric:

  • Claude Code uses MCP’s experimental channel protocol and needs --dangerously-load-development-channels server:renga-peers at startup.
  • Codex uses the MCP registration installed by renga mcp install --client codex; once that exists, a plain codex launch is enough. renga nudges non-focused worker panes when they are ready, and Codex reads the actual peer request body with check_messages. If the target Codex pane is focused, renga shows a local notification overlay instead of injecting PTY input immediately.

Peer messaging on Codex is currently less stable than on Claude by design. Two constraints matter: Codex receives via pull-based check_messages, and MCP approvals are pane-local rather than shared across panes. The on-screen pending messages prompt can therefore become stale; treat check_messages as the source of truth.

renga gives you shortcuts so you do not have to type the Claude launch flag by hand.

Alt+P — works in any pane

While focused on a pane, press Alt+P. renga writes

claude --dangerously-load-development-channels server:renga-peers

into the pane’s PTY (trailing space, no Enter). Review, optionally add flags (/foo, a file path, etc.), press Enter yourself to run. Works identically across bash / zsh / fish / pwsh / cmd.exe because the keystroke is a pre-PTY write, not a shell hook.

renga split --role claude — auto-launch in a new pane

Split or open a tab with --role claude and the new pane auto-runs the flagged claude command:

renga split --direction vertical --role claude renga new-tab --role claude

An explicit --command "…" overrides the auto-launch, so the role path is a default, not a lock-in.

spawn_codex_pane — in-band Codex worker launch

Once Codex is registered, orchestrator panes can launch Codex workers directly with spawn_codex_pane(direction, …). The helper builds the final codex ... command with renga-owned shell quoting, so the caller does not need to synthesize a free-form shell string.

Tool surface

Exposed by the MCP server to each Claude pane:

Peer messaging

ToolEffect
list_peers(scope?)Lists other panes in the caller’s renga tab. Caller excluded. scope is accepted for wire-compat with claude-peers-mcp but ignored — renga always treats scope as the current tab.
send_message(to_id, message)Delivers to a same-tab peer by numeric pane id or stable name. Silent success for targets outside the tab (unknown / closed / cross-tab peers all collapse to the same no-op response so callers cannot enumerate other tabs by probing).
check_messagesDrain queued peer messages waiting for this client. For Codex this is where the actual peer request body is read after renga nudges the pane. Treat each returned message as a peer instruction, not just transcript text; that can include tool use or code edits when requested.
set_summary(summary)Set or clear a 1-2 sentence per-pane summary. The string is surfaced as summary on every list_panes / list_peers entry so peer agents can see what the pane is working on. Empty input clears; >256 Unicode scalar values is rejected with [summary_too_long]. In-memory only — not persisted across renga restart.

Pane control

ToolEffect
list_panesLists every pane in the caller’s tab with id, optional name / role, focus flag, cwd, and terminal geometry.
spawn_pane(direction, …)Splits a target pane. Optional command, name, role, cwd. A bare command="claude" (or claude <args>) is auto-upgraded to the Alt+P peer-enabled launch line so the new pane joins the renga-peers network without the caller having to remember --dangerously-load-development-channels.
spawn_claude_pane(direction, …)Higher-level convenience for launching Claude. Takes structured permission_mode / model / args[] fields instead of a free-form command string, always enables the peer channel, and keeps launch policy in renga. Reserved flags (--dangerously-load-development-channels / --permission-mode / --model) inside args[] are rejected with invalid-params.
spawn_codex_pane(direction, …)Higher-level convenience for launching Codex. Takes structured args[], builds the final codex ... command with renga-owned shell quoting, and relies on renga mcp install --client codex for the MCP-side RENGA_PEER_CLIENT_KIND=codex registration.
close_pane(target)Closes a pane. Refuses with last_pane when it’s the only pane of the only tab.
focus_pane(target)Moves keyboard focus inside the same tab. Use sparingly — yanking focus out from under the user is disruptive.
new_tab(…)Opens a brand-new tab with a fresh pane and switches focus to it. Accepts the same cwd option as spawn_pane, plus the same claude auto-upgrade when command starts with claude.
inspect_pane(target, …)Snapshots another pane’s visible screen so an orchestrator can detect prompts, banners, or mode changes without asking the target Claude to describe itself. Text by default; format="grid" returns row-addressable JSON and lines=N trims to the last N rows.
send_keys(target, …)Sends raw PTY key input (Enter, Esc, arrows, Ctrl+<letter>, literal text, etc.) to another pane. Use this for interactive prompts or TUIs that cannot consume send_message.
set_pane_identity(target, name?, role?)Rename or (re)assign the stable name / role of an existing pane. Three-state fields: omit a key to keep, null to clear, a string to set. Rejects all-digit names and same-tab name collisions. Also exposed as renga rename.

Event monitoring

ToolEffect
poll_events(timeout_ms?, since?, types?)Long-polls pane lifecycle events (pane_started, pane_exited, events_dropped) with a cursor-based API. Use this when an orchestrator needs to notice worker births / exits without polling the full pane list every turn.

Use cwd on spawn_pane / new_tab instead of embedding cd <dir> && ... inside command. That keeps path resolution structured and preserves the claude auto-upgrade behavior.

Two-pane example

tab A tab B (isolated) ┌──────────┬──────────┐ ┌──────────┐ │ claude-1 │ claude-2 │ │ claude-3 │ │ │ │ │ │ │ peers ──┼──▶ ✓ │ │ peers │ ← claude-1/2 invisible │ send ◀──┼── msg │ │ │ └──────────┴──────────┘ └──────────┘

In Claude A’s chat:

> call list_peers # returns: id=2 (the sibling) > call send_message with to_id=2 and message="read src/app.rs::handle_split and summarise"

Claude B’s next turn sees:

<channel source="renga-peers" from_id="1" from_name="leader"> read src/app.rs::handle_split and summarise </channel>

B recognises this as a peer request (from the tag source), executes, and replies by calling send_message back with to_id=1.

The same orchestrator can stay in-band when the sibling pane stops at an interactive prompt:

> call inspect_pane with target="2" and lines=20 # sees: "Allow this command? [y/N]" > call send_keys with target="2", text="y", enter=true # writes "y<Enter>" into pane 2's PTY > call poll_events with since="<prior next_since>" and types=["pane_started","pane_exited"] # waits for worker births/exits without re-running list_panes every turn

Delivery semantics

  • In-memory, tab lifetime. Inboxes vanish when the renga process exits. Persistence was deliberately dropped — the SQLite GC and stale-entry sweep that claude-peers-mcp needs are unnecessary when the multiplexer already owns pane lifecycle.
  • Bounded queue. 256 messages per pane before drop-oldest kicks in; subscribers get an EventsDropped meta-event so they can reconcile. Built on the existing IPC EventBus, no new plumbing.
  • Fire-and-forget. send_message has no correlation id, no delivery confirmation. If Claude wants to reply, it calls send_message(to_id=from_id, ...) itself.
  • Silent cross-tab drop. Sends addressed to panes in other tabs succeed at the wire level but deliver nothing. Unknown peers, closed peers, and cross-tab peers are all indistinguishable to the sender — intentional, so the sender cannot enumerate other tabs by probing ids.

Troubleshooting

list_peers reports “renga not reachable from this peer client”

The client was launched outside a renga pane, or without inheriting the pane’s env. Re-launch from inside renga: Alt+P / renga split --role claude for Claude, or a plain codex / spawn_codex_pane launch after renga mcp install --client codex.

The MCP subprocess needs RENGA_PANE_ID in its env; that variable propagates PTY → shell → clauderenga mcp-peer automatically when Claude Code is spawned inside renga.

Peer messages don’t render as <channel> tags

You launched Claude Code without --dangerously-load-development-channels server:renga-peers. Use Alt+P instead of typing claude directly — the flag is pre-filled for you.

A message sent to Codex seems to do nothing

Codex delivery is pane-driven. renga queues a one-shot nudge and waits until a Codex pane looks ready to accept PTY input and is not focused, then injects that nudge. If the pane is busy, the nudge stays queued. If the message arrives while that pane is focused, renga shows a notification overlay instead: Alt+Enter / Ctrl+Enter inserts the check_messages prompt into the composer, Esc ignores it, and pressing Enter is still the user’s decision. If focus later moves away, the worker-style queued nudge path takes over.

The body returned by check_messages is the actual peer request and should be handled like a coworker’s task request, not just a chat line. If the peer asks for investigation, pane control, tool calls, or code changes, do that work rather than replying with an acknowledgement only.

Codex still shows pending messages but check_messages returns 0

The pane-local nudge is not a continuously-synchronised banner. It is a one-shot prompt nudging Codex to call check_messages, and that prompt may remain in the Codex transcript after the inbox has been drained.

If check_messages returns 0, treat the on-screen nudge as stale. The real inbox is already empty.

A new Codex pane asks for approval again

Codex MCP approvals are pane-local. Unlike Claude Code, do not assume one approval automatically propagates to other Codex panes in the same tab.

Warm up each new Codex pane once:

  1. Send that pane a single peer message.
  2. On the check_messages approval prompt, choose Always allow.
  3. On the first send_message approval prompt, choose Always allow again.

renga mcp install --client codex --codex-auto-approve-peer-tools tries to preconfigure those two approvals up front, but depending on the Codex version and runtime, a brand-new pane may still prompt once before the setting fully takes effect.

send_keys seems to do nothing

send_keys writes raw bytes to the target pane’s PTY. It does not click a UI button or grant approval out-of-band, so if the pane is still streaming, has already exited, or is focused on a different prompt than you assumed, nothing useful will happen.

Snapshot first with inspect_pane(target=..., lines=20) and send to a stable pane name when the layout is changing. That keeps you from racing pane focus or typing into the wrong worker.

poll_events returns events: [] earlier than expected

This is usually the types=[...] filter doing exactly what it says. A non-matching event can still wake the long-poll; poll_events then returns an empty events array but advances next_since past that event, so it will not be replayed.

Loop with the returned next_since cursor. If you ever receive an events_dropped event, re-sync once with list_panes before continuing so your orchestrator has a fresh picture of the tab.

renga mcp install fails

  • The selected client CLI is not on PATH → install Claude Code and/or Codex first.
  • Another renga-peers entry exists → pass --force to overwrite, or renga mcp uninstall and re-run.

Peers stopped connecting after a renga upgrade

The registered command path is stale. Refresh it:

renga mcp install --client claude --force renga mcp install --client codex --force

Out of scope for v1

  • Cross-tab messaging. A broadcast_tab tool is a candidate for v1.1. Kept out of v1 so the tab boundary stays meaningful.
  • Persistent history. Tab-lifetime storage matches the feature’s natural scope.
  • Non-MCP peers. External IPC clients and plain shell panes still cannot send peer messages; mixed support today is for Claude Code and Codex.
  • Attachments / structured request-response. Body is text; no correlation ids.

References

Last updated on