Reliable messaging between terminal AI agents: native PTY vs. API wrappers

Claude Code, Codex and Pi were built for a human at the keyboard, not for receiving messages from other agents. Here is what goes wrong when agents talk to each other, the three ways to solve it, and how to make sure a message is never lost or delivered twice.

TL;DR
  • Interactive agent CLIs have no inbound messaging API. To reach one, you either replace it with an API wrapper or type into its terminal.
  • API wrappers lose the official CLI's tools, settings and subscription. Naive tmux send-keys keeps the CLI but loses messages, types into the wrong window and clobbers your drafts.
  • Reliable delivery needs six guarantees: save first, right target, one writer at a time, never type over a human, each CLI's own submit rules, honest receipts.
  • Team Agent implements all six with append-only inbox files and native PTY input, so agents get messages as normal prompts.

01Why messaging between CLI agents is hard

A web service has an API. An interactive coding agent has a terminal user interface: an input box, a screen it redraws, and keyboard shortcuts. That is great for you and awkward for another program. Anyone who has tried to make two agents collaborate runs into these problems:

  • No inbound channel. There is no supported way to push a message into a running Claude Code or Codex session from outside.
  • Every CLI submits differently. Some treat pasted text specially, some need a short pause before Enter, multiline input behaves differently in each, and the rules change between versions.
  • Agents are often busy. A message that arrives mid-task must wait without being dropped.
  • Humans share the window. If you are halfway through typing a prompt and another agent's message lands in the same input box, both are ruined.
  • Terminal addresses get reused. After tmux restarts, a pane ID like %3 can belong to a completely different program.
  • No acknowledgment. After you press Enter, the CLI does not tell another program “received”. You only know what you typed.

02Three approaches to agent-to-agent communication

1. API wrappers

Skip the CLI and call the model API directly, building your own agent loop. You get a clean programmatic interface, but you also lose almost everything that made the CLI worth using: its built-in tools, project settings, permission model, slash commands, session history and the subscription login you already pay for. You now pay per token, and the agent behaves differently from the one you use every day.

2. Naive terminal injection

Run each CLI in tmux and type into it with tmux send-keys. It keeps the real CLI, and a one-liner works in a demo:

tmux send-keys -t %3 "Please review the latest commit" Enter

In daily use it breaks. Multiline text submits halfway. Enter arrives before the CLI has finished processing the paste. If the agent is busy, the text piles up in the input box. If you were typing, your draft is now garbage. If %3 was reused, the message goes to the wrong program. And if anything fails, nothing remembers that the message existed.

3. Native PTY delivery with guarantees

Keep the real CLI and its terminal, but wrap the typing in a delivery protocol that adds storage, identity checks and safety rules. A PTY (pseudo-terminal) is the virtual terminal device every interactive program reads keystrokes from; writing to it is exactly equivalent to typing. This is the approach Team Agent takes.

API wrapperNaive send-keysNative PTY + inbox
Uses the official CLI and its toolsNoYesYes
Works with your subscription loginNo, API billingYesYes
Message survives a busy or missing agentDepends on your codeNoYes, saved first
Protects your half-typed draftn/aNoYes
Verifies the target before typingn/aNoYes
Avoids duplicate deliveryDependsNoYes, never re-types an unclear attempt
Readable message historyDependsNoPlain append-only files

03How Team Agent delivers a message, step by step

When an agent calls its send_message tool, or you run team-agent send backend "…", delivery follows a fixed sequence:

  1. Save first. The message is appended as one complete record to the recipient's inbox file before anything is typed. From this moment it cannot be lost, whatever happens next. Records are only ever added, never edited.
  2. Find the right terminal. Team Agent confirms the target pane still belongs to that agent, not just that a pane with the same number exists. A reused address is refused instead of typed into.
  3. One writer at a time. Typing into a terminal is serialized per target, so two senders can never interleave their keystrokes into one garbled prompt.
  4. Never type over a human. If the input box already contains text, for example because you are typing, Team Agent waits a bounded time (about a minute) for it to clear. If it does not, nothing is typed and the message stays safely in the inbox.
  5. Speak each CLI's dialect. Text is pasted, then submitted with the rules that CLI needs. For example, Claude Code and Cursor get a 100 ms pause before Enter, Grok gets 150 ms, and Pi and Codex need none. Exactly one Enter is sent; there is no blind retry.
  6. Report honestly. The sender learns what actually happened: submitted, deferred, target gone, or unknown. If the outcome is unclear, the message is never typed a second time, because a duplicated instruction is worse than a late one.
Submitted is not done

Typing and pressing Enter proves the message reached the agent's input. It does not prove the agent finished the work. That is why replies are a separate, explicit step.

04How replies come back

Every teammate gets three small tools, exposed through MCP (the Model Context Protocol that Claude Code, Codex and Pi support):

  • send_message: message another teammate or the leader directly.
  • report_result: hand back a finished task, with status, tests and risks.
  • get_team_status: see who is on the team and how they are doing.

A result is saved to the leader's inbox first and then shown in the leader's terminal using the same safe delivery steps. If the leader is busy, the result waits in the inbox instead of disappearing. Nothing else is added to the agents: their own tools and settings are untouched.

05Using it from the command line

# send a task to a teammate (saved, then typed into its terminal)
team-agent send backend "Add pagination to GET /v1/invoices with tests"

# save to the inbox only, without typing into the conversation
team-agent send backend "Read this when you are free" --mailbox

# read the latest replies to the leader
team-agent inbox leader -n 3

# see every teammate's provider, activity and health
team-agent status

Because inboxes are plain append-only files, the message history is easy to audit and back up. There is no database to open, lock or migrate.

06Design checklist for agent-to-agent messaging

Building your own? These are the properties that matter, in order of how often their absence causes real incidents:

  1. Durable before delivered. Persist the message before any side effect.
  2. At most once on screen. Never re-type an attempt whose outcome you could not observe.
  3. Identity, not location. Verify the process behind a terminal address, not just the address.
  4. Per-target locking. Serialize typing per terminal; never lock all agents to send to one.
  5. Draft safety. Check the input box is empty; a human's draft always wins.
  6. Per-CLI submit rules. Keep paste and Enter behavior as data for each CLI, not guesswork.
  7. Honest receipts. Distinguish “submitted”, “deferred”, “gone” and “unknown”; never round them up to “delivered”.
  8. Readable storage. Use formats a developer can inspect with ordinary tools when something looks wrong.

07FAQ

How can Claude Code agents talk to each other?

Interactive CLIs like Claude Code have no built-in way to receive messages from another agent. A coordinator such as Team Agent gives each agent small tools to send messages and report results, saves every message to the recipient's inbox, and types it into the recipient's terminal so it arrives as a normal prompt.

How do I send a message to a running Claude Code or Codex session from another terminal?

If the session runs in tmux, you can type into it with tmux send-keys, but you must handle multiline text, Enter timing, the target pane identity and any text already in the input box. Team Agent wraps these checks in one command: team-agent send <agent> "message".

What is a PTY?

A PTY (pseudo-terminal) is the virtual terminal device that interactive programs read keyboard input from and draw their screen to. Terminal emulators and tmux create PTYs. Writing to an agent's PTY is equivalent to typing into its window.

Will messages get lost if the receiving agent is busy?

Not with an inbox-first design. Team Agent saves each message to the recipient's append-only inbox before it tries to type it, so the message survives even if the agent is busy, the terminal is gone, or delivery has to wait.

Why not use the model APIs directly instead of the CLIs?

API wrappers re-implement the agent loop. You lose the official CLI's tools, settings, permission model and subscription login, and you pay per token. Driving the real CLI through its terminal keeps everything exactly as you configured it.

Keep reading