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> - 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, timeout detection, stall detection, deterministic retry backoff, and state snapshots
- A Charm v2 terminal UI built with Bubble Tea, Bubbles, and Lip Gloss
- A React dashboard served from the Go binary, with state snapshots and live SSE updates
- 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.
- Claim an eligible issue.
- Create or reuse a git worktree in
workspaces/<issue-id>. - Render the prompt body from
WORKFLOW.mdusing issue data. - Launch the configured agent runner.
- Stream agent events, track tokens/phases, and publish orchestrator events.
- Retry failed runs with exponential backoff + deterministic jitter.
- Mirror state into the TUI and, when enabled, the embedded web dashboard.
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 web dashboard currently has live metrics, running sessions, and retry queue data. The rate-limit panel exists, but there is not yet a live rate-limit feed behind it.
- 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
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.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 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, embedded web dashboard, headless mode |
| Live config reload | Yes (WORKFLOW.md via fsnotify) |
| State streaming | JSON snapshot API + SSE |
- 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-server - Performs
initializeβinitializedβthread/startβturn/start - Streams newline-delimited JSON notifications and usage updates
- 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 - Shuts down the team with
omx team shutdown ... --force(and--ralphwhen configured)
- 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 - Shuts down the team with
omc team shutdown ... --force
- Launches
When --port is set, Contrabass serves the embedded dashboard and a small JSON/SSE API.
GET /api/v1/stateβ full orchestrator snapshotGET /api/v1/{identifier}β cached issue lookup from the latest snapshotGET /api/v1/eventsβ SSE stream (initial snapshot + live orchestrator events)POST /api/v1/refreshβ currently returns202 Acceptedas a placeholder hook
The dashboard currently renders:
- connection status
- aggregate runtime/token metrics
- running session table
- retry queue
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.2.0
git push origin v0.2.0This 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.





