Skip to content

feat(tmux): add TMUX agent-cockpit skill — CMUX parity on Linux and any SSH host - #1658

Open
elhoim wants to merge 1 commit into
danielmiessler:mainfrom
elhoim:feat/tmux-cockpit-skill
Open

feat(tmux): add TMUX agent-cockpit skill — CMUX parity on Linux and any SSH host#1658
elhoim wants to merge 1 commit into
danielmiessler:mainfrom
elhoim:feat/tmux-cockpit-skill

Conversation

@elhoim

@elhoim elhoim commented Jul 26, 2026

Copy link
Copy Markdown

What this is

CMUX gives LifeOS a scriptable agent cockpit, and it is macOS-only. Its own SKILL.md says where the gap is: "No Linux/WSL — that path is tmux." That path was never built. This is it — same subcommand surface, same recipes, different substrate, so the two skills read alike and muscle memory transfers.

Parity with CMUX

13 of 16 capabilities full or better, 2 degraded, 1 absent. The gaps are stated in SKILL.md rather than quietly omitted:

  • Degraded: flash and per-team colour identity become display-message + terminal bell + border styling instead of GUI chrome, and none of it reaches a detached client. monitor's voice notification through Pulse is the attention mechanism that actually works headless.
  • Absent: the in-app browser pane. tmux has no such object; not closeable.

Three capabilities tmux adds that CMUX cannot do at all: sessions survive disconnection, #{pane_dead_status} gives real exit codes so a crashed agent is distinguishable from a finished one, and -F templates return structured topology instead of the ~30 lines of regex in cmux.ts that try to recover object refs from human-readable stdout.

Gotchas, all measured

Several of these were found because they broke a first attempt. They are in SKILL.md because the next person will hit them too.

Behaviour Consequence
A newline executes the text — send-keys -l and all four paste-buffer variants ran the payload The control is the payload, not the transport. The wrapper refuses multi-line text unless --enter was passed. Nothing here claims paste-buffer is safe.
-t resolves exact → fnmatch → unambiguous prefix kill-session -t production destroys production-work, exit 0, no warning. Teardown is ID-only, with a server-generation token so a stale ID cannot retarget after a restart.
remain-on-exit is a window option; the session-scoped form silently no-ops Without it set with -w, an exited pane is destroyed and the exit status goes with it — a crashed agent leaves no evidence.
capture-pane -S -N is a scrollback offset, not a line count Asking for 10 on a 24-row pane returns 34. Uses -S - and takes the last N.
select-layout tiled ignores the requested grid 1x5 and 5x1 both produce 3×2. Grids are built by explicit row/column splits and the geometry is verified.
Naive successive splits halve exponentially no space for new pane around 7 panes. Splits the lead each time and re-applies main-vertical.
pane_current_command resolves one level bash run.sh reports bash, so "back to a shell" is a false finish signal. monitor layers signals and labels the weak one low-confidence in its JSON.
Global -g hooks/options reach sessions you never created And set-hook -gu cleanup strips the operator's own. Everything is session-scoped.

Verification

A 25-case parity suite against a real tmux 3.6 server, on a private -L socket. 25 passed, 0 failed, including the cases that break naive implementations:

✓ 4 tiers -> 4 panes                    ✓ 7 tiers -> 7 panes, no space error
✓ role titles applied                   ✓ send --enter then read sees output
✓ no-enter does not execute             ✓ multi-line send refused, for the right reason
✓ race --agents 4 -> 4 panes            ✓ race-N titles applied
✓ 2x2 -> 2 rows x 2 cols                ✓ 1x5 -> 1 row x 5 cols (tiled fails this)
✓ 2 hosts -> 2 panes                    ✓ monitor labels signal confidence
✓ exited pane retained, status=7        ✓ list parses as structured JSON
✓ kill refuses a bare name / prefix     ✓ neighbouring session survived
✓ kill by session ID works              ✓ killed session is gone

The refusal assertions check the reason, not just ok:false — an earlier version of the suite passed those cases because the command had errored for an unrelated reason, which is exactly the false green this skill is meant to prevent.

Tools/Tmux.ts type-checks clean under bun build --target bun. Public-clean grep for identity, home paths and hostnames returns zero matches; remote hosts come from USER/CUSTOMIZATIONS/SKILLS/TMUX/fleet.json.

Scope note

This does not replace the built-in split-pane teammates. teammateMode (in-process | auto | tmux | iterm2) already gives visible teammate panes when the leader is running inside tmux, and those teammates inherit the lead's permission mode with prompts bubbling up for approval. This skill is for what that cannot serve: a leader with no attached terminal, work that must outlive the connection, non-Claude processes, and TUIs with no API. SKILL.md states plainly that an agent started by typing claude ... into a pane is a sibling process outside the session's permission envelope.

…ny SSH host

CMUX is a macOS GUI app and its own SKILL.md says the Linux path is tmux.
That path was never built. This is it: same subcommand surface, same
recipes, different substrate.

Parity: 13 of 16 CMUX capabilities full or better, 2 degraded, 1 absent.
Degraded are flash and per-team colour identity, which become
display-message plus bell plus border styling rather than GUI chrome.
Absent is the in-app browser pane, which tmux has no object for. All three
are stated in SKILL.md rather than quietly omitted.

Three things tmux does that CMUX cannot: sessions survive disconnection,
pane_dead_status gives real exit codes so a crash is distinguishable from a
finish, and -F templates return structured topology instead of the regex
scraping cmux.ts needs to recover refs from stdout.

Every gotcha in SKILL.md was measured on tmux 3.6, several of them after
they broke a first attempt:
- newlines execute regardless of send mechanism (send-keys and all four
  paste-buffer variants), so the wrapper validates the payload rather than
  trusting the transport
- -t prefix-matches, so kill-session -t production destroys
  production-work; teardown is ID-only with a server-generation token
- remain-on-exit is a window option and the session-scoped form silently
  no-ops, taking pane_dead and the exit status with it
- capture-pane -S -N is a scrollback offset, not a line count
- select-layout tiled ignores the requested grid (1x5 and 5x1 both give 3x2)
- naive successive splits fail with 'no space' around 7 panes

Verified by a 25-case parity suite against a real server, including the
anti-tests: multi-line send refused for the right reason, teardown refusing
a prefix name while the neighbouring session survives, and a dead pane
retaining exit status 7.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant