Skip to content

Repository files navigation

tmux-agents

CI Release

tma is a self-hosted companion for the coding agents you run in tmux. It finds them in your panes, shows which are blocked, working, or idle, takes you to the one that needs you, and answers the prompt it is stopped on. The same commands cover six agents, so a fleet split across Claude Code, Codex and OpenCode reads as one list rather than three. State lives in tmux's own pane options, so any tmux show-options or #{@agent_state} format string reads it directly and tma ls --json gives a stable structured feed. There is no socket, no required background process, and nothing between you and your agents but your own machine. Why tma is the short case for that design.

What it does

See the fleet. tma opens a picker over every agent pane on the server, tma ls prints the same rows for a script, and #(tma status) renders a one-line summary in your status bar. [daemon] window_names renames each window after the agents in it, so a blocked agent shows up in the window list of a session you are not looking at. Notifications fire on blocked and on completions, and [notify] stall fires once when a pane has been working longer than you expected it to.

Know what stopped it. tma transcript reads the agent's own transcript file and normalizes it, so a claude pane and a codex pane answer in the same vocabulary. Four of the six stores are served (claude, codex, gemini, pi), and none of those agents was asked to cooperate: the files were already on disk.

Get to it. tma jump --blocked moves an attached client to the pane. tma attach --pane %5 is the way in from a terminal that is not a tmux client yet, a fresh ssh session or a phone; inside tmux it is exactly tma jump.

Answer it. tma act approve and deny resolve a permission prompt, tma act interrupt stops a turn, and tma act steer --text "use the existing helper" sends the agent a line of your own. On a claude pane the answer goes back through the permission hook itself, which holds the call open for a decision (the hook reply lane, on by default), so the tool runs with no keystroke landing anywhere. tma act --slot <id> makes a dispatch run at most once and tma receipts reads the outcome back, which is how a caller whose connection dropped learns what happened instead of approving a second time to find out.

Build on it. tma wait blocks a script until a pane changes, tma subscribe streams those changes, and the @agent_state contract is the spec a second tool needs to write the same options without clobbering tma.

What it will not do

Every action is gated on what the pane actually shows, and the gate is re-asserted inside the pane's action lock rather than trusted from the read that prompted it. approve and deny fire only at detail = permission, so a dialog that asks you to pick an option instead of granting a request offers no approve key at all; a pane that moved on between the read and the dispatch refuses rather than landing your answer on the prompt that replaced it. Notifications take you to a pane. They never answer anything for you.

tma also does not compete with a vendor's own remote control of that vendor's own agent. It covers the case none of those cover: the mixed fleet you actually run, on a machine you own, answered from wherever you are.

Supported agents

Six agents ship with detection out of the box. tma reads state from three kinds of evidence: hook events the agent fires, its on-screen chrome, and the process in the pane. The table below is the shipped coverage; the full, evidence-backed record is in docs/reference/agent-coverage.md.

agent hook-covered states blocked signal screen rules identity
Claude Code working / idle / blocked / lifecycle hook + screen working, idle, blocked process claude
Codex CLI working / idle / blocked / lifecycle hook + screen working, blocked process codex
OpenCode working / idle / blocked (registers on start; no end hook) hook + screen blocked process opencode
Gemini CLI working / idle / blocked / lifecycle hook + screen working, blocked process node, narrowed by title
Cursor CLI working / idle / lifecycle screen only working, blocked process node/agent, title Cursor Agent
pi working / idle / lifecycle none (pi auto-approves tools) working process node/pi, title π …

blocked is hook-covered for four agents and rides a screen rule for Cursor (which exposes no permission hook). pi has no blocked state at all: it runs tools without a permission prompt, so there is nothing to detect. Agents that run under a generic process name (node) are disambiguated by their pane title.

Adding an agent tma does not ship is one TOML manifest, no code: identity, screen rules, and a hook map in a file dropped in ~/.config/tma/agents/. See add a custom agent and the manifest schema.

Quickstart

You need tmux 3.6 or newer (that is what tma is developed and tested against; tma doctor warns on anything older).

Install the binary. From the first public release onward, one command does it:

curl -fsSL https://raw.githubusercontent.com/pperanich/tmux-agents/main/scripts/install.sh | sh

That fetches the prebuilt binary for your platform (macOS on Apple Silicon or Intel, Linux on x86_64 or aarch64), checks it against the release's SHA256SUMS, and installs it to ~/.local/bin. Pass TMA_VERSION to pin a tag and TMA_INSTALL_DIR to install somewhere else. With a Rust toolchain you can build from a checkout instead:

cargo install --path crates/tma

The Nix flake and the Home Manager module are the other two supported paths; see install tma.

Then let the setup wizard do the wiring: it finds the agents you actually have installed, wires each one's hooks, installs the keybindings, prints the status-line entry to add, and finishes with a tma doctor report. Every write shows you a diff first.

tma init

Prefer to do it a piece at a time? Wire one agent so its state is reported the instant it changes (Claude Code here):

tma install-hooks claude

Either way, run the picker to see every agent pane and jump to one:

tma

For ambient state in your status line, add the driver to your tmux config (~/.tmux.conf or ~/.config/tmux/tmux.conf):

set -g status-right '#(tma status) %H:%M'

#(tma status) is not just cosmetic: without a daemon it is also what keeps pane state fresh. The getting-started tutorial walks the whole loop end to end, and status line and keybindings covers the picker, the temporary watch session, and the jump bindings.

Configuration

tma reads an optional config.toml (--config <path>, then TMA_CONFIG, then $XDG_CONFIG_HOME/tma/config.toml, then ~/.config/tma/config.toml). Every setting has a working default, so zero-config works; unknown keys are a loud parse error rather than a silent typo. A minimal example:

[status]                     # `tma status` glyphs + colors
blocked = { glyph = "", color = "red" }

[notify]                     # notifications
on = ["blocked", "done"]     # fire on blocked, and on working->idle completions

The full key reference is in docs/reference/configuration.md.

Documentation

The docs are a Diátaxis tree, also buildable as an mdBook site (mdbook build from the repo root renders docs/ into book/):

Contributing

CONTRIBUTING.md has the toolchain, the mise tasks, the crate layout, and the architecture invariants a change has to hold. CHANGELOG.md records what each release changed, breaking changes first.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work shall be dual licensed as above, without any additional terms or conditions.

About

Agent-state monitor for tmux: see, jump to, and script your AI coding agents

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages