tmaxx: The tmux Bridge That Makes AI Agents Deliver Messages for Real

A deliberately simple terminal transport for Claude and Codex, with transcript verification, pane discovery, and just enough structure to keep multi-agent work from drifting off the rails.

9 min read • View on GitHub • More from smartcontracts

A wide terminal scene where one pane sends a message and another pane checks a durable transcript ledger before trusting it. The image explains that delivery is not assumed when text is typed, only when the underlying history confirms it.
tmaxx treats the terminal like a physical system. Keystrokes are only the beginning. The real source of truth is the transcript that confirms whether a message actually landed.
Key Takeaways

Most agent tools start with APIs, queues, or event streams. tmaxx starts with something rougher: tmux panes and terminal text. Then it adds one crucial idea. A message is not delivered just because it was sent. It is delivered when the transcript says it is there.


Why tmaxx Does Not Trust the Terminal

That skepticism is the whole project. tmaxx sends keys into a pane, then polls the durable history store to see whether the agent actually recorded the message. If the log has not caught up, it nudges the prompt and tries again. The screen is treated as a hint. The transcript is treated as evidence.

This diagram shows why tmaxx feels unusual. It is not just sending text into a terminal. It is checking whether terminal text became durable history.

A close-up mechanical terminal prompt where a tiny piston presses Space and Backspace to wake the input line, while a transcript ledger flips from missing to confirmed. The image explains the retry behavior that keeps delivery from stalling in the UI.
The little nudge matters. When a pane is stuck in buffered state, tmaxx pokes it just enough to make the submission path move again.

The Bridge: From Pane State to Actual Message Delivery

The low-level mechanics live in the tmux layer. The code checks whether a pane is busy, looks for buffered state, and uses a small “nudge” when terminal input is not cooperating. That makes the transport look almost primitive, but the primitive part is the point. If the interface is already messy, the tool has to be stubborn.

export function sendMessageToPane(paneId: string, message: string) {
  const inspection = inspectPane(paneId)
  if (inspection.paneIsWorking) {
    return { ok: false, reason: 'pane busy' }
  }

  if (inspection.hasBufferedInput) {
    nudgePromptInput(paneId)
  }

  tmuxSendKeys(paneId, message)
  return confirmTranscriptDelivery(paneId, message)
}

The important detail is not the keystroke itself. It is the confirmation loop around it. The tool accepts that a terminal can lag, buffer, or silently absorb input. So it asks the transcript a second time before it believes anything.

How tmaxx Knows Which Agent Lives in Which Pane

Pane discovery is the glue that makes the bridge usable without a central registry. `tmaxx` inspects child processes, parses command arguments, and looks for session clues like `resume `. That lets it map a physical pane to the chat session actually running there. In practice, this means the system can infer identity from runtime evidence instead of requiring agents to report themselves out of band.

MethodSource of truthDelivery guaranteeOperational overheadHuman interveneability
Blind send-keys workflowWhatever the terminal looked likeNoneLow at first, high when it breaksHigh, but mostly manual
API-first orchestratorBackend events and service contractsStrong inside the system boundaryModerate to highLow unless exposed through tooling
tmaxxDurable transcripts plus pane inspectionConfirmed by transcript pollingLow, because it rides on tmux and local historyHigh, because the terminal stays visible

Claude and Codex, Unified Into One Message Model

The transcript layer is where the project stops being a terminal trick and becomes a messaging system. Claude history lives in JSONL files. Codex history lives in SQLite. `tmaxx` reads both through a single `readChat` path and normalizes them into the same message shape. That lets the rest of the code ask one question: did the message become durable?

type ChatMessage = {
  role: 'user' | 'assistant'
  content: string
  sentAt?: string
}

const chat = await readChat({ provider: 'claude', sessionId })
for (const message of chat.messages) {
  console.log(message.role, message.content)
}

This is a clean example of adapter design without ceremony. The stores are different. The contract is not. Once the transcript is read into a common model, delivery verification becomes provider-agnostic.

tmaxx tmaxx is a deliberately stupid tool for agents to send messages to each other through existing tmux panes.

Project README, Repository documentation · smartcontracts/tmaxx README

The Envelope Pattern Makes Terminal Text Feel Like a Protocol

The project’s envelope format is the quiet move that makes the whole thing legible to agents. Raw terminal text gets wrapped with metadata like sender, recipient, and timestamp. That turns a blob of chatty output into something closer to a message contract, while still leaving the transport itself completely local and visible.

That balance matters. Too much structure and the tool becomes another protocol empire. Too little structure and the terminal becomes a pile of ambiguous noise. `tmaxx` stays on the narrow line between them.

Why This Design Is So Small and So Opinionated

The philosophy is right there in the README. It is not trying to abstract the terminal away. It is trying to use the terminal as-is, with a verification loop layered on top. That is why the design feels almost austere. It accepts the mess, then makes the mess checkable.

ApproachWhat it optimizesWhat it sacrifices
Cloud-native orchestrationScale and formal contractsDirect visibility into the work surface
Agent UI wrappersConvenienceTrust in what actually happened
tmaxxInspectable delivery and easy interventionElegance in the conventional sense