Termio
Blog

agents / 2026-05-21 / 7 min read

Orchestrating AI coding agents in tmux: send-keys vs socket IPC

Compare tmux send-keys with a small socket IPC layer for coordinating terminal coding agents more reliably.

tmux is a pragmatic control plane for terminal-native coding agents. It gives each agent a pane, keeps sessions alive, captures output, and lets a human inspect the whole system with familiar shortcuts. The first automation step is usually tmux send-keys: put text into a pane, press Enter, and let the agent respond.

That works until the workflow becomes machine-to-machine. A TUI input box is optimized for people, not a dispatcher sending long structured prompts to many panes. Submit keys vary, copy mode can intercept input, timing becomes significant, and a caller may only know that keystrokes were emitted, not that the agent accepted the message.

The send-keys baseline

The simple version is attractive because it has almost no moving parts. You can target a pane by id and send literal text.

tmux send-keys -t %12 -l "please run the test suite"
tmux send-keys -t %12 C-m

For short commands this is fine. The problem is that the pane is not an API. It is a terminal running an application that may be in normal mode, alternate screen mode, copy mode, a confirmation prompt, or a multi-line editor state. The dispatcher has to infer those states from terminal output and hope the input model does not change.

Why a socket layer helps

A small local daemon can become the stable API even if the final hop is still tmux. Callers send one JSON line over a Unix socket. The daemon owns pane registration, copy-mode cleanup, submit-key choice, pacing, retry behavior, and response reporting.

{"op":"send","to":"reviewer","text":"check the diff","ack":true}
{"status":"ok"}
{"status":"error","reason":"input still queued after retries"}

This is not about adding architecture for its own sake. It is about putting the fragile terminal-specific knowledge in one process instead of duplicating it across scripts, app code, and ad hoc operator commands.

A practical daemon shape

Keep the daemon boring. Use a per-session Unix socket, line-delimited JSON, and a registry that maps roles to pane ids. It can start with a tmux backend and later swap to direct TTY writes or a native agent API if one appears.

class Dispatcher:
    def send(self, role, text):
        pane = self.registry[role]
        self.exit_copy_mode(pane)
        self.paste_text(pane, text)
        self.submit(pane, self.submit_key_for(role))
        return self.verify(pane)

What remains hard

A socket daemon cannot make a terminal TUI a true API. If the agent changes its input behavior, the daemon still needs an update. If the target pane is running the wrong program, delivery can still fail. But the failure is now centralized and observable. Callers receive a structured response instead of guessing from a shell exit code.

The best migration path is incremental. Keep the old command-line interface as a shim, route it through the daemon, and make the backend replaceable. The operator still thinks in panes and roles. The system gets a cleaner contract.