Mission control for AI coding agents.
regatta runs many Codex (or Claude) workers in parallel — each on its own task, in its own git worktree, in its own tmux window — and gives you one terminal cockpit to steer them. A foreman agent does the planning, the workers do the coding, and you do the steering.
freebird 5 active · 2 blocked · 1 stale · 3 ready last 8s progress
▰▰▰▰▰▰▰▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱ 47%
activity ─────────────────────────────────────────────────────────────────────────────────────────
▁▁▂▃▆█▇▅▃▂▂▁▁▂▃▄▆▇█▇▅▄▃▂▁▁▂▃▄▅▆
events · last 30m · 412
lanes ─────────────────────────────────────────────────────────────────────────────────────────────
READY 3 │ ACTIVE 5 │ BLOCKED 2 │ CHANGED 4 │ DONE 11
─ csv-import │ ─ backend-api │ ─ frontend-up… │ ─ migration-fix │ ─ schema-v2
─ retry-flow │ ─ test-harden │ ─ s3-creds │ ─ docs-rewrite │ ─ ci-cache
─ docs-bulk │ ─ rate-limiter │ │ ─ rbac-models │ +9 more
│ ─ webhook-sign │ │ ─ logging-cleanup│
│ ─ idle-worker ●STALE │ │
This is what a regatta session looks like with five things happening at once.
- Real parallelism. Each task gets its own worktree and its own agent. No more sharing one repo with one conversation. No more rebasing manually.
- You stay in the loop. The mission control TUI shows what every worker is doing right now — heartbeat, terminal tail, file changes, blockers — without context-switching across tmux windows.
- Steer from anywhere. Same control surface from the TUI, the CLI (
regatta spawn,regatta retask,regatta pause), or Discord (!regatta retask backend-api ...). Pick whichever is handy. - Boring under the hood. Plain tmux, plain SQLite, plain Python. No daemons, no servers, no cloud. If you can read a
.regatta/runs/<id>/state.dbfile you can debug anything.
pip install regatta # once published
# or, from a checkout:
pip install -e .Requires tmux, git, Python 3.10+, and a working codex CLI on your $PATH. Optional: claude CLI for tasks you want Claude to run.
Run regatta doctor to check your environment in one shot.
cd ~/Code # or any directory containing repos you want to work on
regatta start # creates a tmux session called `regatta`, opens mission controlThat's it. You're now in a tmux session with:
- a foreman pane running Codex — talk to it like a colleague who can dispatch work
- a mission window — the cockpit pictured above
- compact summary / focus panes that stay glanceable
- an ops shell for whatever you'd usually do in a terminal
- one worker window per task once you spawn them
To switch to mission control: Ctrl-b then 2 (or tmux select-window -t regatta:mission).
To add work, just tell the foreman in plain English:
"Three things to do: add CSV bulk import to the backend, harden upload validation, document the new flow. Spawn workers for the first two and leave the docs as ready."
The foreman files the tasks into SQLite, spins up worktrees, opens worker windows, and the mission view updates live.
The mission window is the operator surface. It updates live — the activity sparkline ticks, heartbeats decay, lane cards move between columns as workers progress.
| key | action |
|---|---|
↑ ↓ |
move selection between tasks |
f |
share selection as the run-wide focus |
s |
spawn the selected task |
p |
pause the selected task |
e |
resume the selected task |
w |
jump to the selected worker's tmux window |
t |
toggle between lane board (default) and table view |
d |
toggle the dependency tree overlay |
/ |
focus the filter input |
; |
focus the command input |
c |
clear filter |
r |
force refresh |
1–7 |
bucket filters: all / ready / active / blocked / paused / todo / done |
q |
quit |
Anything you can do by hotkey, you can do with arguments. Some things only work here.
spawn backend-api
pause backend-api waiting on API contract
resume backend-api contract is stable now
retask backend-api defer caching, focus on validation + tests
focus test-hardening
worker frontend-upload
filter csv
bucket blocked
If you omit the task id, the currently-selected row is the target.
You don't have to be in the TUI to drive a run. Every action has a CLI form:
regatta status # one-frame overview, pretty in any pane
regatta tasks # list tasks
regatta spawn backend-api # start a worker
regatta pause backend-api "waiting on contract"
regatta resume backend-api "contract is stable"
regatta retask backend-api "tighten scope, add tests"
regatta done backend-api "shipped"
regatta inspect backend-api # full focused view of one task
regatta events --limit 30 # raw event stream
regatta where # which run/workspace am I in?
regatta runs # list recent runs
regatta logs backend-api --follow # tail a worker's output
regatta note add "API contract is frozen" # append to the shared knowledge base
regatta ship backend-api # open a PR from the task's branch
regatta export <run-id> # archive a run (state.db + logs) for sharingFor the deterministic low-level surface (adding tasks programmatically, etc.):
regatta ctl add-task \
--task-id docs \
--title "Update docs" \
--repo-root ~/Code/my-repo \
--prompt "Document the new flow."For a workspace you orchestrate repeatedly, drop a regatta.yaml at the workspace root:
regatta init-config --objective "Coordinate active work across my local repos"workspace_root: .
run_root: .regatta/runs
session_name: regatta
objective: Coordinate active work across my local repos
mission_ui: auto # auto | textual | rich
pane_refresh: 5 # seconds, compact panes
mission_refresh: 1 # seconds, mission window
max_active_workers: 8 # cap on simultaneously-active workers; 0/negative = unlimited
discord: falsemax_active_workers caps how many workers can be active at once. When you are at
the cap, regatta spawn <task> refuses with an actionable error; pass --force to bypass
it once, or --max-active-workers N to raise the cap and spawn in one command. The
status/mission header shows the current count as active N/M.
regatta searches upward for regatta.yaml, regatta.yml, .regatta.yaml, or .regatta.yml. Paths inside the config resolve relative to the config file.
Steer a run from your phone. The bridge is a small bot that reads one Discord channel and calls the same control actions the TUI uses.
export DISCORD_BOT_TOKEN='...' # required (env only; never argv)
export DISCORD_CHANNEL_ID='123456789012345678' # required: scope the bot to one channel
export DISCORD_ALLOWED_USER_IDS='111,222' # required: only these user IDs may issue commands
export DISCORD_COMMAND_PREFIX='!regatta' # optional
regatta start --discordSee the Security and trust model section for why every one of those should be set, not just DISCORD_BOT_TOKEN.
!regatta status
!regatta tasks [all|ready|active|blocked|paused|todo|done|failed]
!regatta inspect [task_id]
!regatta events [limit]
!regatta focus <task_id>
!regatta spawn <task_id> [--force]
!regatta pause <task_id> [message]
!regatta resume <task_id> [message] [--force]
!regatta retask <task_id> <message>
Enable the bot's Message Content Intent in the Discord developer portal first.
Preload a task board from YAML — useful when you know up-front what work needs to ship:
regatta start \
--plan plans/example_plan.yaml \
--objective "Ship CSV bulk import safely with parallel workers"See plans/ for examples.
The package lives under src/regatta/:
| module | role |
|---|---|
regatta.regatta_cli |
the regatta CLI front door |
regatta.orchestrator |
session bootstrap (tmux windows, panes, foreman) |
regatta.tmux |
tmux helpers (windows, panes, send-keys) |
regatta.git_ops |
git / worktree helpers |
regatta.plans |
YAML plan loader |
regatta.actions |
spawn / retask / pause / resume / focus business logic |
regatta.cli |
deterministic CLI surface (used by regatta ctl …) |
regatta.regattactl |
aggregated re-exports of the split control modules |
regatta.state |
SQLite schema, migrations, and queries |
regatta.doctor |
environment health checks for regatta doctor |
regatta.mission_textual |
the Textual mission control app |
regatta.mission_widgets |
native Textual widgets (lane board, heartbeat, etc.) |
regatta.mission_cache |
TTL cache for git / tmux IO |
regatta.ui_render |
Rich rendering — shared by mission view and dashboard panes |
regatta.ui_snapshot |
builds the unified snapshot every UI consumes |
regatta.dashboard |
one-frame and --watch Rich views, plus mission full-screen mode |
regatta.control_room |
thin deprecation shim — folded into dashboard |
regatta.discord_bridge |
Discord bot |
regatta.hooks |
user-defined lifecycle event hooks (task.spawned / done / failed / worker.stale) |
regatta.plans/ (package data) |
starter plans for preloading a board |
regatta.prompts/ (package data) |
foreman/system prompts |
State lives under .regatta/runs/<run-id>/. Tasks, focus, events, and worker metadata are all in state.db. Worker code lives in per-task worktrees alongside.
- The foreman is for planning, the mission view is for piloting. The foreman pane is where you discuss what work to do and why. The mission view is the fast lane for spawning / pausing / retasking once the work exists.
- Selection is local; focus is shared. Browsing tasks in the mission view doesn't disturb the rest of the control room. Press
f(orEnter) when you want to commit a selection as the run-wide focus. - The lane board is the primary view. It shows movement — who's moving, who's stuck, who's queued — at a glance. The table is still there (
t) for when you want sortable density. - Visualisations are live. The activity sparkline shows events-per-minute over the last 30 minutes. Heartbeat decay bars per active worker turn magenta when stale (no heartbeat in 10 min). Dependency overlay (
d) shows what's blocking what. - No daemon. SQLite is the source of truth. The TUI, the CLI, and the Discord bridge all read and write the same store.
- Python 3.10+
tmuxgitcodexCLI on$PATH(and optionallyclaude)- Python deps:
textual,rich,PyYAML,discord.py(auto-installed bypip install -e .)
regatta is a developer tool, not a sandbox. Before pointing it at anything sensitive, read this section.
Every task prompt is sent to whichever model your provider CLI is configured for (codex, claude, …). If you are running on a private repository, assume the contents of your task prompt and any code the worker reads have left your machine. If your provider relationship doesn't permit that, do not run regatta against that repo.
.regatta/runs/<run-id>/ contains task prompts, worker transcripts, logs, and any output the agents produced. It is gitignored — do not commit it. The directory may contain secrets the agents surfaced; treat it the way you would treat a shell history file.
The Discord bridge is a remote-control surface. Anyone able to post in the configured channel can spawn or retask workers, which means they can run arbitrary AI agents inside your worktrees. Configure it defensively:
DISCORD_ALLOWED_USER_IDS(comma-separated numeric user IDs) is required: the bridge fails closed and refuses to start unless at least one trusted operator id is set, so an empty allowlist can never leave commands open to everyone.- Always set
DISCORD_CHANNEL_IDso the bot also ignores messages from other channels. DISCORD_BOT_TOKENis read from the environment only. It is never accepted on the command line; that would leak it viaps.
PlanLoader trusts the YAML author. A plan file prescribes prompts, repos, and metadata that drive what workers do. Only load plans you wrote or fully reviewed. Loading an untrusted plan is the rough equivalent of running someone else's shell script — the prompts may be hostile, and the metadata can route writes into repos you didn't expect.
Run directories accumulate task prompts, model output, log files, and SQLite databases. They are large, low-signal, and may contain secrets that agents accidentally echoed. The default .gitignore excludes them; please keep it that way.
MIT. See also SECURITY.md for reporting vulnerabilities and CONTRIBUTING.md for how to help.