A thin, opinionated CLI for managing tmux sessions that run AI coding agents
Project description
tmux-pilot
A thin, opinionated CLI for managing tmux sessions, task worktrees, and PR metadata for AI coding agents (Claude Code, Codex, etc).
Why? When you run multiple AI coding agents in parallel — each in its own tmux session and often each in its own git worktree — you need a way to bootstrap task branches, list active sessions, peek at output, send follow-up instructions, refresh PR state, and clean up once the branch lands. tmux-pilot wraps the fiddly tmux and git conventions into a single tp command designed for both humans and AI orchestrators.
Install
# pip
pip install tmux-pilot
# pipx (isolated install)
pipx install tmux-pilot
# uv
uv tool install tmux-pilot
Requirements: Python 3.10+, tmux. Optional: fzf (for tp jump picker).
Quick Start
# Start Codex in an existing checkout.
# tmux-pilot runs: codex --profile yolo
tp new auth-flow --profile codex -c ~/repos/myapp
# Start Claude Code in an existing checkout.
# tmux-pilot runs: claude --permission-mode bypassPermissions
tp new review-pass --profile claude -c ~/repos/myapp
# Start Pi in an existing checkout.
# tmux-pilot runs: pi --offline --no-extensions --no-skills --no-prompt-templates --no-themes --session-dir ~/repos/pi-mono/.tmux-pilot/pi/sessions
tp new pi-local --profile pi -c ~/repos/pi-mono
# Bootstrap a task branch + worktree from a local repo, then launch Codex there.
# Default branch: feat/oauth-fix
# Default worktree: ~/worktrees/myapp-oauth-fix
tp new oauth-fix --profile codex --repo ~/repos/myapp -d "Fix OAuth callback handling"
# Bootstrap from GitHub if the repo is not cloned locally yet.
# The repo is cloned to ~/repos/pi-mono first, then a worktree is created.
tp new pi-smoke --profile pi --repo badlogic/pi-mono
# Check on all your sessions
tp ls
# Refresh PR/review metadata, then show a compact dashboard
tp refresh --repo myapp
tp ls --cols NAME,PR,STATUS,DIR
# Inspect the transcript trace bound to a session
tp trace auth-flow
# Turn PR state into the next follow-up prompt
tp prod --dry-run --repo myapp
# Peek at output without attaching
tp peek auth-flow -n 30
# Send a follow-up instruction
tp send auth-flow "now add tests for the auth module"
# Get detailed status
tp status auth-flow
# Done — tear it down
tp kill auth-flow
Docs
The published documentation site is intended to live at https://cmungall.github.io/tmux-pilot/.
Detailed documentation also lives under docs/ and is organized using Diataxis:
- overview:
docs/overview.md - tutorial:
docs/tutorials/drive-a-kept-alive-agent-session.md - how-to:
docs/how-to/create-sessions.md - how-to:
docs/how-to/wait-for-interactive-agents.md - how-to:
docs/how-to/start-task-sessions-with-profiles-and-worktrees.md - explanation:
docs/explanation/file-backed-agent-state.md - reference:
docs/reference/agent-state.md - reference:
docs/reference/session-creation.md
Commands
tp ls — List sessions
tp ls # table view
tp ls --json # JSON output (for AI orchestrators)
tp ls --status active # filter by @status metadata
tp ls --repo myapp # filter by repo (substring match)
tp ls --process claude-code # filter by detected process
tp ls --all-metadata # append known metadata columns
tp ls --cols NAME,PR,DIR # compact PR dashboard
tp ls --json --status active # combine filters with JSON
tp ls --all-metadata also exposes cached trace fields such as TRACE_AGENT and TRACE_PATH.
PR is a compact summary column. It starts with the PR number, then appends short review/merge codes when available:
M: mergedX: closedA: approvedCR: changes requestedRR: review requiredP: pending review stateD: dirty/conflictedB: blockedC: clean
Examples: 1548 RR D, 1553 CR, 1547 M.
tp new — Create a session
tp new NAME # bare session
tp new NAME -c ~/repos/myapp # set working directory + @repo
tp new -c ~/repos/myapp # infer session name from the directory
tp new NAME --here # use cwd and infer repo/branch/worktree metadata
tp new --here # infer the session name from cwd/worktree
tp new NAME --here -j # create, then auto-jump into the session
tp new NAME -d "description" # set @desc metadata
tp new NAME -c DIR -d DESC # both
# Launch a built-in agent profile in-place
tp new NAME --profile codex -c ~/repos/myapp
# Bootstrap a task branch + worktree from a repo, then launch the profile
tp new NAME --profile claude --repo ~/repos/myapp
# `--repo` accepts a local path, GitHub owner/repo, or GitHub URL
tp new NAME --profile pi --repo badlogic/pi-mono
tp new NAME --profile pi --repo https://github.com/badlogic/pi-mono.git
# Override branch/base selection when needed
tp new NAME --profile codex --repo ~/repos/myapp --branch chore/name-cleanup
tp new NAME --profile codex --repo ~/repos/myapp --base-ref origin/release/1.2
Concrete profile examples:
# Launches `codex --profile yolo` in ~/repos/myapp
tp new auth-pass --profile codex -c ~/repos/myapp
# Launches `claude --permission-mode bypassPermissions` in ~/repos/myapp
tp new review-pass --profile claude -c ~/repos/myapp
# Launches `pi --offline --no-extensions --no-skills --no-prompt-templates --no-themes --session-dir ~/repos/pi-mono/.tmux-pilot/pi/sessions`
tp new pi-local --profile pi -c ~/repos/pi-mono
When --repo is used, tp new now handles the full task bootstrap flow:
- resolves or clones the repo
- derives a task branch from the session name (or
--issue) - creates a git worktree under the configured worktree base
- starts the requested agent inside that worktree
Bootstrap worktrees are named <repo>-<session> by default. If NAME already starts with <repo>-, tp reuses NAME as the worktree leaf directory instead of doubling the repo prefix.
Concrete bootstrap examples:
# Creates branch `feat/oauth-fix`, worktree `~/worktrees/myapp-oauth-fix`,
# then launches `codex --profile yolo` inside that worktree.
tp new oauth-fix --profile codex --repo ~/repos/myapp
# Creates branch `fix/771-issue-771`, fetches the issue title for @desc,
# then launches `claude --permission-mode bypassPermissions`.
tp new issue-771 --profile claude --repo ~/repos/myapp --issue 771
# If ~/repos/pi-mono does not exist yet, clone it first.
# Then create branch `feat/pi-smoke`, worktree `~/worktrees/pi-mono-pi-smoke`,
# and launch Pi with a worktree-local session dir.
tp new pi-smoke --profile pi --repo badlogic/pi-mono
# Pin the branch name or starting point when needed.
tp new cleanup --profile codex --repo ~/repos/myapp --branch chore/cleanup
tp new backport --profile codex --repo ~/repos/myapp --base-ref origin/release/1.2
Built-in launch profiles:
codex:codex --profile yoloclaude:claude --permission-mode bypassPermissionspi:pi --offline --no-extensions --no-skills --no-prompt-templates --no-themes --session-dir {worktree}/.tmux-pilot/pi/sessions
Recommended profile config lives at ~/.config/tmux-pilot/profiles.toml:
[default]
extends = "codex"
worktree_base = "~/worktrees"
clone_base = "~/repos"
[profiles.pi]
extends = "pi"
branch_prefix = "task"
[profiles.myapp]
extends = "codex"
repo = "~/repos/myapp"
branch_prefix = "feat"
base_ref = "origin/main"
[prod]
[[prod.rules]]
name = "changes-requested"
match = { pr_review = "CHANGES_REQUESTED", pr_state = "OPEN" }
prompt = "Address all requested review comments on {pr_display}. Re-check each thread, update tests, and push the fixes."
[[prod.rules]]
name = "merge-blocked"
match = { pr_state = "OPEN", pr_merge_state = ["BLOCKED", "DIRTY"] }
prompt = "Your PR {pr_display} is not mergeable. Resolve the conflicts or other merge blockers, then push an update."
extends can target another configured profile or one of the built-in profiles above. Config values override the inherited profile, so you can keep reusable agent defaults separate from repo-specific task defaults.
For interactive Codex sessions, codex --profile yolo --no-alt-screen plus tp send --wait is the current best-supported flow. Brand-new repos and worktrees can still stop at a Codex trust prompt before normal readiness begins. tp now verifies the tmux pane cwd before and immediately after agent launch and fails loudly if the shell or agent drifts out of the requested directory.
--here is plain-mode only. It uses your current working directory as the session directory, records inferred git metadata such as repo root, current branch, and whether the checkout is a linked worktree, and can infer the session name from that directory when you omit NAME. If that inferred name already exists, tp new auto-suffixes it as -1, -2, and so on. -j/--jump attaches or switches to the new session immediately after creation.
Concrete config-driven examples:
# Explicit in-place launch using the built-in Codex profile.
tp new rename-types --profile codex -c ~/repos/myapp
# Uses the repo/base branch from `[profiles.myapp]`,
# so `--repo ~/repos/myapp` is not needed here.
tp new api-cleanup --profile myapp
# Uses the customized Pi profile, so the derived branch is `task/pi-smoke`
# instead of the default `feat/pi-smoke`.
tp new pi-smoke --profile pi --repo badlogic/pi-mono
tp peek — View scrollback without attaching
tp peek NAME # last 50 lines (default)
tp peek NAME -n 100 # last 100 lines
tp send — Inject text into a session
tp send NAME "any command" # sends text + Enter
tp send --wait NAME "follow-up instruction"
tp send NAME "claude-code --print 'fix the auth bug'"
tp prod — Send configured follow-up prompts
tp prod # all sessions, auto-refresh first
tp prod dragon-assign # one named session
tp prod --repo dismech # repo-scoped subset
tp prod --dry-run --repo myapp # preview prompts without sending
tp prod --json # machine-readable plan
tp prod --wait dragon-assign # opt into wait-until-ready before sending
tp prod reads [prod] rules from ~/.config/tmux-pilot/profiles.toml, refreshes PR metadata by default, picks the first matching rule for each targeted session, renders its prompt template, and sends it via plain tp send semantics. Use --no-refresh to rely on cached metadata instead. Pass --wait only when you explicitly want to wait for readiness before sending.
tp jump — Attach or switch to a session
tp jump NAME # attach (or switch if inside tmux)
tp jump # fzf picker (requires fzf)
tp status — Detailed session info
Shows process, PID, working directory, all metadata, relative freshness for cached metadata, and the last 5 lines of scrollback.
tp status NAME
PR-related metadata is shown with refresh ages when available, for example:
@pr = 1548 (updated 2m ago)
@pr_review = REVIEW_REQUIRED (updated 2m ago)
@pr_merge_state = DIRTY (updated 2m ago)
@last_refresh = 2026-04-19T22:39:42.658Z
When a transcript has been resolved, tp status also shows the cached @trace_agent and @trace_path metadata for that session.
tp trace — Inspect the bound transcript trace
Use this when a tmux pane cwd is not the whole story and you want the actual transcript binding that tp will use for agent state.
tp trace auth-flow
tp trace auth-flow --refresh
tp trace auth-flow --json
tp trace auth-flow --show raw --lines 10
tp trace auth-flow --show json
tp trace auth-flow --show yaml
tp trace auth-flow --show tsv
tp trace auth-flow --show formatted
tp trace auth-flow --show yaml --color always
tp trace prefers cached session metadata (@trace_agent, @trace_path) and falls back to a cwd-based scan when needed. This makes it practical for one tp session to stay associated with one chat/session trace even after later checks no longer rely purely on pane_current_path.
Use --json for machine-readable trace metadata. Use --show raw|json|yaml|tsv|formatted when you want the transcript content itself, whether the underlying file is a Codex, Claude Code, or Pi JSONL trace. tsv emits normalized rows for scripts, while formatted renders a readable timeline. --color auto|always|never controls ANSI coloring for the human-readable render modes.
tp refresh — Refresh PR metadata without reaping
Use this when you want a review dashboard or fresh PR metadata without any destructive cleanup.
tp refresh # all sessions
tp refresh docs-pass # one named session
tp refresh --repo myapp # repo-scoped subset
tp refresh --json # machine-readable output
tp refresh updates @pr, @pr_state, @pr_review, @pr_merge_state, and @last_refresh in tmux metadata. It does not kill sessions, remove worktrees, or delete branches.
tp prod builds directly on those cached fields, so the common orchestration loop is:
tp refresh --repo myapp
tp ls --cols NAME,PR,STATUS,DIR --repo myapp
tp prod --dry-run --repo myapp
tp prod --repo myapp
tp set / tp get — Session metadata
Metadata is stored as tmux user options (@-prefixed). Common built-in keys include repo, task, desc, status, origin, branch, needs, last_send, pr, pr_state, pr_review, pr_merge_state, last_refresh, trace_agent, and trace_path.
tp set NAME status "waiting-for-review"
tp set NAME branch "feat/auth"
tp get NAME status
For AI Orchestrators
tmux-pilot is designed to be called by AI coding agents and orchestration scripts, not just humans. The --json flag on tp ls outputs machine-readable JSON:
$ tp ls --json
[
{
"name": "auth-flow",
"process": "claude-code",
"working_dir": "/home/user/repos/myapp",
"metadata": {
"desc": "Implement OAuth2 login",
"status": "active",
"repo": "/home/user/repos/myapp"
}
}
]
A typical orchestrator loop:
# Refresh review state for a repo
tp refresh --repo myapp
# See which branches need attention
tp ls --cols NAME,PR,STATUS,DIR --repo myapp
# Drill into the sessions that need work
tp ls --json --repo myapp | jq -r '.[] | select(.metadata.pr_review == "CHANGES_REQUESTED") | .name' | while read name; do
tp peek "$name" -n 20
tp send --wait "$name" "address the requested review changes"
done
Key Features
- Zero dependencies — stdlib only (subprocess calls to tmux)
- Process detection — distinguishes claude-code vs codex vs bare shell
- Task bootstrap — create task branches and worktrees directly from
tp new --repo - PR refresh — cache PR number, state, review state, and merge state with
tp refresh - Trace binding — cache the transcript trace for a session with
tp trace - Metadata — tmux user options (@status, @desc, @repo, @branch, @pr, etc.)
- Metadata freshness —
tp statusshows when cached fields were last updated - Peek without attaching — critical for orchestrators monitoring sessions
- JSON output —
tp ls --jsonfor machine-readable session data - Filtering —
tp ls --status/--repo/--processandtp refresh --repoto narrow results - fzf integration — optional fuzzy picker for
tp jump
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tmux_pilot-0.1.1.tar.gz.
File metadata
- Download URL: tmux_pilot-0.1.1.tar.gz
- Upload date:
- Size: 142.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2413008cfa7719f2b4622ce3401fac97e564ae5e725a8a669b9c912fb664a893
|
|
| MD5 |
662995e04095b628437151bc24598bf5
|
|
| BLAKE2b-256 |
920b4abf66efb6247c3444a464ca0d51861990167f82865735ccfbfaa381c737
|
Provenance
The following attestation bundles were made for tmux_pilot-0.1.1.tar.gz:
Publisher:
publish.yml on cmungall/tmux-pilot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tmux_pilot-0.1.1.tar.gz -
Subject digest:
2413008cfa7719f2b4622ce3401fac97e564ae5e725a8a669b9c912fb664a893 - Sigstore transparency entry: 1375652974
- Sigstore integration time:
-
Permalink:
cmungall/tmux-pilot@77aac52315966e9b1884a3edb6a8edf15474efc1 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/cmungall
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@77aac52315966e9b1884a3edb6a8edf15474efc1 -
Trigger Event:
release
-
Statement type:
File details
Details for the file tmux_pilot-0.1.1-py3-none-any.whl.
File metadata
- Download URL: tmux_pilot-0.1.1-py3-none-any.whl
- Upload date:
- Size: 47.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
99277ccf87369da36ded89eb7ae03d5318061e81b8966f87db1f22d54eb1a89f
|
|
| MD5 |
f920c9c1884387c38ad0c381a99d5185
|
|
| BLAKE2b-256 |
5192dc9c2f5ee854a83db634a1b24d367af226f5d32c670749a3f15ccf9367f9
|
Provenance
The following attestation bundles were made for tmux_pilot-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on cmungall/tmux-pilot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tmux_pilot-0.1.1-py3-none-any.whl -
Subject digest:
99277ccf87369da36ded89eb7ae03d5318061e81b8966f87db1f22d54eb1a89f - Sigstore transparency entry: 1375653058
- Sigstore integration time:
-
Permalink:
cmungall/tmux-pilot@77aac52315966e9b1884a3edb6a8edf15474efc1 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/cmungall
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@77aac52315966e9b1884a3edb6a8edf15474efc1 -
Trigger Event:
release
-
Statement type: