A project-level orchestrator for AI coding agents
Go + Charm stack reimplementation of OpenAI's Symphony (openai/symphony) β manage work, not agents
Contrabass is a terminal-first orchestrator for issue-driven agent runs, with an optional local web dashboard for live visibility.
Today Contrabass ships with:
- A Cobra CLI with TUI, headless, and optional embedded web dashboard modes
- A
WORKFLOW.mdparser with YAML front matter, Liquid prompt rendering, and$ENV_VARinterpolation - Issue tracker adapters for Linear, GitHub Issues, and a built-in Internal Board (local filesystem, no external service required)
- Agent runners for Codex app-server, OpenCode, oh-my-opencode, OMX (oh-my-codex), and OMC (oh-my-claudecode)
- Git-worktree-based workspace provisioning under
workspaces/<issue-id>with non-git fallback for repositories without git - Teams: multi-agent coordination with a local task board, phased pipeline (plan β exec β verify), live TUI team table, and dual worker modes (tmux-based multi-process or goroutine-based in-process)
- An orchestrator with claim/release, BlockedBy gating, orphan claim recovery, branch advance verification, stall detection, deterministic retry backoff, liveness snapshots with agent stage classification and ETA estimation
- A Charm v2 terminal UI built with Bubble Tea, Bubbles, and Lip Gloss
- Ziikoo β a React dashboard (neo-brutalism theme, shadcn + Tailwind v4) with a three-pane IDE-style layout, live SSE streaming, queue navigation, stage progression pills, completion ETAs, issue detail sheets with Linear metadata and workflow timelines, team/worker tables, agent logs, and zh-CN localization
- Go unit/integration tests, TUI snapshot tests, and dashboard component/hook tests
- A tmux-based multi-process worker mode (default) alongside the in-process goroutine mode, with JSONL event logging, file-based heartbeats, dispatch queue, governance policies, and crash recovery
- Go 1.25+
- Bun 1.3+ for the dashboard/landing workspace
- Git (workspace creation uses
git worktree) - tmux (required for the default tmux worker mode in team runs; not needed for goroutine mode)
- A supported agent runtime:
codex app-serveropencode serveoh-my-opencodeomx(oh-my-codex team runtime)omc(oh-my-claudecode team runtime)
- Tracker credentials for the backend you use:
- Linear:
LINEAR_API_KEY - GitHub:
GITHUB_TOKEN
- Linear:
From a fresh clone, run bun install once before using the JS/landing build and test commands.
brew install junhoyeo/contrabass/contrabassPre-built binaries for macOS and Linux (amd64/arm64) are available on the Releases page.
git clone https://github.com/junhoyeo/contrabass.git
cd contrabass
bun install
make buildmake build first builds packages/dashboard/dist/ and then embeds it into the Go binary.
Note:
go install github.com/junhoyeo/contrabass/cmd/contrabass@latestworks for the CLI and TUI, but the embedded web dashboard (--port) will be empty becausego installdoes not run the JS build step.
LINEAR_API_KEY=your-linear-token \
./contrabass --config testdata/workflow.demo.mdLINEAR_API_KEY=your-linear-token \
./contrabass --config testdata/workflow.demo.md --port 8080Then open http://localhost:8080.
LINEAR_API_KEY=your-linear-token \
./contrabass --config testdata/workflow.demo.md --no-tui--config string path to WORKFLOW.md file (required)
--dry-run exit after first poll cycle
--log-file string log output path (default "contrabass.log")
--log-level string log level (debug/info/warn/error) (default "info")
--no-tui headless mode β skip TUI, log events to stdout
--port int web dashboard port (0 = disabled)
contrabass team run --config workflow.md [flags]
--worker-mode string override worker mode (goroutine|tmux, default from config)
- Poll the configured tracker for candidate issues.
- Skip issues with unresolved
BlockedBydependencies (BlockedBy gating). - Claim an eligible issue, recording the workspace HEAD SHA at claim time.
- Create or reuse a git worktree in
workspaces/<issue-id>(falls back to plain directory when git is unavailable). - Render the prompt body from
WORKFLOW.mdusing issue data. - Launch the configured agent runner.
- Stream agent events, classify agent stage (Exploration β Editing β Testing β Reviewing β Wrapping), track token consumption, and estimate completion ETAs.
- On completion, verify the workspace branch advanced beyond the claim HEAD before marking success.
- On failure, retry with deterministic exponential backoff + FNV-hash jitter.
- Recover orphaned claims on restart β issues marked Claimed but not actively running are reset to Unclaimed.
- Mirror state into the TUI, the Ziikoo dashboard (via SSE), and the JSON snapshot API.
| Feature | Description |
|---|---|
| BlockedBy gating | Issues with unresolved blockers are deferred from dispatch |
| Orphan claim recovery | Claimed-but-not-running issues are reclaimed on restart |
| Branch advance verification | Verifies agents made commits before marking success |
| Agent stage classification | Monotonic 5-stage progression based on diff velocity and token patterns |
| Completion ETA | Confidence-banded estimates (requires 3+ min elapsed, stage β₯ 3 for high confidence) |
| Liveness snapshots | Per-agent heartbeat age, activity timestamps, diff stats, iteration progress |
| Stall detection | Flags runs lacking recent events beyond stall_timeout_ms |
| Deterministic backoff | Exponential growth with FNV-hash jitter (reproducible across restarts) |
| Graceful shutdown | Drains running agents before process exit |
WORKFLOW.mdis watched withfsnotify; on parse errors, Contrabass keeps the last known good config.- The Codex runner speaks newline-delimited JSON (
JSONL) tocodex app-serverrather thanContent-Lengthframed messages. Seedocs/codex-protocol.md. - The Codex runner handles
-32001server overload errors with exponential backoff retry (up to 5 attempts) and detects stalled streams via configurable read timeouts. - The workflow parser already accepts more Symphony-shaped fields than the runtime fully consumes today. For example,
workspace,hooks, and somecodexsettings are parsed, but the current runtime mainly uses tracker selection, timeouts, retry settings, binary paths, and prompt/template fields.
Teams support two worker modes, configured via team.worker_mode in the workflow file or the --worker-mode CLI flag:
| Mode | Description | Default |
|---|---|---|
tmux |
Each worker runs in a separate tmux pane with process isolation, cross-process IPC via JSONL events, and file-based heartbeats | Yes |
goroutine |
Workers run as goroutines within the contrabass process β lighter weight, no tmux dependency |
tmux mode (default) provides:
- Process isolation β each agent CLI runs in its own tmux pane
- JSONL event log for cross-process event streaming
- File-based heartbeat monitoring with stale detection
- Dispatch queue with ack tracking and timeout redelivery
- Governance policies with role routing heuristics
- Crash recovery with state diagnosis and automatic cleanup
- Advisory file locking via
flock(2)for safe concurrent access
goroutine mode runs all workers in-process using Go's errgroup and sync.Mutex. It requires no external dependencies but shares the process address space.
Team state is persisted as JSON files under .contrabass/state/team/{teamName}/.
Contrabass reads a Markdown workflow file with YAML front matter followed by the prompt template body.
---
max_concurrency: 3
poll_interval_ms: 2000
max_retry_backoff_ms: 240000
model: openai/gpt-5-codex
project_url: https://linear.app/acme/project/example
agent_timeout_ms: 900000
stall_timeout_ms: 60000
tracker:
type: linear
linear:
issue_details:
enabled: true
sync_comments:
enabled: false
mode: reply_thread
agent:
type: codex
codex:
binary_path: codex app-server
---
# Workflow Prompt
Issue title: {{ issue.title }}
Issue description: {{ issue.description }}
Issue URL: {{ issue.url }}
Produce code and tests that satisfy the issue requirements.When tracker.type: linear is used, the dashboard can load richer issue
metadata through the Contrabass backend without exposing Linear credentials to
browser code.
linear:
issue_details:
enabled: true
sync_comments:
enabled: false
mode: reply_thread # reply_thread by default; top_level is the fallback-safe modelinear.issue_details.enabledcontrols backend issue detail reads used by the issue detail sheet. Candidate polling remains lean.linear.sync_comments.enabledis opt-in and defaults tofalse; when enabled, durable workflow timeline nodes are projected to Linear comments.- Comment sync is best-effort and asynchronous. It records retry/sync status in local timeline state and does not block issue completion, retry queueing, or dashboard rendering.
- Disable
linear.sync_comments.enabledto preserve legacy direct completion comments and avoid any Linear comment projection.
The current prompt renderer exposes:
issue.titleissue.descriptionissue.url
String values in YAML front matter can reference environment variables using $NAME syntax.
Examples:
tracker.token: $GITHUB_TOKENopencode.password: $OPENCODE_SERVER_PASSWORDomx.binary_path: $OMX_BINARYomc.binary_path: $OMC_BINARY
For Linear trackers, Contrabass can load richer issue metadata for the dashboard and maintain a local workflow timeline that is projected back to Linear comments only when explicitly enabled.
tracker:
type: linear
linear:
issue_details:
enabled: true
sync_comments:
enabled: false
mode: reply_thread # or top_levellinear.issue_details.enableddefaults to enabled for Linear trackers and is ignored for non-Linear trackers.linear.sync_comments.enableddefaults tofalse; comment sync is best-effort and opt-in.linear.sync_comments.modedefaults toreply_thread; usetop_levelwhen threaded replies are unsupported or undesired.- Workflow timeline files are local Contrabass state and remain the source of truth even when Linear sync is disabled or temporarily fails.
For team-runtime-backed runners, set agent.type to omx or omc and configure the corresponding section.
agent:
type: omx
omx:
binary_path: omx
team_spec: 2:executor
poll_interval_ms: 1500
startup_timeout_ms: 22000
ralph: trueagent:
type: omc
omc:
binary_path: omc
team_spec: 2:claude
poll_interval_ms: 1200
startup_timeout_ms: 21000Notes:
binary_pathcan point to the installed CLI wrapper, for exampleomxoromc.team_specis passed directly to the team runtime, such as1:executor,2:executor, or2:claude.- Contrabass writes the rendered task prompt into
.contrabass/runner/<runner>/...inside the workspace and instructs the team runtime to execute from that file. - OMC/OMX team runners generally require the underlying toolchain prerequisites those CLIs expect, especially tmux-based team support.
The team section configures multi-agent coordination:
team:
max_workers: 5
max_fix_loops: 3
claim_lease_seconds: 300
state_dir: .contrabass/state/team
execution_mode: team # team | single | auto
worker_mode: tmux # tmux (default) | goroutineworker_mode: Controls how agent workers are spawned.tmux(default) uses separate tmux panes with process isolation.goroutineruns workers in-process.execution_mode: Controls coordination strategy.teamuses the full phased pipeline,singleruns one agent at a time,autoselects based on task count.
testdata/workflow.demo.mdβ demo Linear + Codex workflowtestdata/workflow.github.mdβ GitHub + OpenCode workflowtestdata/workflow.ohmyopencode.mdβ oh-my-opencode workflowtestdata/workflow.omx.mdβ OMX workflowtestdata/workflow.omc.mdβ OMC workflowtestdata/workflow.mdβ realistic Linear fixture
| Surface | Current support |
|---|---|
| Trackers | Linear, GitHub Issues, Internal Board |
| Agent runners | Codex app-server, OpenCode, oh-my-opencode, OMX, OMC |
| Operator surfaces | Charm TUI, Ziikoo web dashboard, headless mode |
| Live config reload | Yes (WORKFLOW.md via fsnotify) |
| State streaming | JSON snapshot API + SSE (orchestrator, team, board, agent log events) |
- Linear
- GraphQL-based issue fetch, claim, release, state update, and comment posting
- Can auto-resolve the assignee from the API token when
tracker.assignee_idis omitted
- GitHub Issues
- REST-based issue fetch, assign/unassign, comment, and close-on-release behavior
- Pull requests are skipped when fetching issues
- Internal Board
- File-based local issue tracking under
.contrabass/board/β no external service required - Supports team-scoped boards for multi-agent coordination
- See
docs/local-board.mdfor format details
- File-based local issue tracking under
- Codex
- Launches
codex app-serverwith JSONL protocol (newline-delimited JSON, not Content-Length framed) - Performs
initializeβinitializedβthread/startβturn/start - Streams notifications and token usage updates in real time
- Handles
-32001server overload with exponential backoff retry (up to 5 attempts) - Detects stalled streams via configurable read timeout (
WithStreamReadTimeout) - Closes stdin on terminal events (
turn/completed,turn/failed,turn/cancelled) for clean exit - Supports Codex 0.128+
thread/tokenUsageshape - Forwards workflow-level
codexconfig as-c key=valueoverrides (model, approval policy, sandbox)
- Launches
- OpenCode
- Starts or reuses an
opencode serveprocess - Creates sessions over HTTP and streams events over SSE
- Starts or reuses an
- oh-my-opencode
- Wraps the
oh-my-opencodeagent binary - HTTP session creation with SSE event streaming
- Wraps the
- OMX (oh-my-codex)
- Launches
omx team ...with a workspace-scoped task file - Polls
omx team api get-summaryandomx team api list-tasksfor status and results - Tracks per-session token usage (input/output/total) and rate limit proximity (5-hour, weekly)
- Monitors worker liveness via file-based heartbeats with stale detection
- Shuts down the team with
omx team shutdown ... --force(and--ralphwhen configured) - Supports OMX v0.16+ native worker supervisor protocol
- Launches
- OMC (oh-my-claudecode)
- Launches
omc team ...with a workspace-scoped task file - Polls
omc team api get-summaryandomc team api list-tasksfor status and results - Same token/heartbeat monitoring as OMX
- Shuts down the team with
omc team shutdown ... --force
- Launches
When --port is set, Contrabass serves Ziikoo β a React dashboard embedded in the Go binary β alongside a JSON/SSE API for programmatic access.
Ziikoo uses a three-pane IDE-style layout:
- Left sidebar β queue navigation (running, backoff, todo, backlog, recently done, canceled) with live counts
- Main content β responsive data tables with aggregate metric cards
- Right detail sheet β slide-out panel with issue metadata, workflow timeline, and agent controls
Key capabilities:
- 5-step agent stage pill showing progression: Exploration β Editing β Testing β Reviewing β Wrapping
- Completion ETA with confidence bands (low/medium/high)
- Activity indicators with freshness coloring (fresh/warm/stale based on heartbeat age)
- Live metrics β running load, queued count, archived count, token consumption (in/out)
- Issue detail sheets β Linear metadata (assignee, creator, team, project, cycle, estimate, due date, relations), workflow timeline with sync status badges, debug info (PID, session ID, workspace path)
- Blocked queue panel β issues deferred by BlockedBy with blocker identifiers
- Retry queue β backoff entries with live countdown timers
- Team table β team phase, worker counts, task counts, fix loop progress
- Worker table β per-worker status (busy/idle/stopped), current task, PID
- Agent logs β streaming stdout/stderr with worker filter dropdown
- Board view β CRUD interface for the internal board tracker (create/edit issues, change state)
- Stop agent button β terminate running agents directly from the detail sheet
- zh-CN localization β full Simplified Chinese interface
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/state |
Full orchestrator snapshot (stats, running entries, backoff queue, issues, build info) |
GET |
/api/v1/issues/{issue_id}/details |
Issue with Linear metadata when available |
GET |
/api/v1/issues/{issue_id}/timeline |
Workflow timeline snapshot |
GET |
/api/v1/{identifier} |
Single issue lookup from snapshot |
GET |
/api/v1/board/issues |
List all internal board issues |
GET |
/api/v1/board/issues/{identifier} |
Get single board issue |
POST |
/api/v1/board/issues |
Create board issue |
PATCH |
/api/v1/board/issues/{identifier} |
Update board issue (title, description, state, assignee) |
POST |
/api/v1/running/{issue_id}/stop |
Terminate running agent and release issue |
POST |
/api/v1/refresh |
Trigger refresh (202 Accepted) |
GET |
/api/v1/events |
SSE event stream |
Connect to /api/v1/events for real-time updates. The initial event is a full snapshot, followed by incremental events:
| Kind | Events |
|---|---|
orchestrator |
StatusUpdate, AgentStarted, AgentFinished, BackoffEnqueued, IssueReleased |
team |
tool_call, team/stalled, team/all_idle, team/missing, team/event |
board |
board_issue_created, board_issue_updated, board_issue_moved |
agent_log |
Streaming worker stdout/stderr |
queue |
Dispatch blocked by unresolved dependencies |
Heartbeat events are filtered server-side and never reach clients. Keep-alive comments are sent every 15 seconds.
make build # build dashboard, then build ./contrabass
make build-dashboard # build packages/dashboard/dist only
make build-landing # build packages/landing/dist only
make test # go test ./... -count=1
make test-dashboard # bun test in packages/dashboard
make test-landing # astro check in packages/landing
make test-quick # recommended local validation path
make test-all # Go + dashboard tests + landing checks
make ci # lint + test-quick + binary/dashboard build + landing build
make lint # go vet ./...
make clean # remove built artifacts
make release-dry # dry-run GoReleaser locally (skips publish)For day-to-day local validation, use make test-quick.
For a fuller pre-push or CI-style pass, use make ci.
make dev-dashboard
make dev-landingThe repository is a root Bun workspace with packages/dashboard and packages/landing.
The Astro landing site renders README.md, so this file is both repo documentation and site content.
go run ./cmd/contrabass --config testdata/workflow.demo.md --port 8080docs/codex-protocol.mdβ notes on the Codex app-server framing and lifecycle used heredocs/local-board.mdβ internal board tracker file format and schemadocs/test-plan.mdβ ported test-plan notes from the Elixir codebasetestdata/snapshots/β golden snapshots for the TUI renderer
Direct dependencies from the Charm v2 ecosystem:
| Logo | Library | Import Path | Purpose |
|---|---|---|---|
Β Β ![]() |
Bubble Tea | charm.land/bubbletea/v2 |
TUI framework (Elm architecture) |
![]() |
Lip Gloss | charm.land/lipgloss/v2 |
Styling & layout |
![]() |
Bubbles | charm.land/bubbles/v2 |
Reusable TUI components |
![]() |
Log | github.com/charmbracelet/log |
Structured logging |
![]() |
x | github.com/charmbracelet/x |
x/mosaic for terminal image rendering |
Plus:
github.com/charmbracelet/logfor structured logginggithub.com/fsnotify/fsnotifyfor config watchinggithub.com/osteele/liquidfor prompt templatinggithub.com/stretchr/testifyfor Go test assertions
CI and release workflows run automatically via GitHub Actions:
- CI (
.github/workflows/ci.yml) β runs on every push and PR: lint, test, build - Release (
.github/workflows/release.yml) β triggered by pushing a version tag
To ship a new release:
git tag v0.4.1
git push origin v0.4.1This builds cross-platform binaries (macOS/Linux, amd64/arm64) via GoReleaser, publishes a GitHub Release with grouped changelogs, and updates the Homebrew tap.
After GoReleaser publishes the release, scripts/generate-release-notes.ts
appends contributor attribution β each change is tagged with the author's @username and linked PR,
and first-time contributors get a dedicated shout-out section.
For detailed contribution guidelines, see CONTRIBUTING.md.
- The dashboard assets must exist before the Go binary is built because the binary embeds
packages/dashboard/dist. packages/landingrendersREADME.md, so README changes also affect the landing site.- If workspace package resolution looks broken in
packages/dashboardorpackages/landing, rerunbun installat the repository root to refresh workspace links. - TUI snapshots live in
testdata/snapshots/and are exercised byinternal/tuitests.





