Contrabass

Contrabass Logo

A project-level orchestrator for AI coding agents
Go + Charm stack reimplementation of OpenAI's Symphony (openai/symphony) — manage work, not agents

Contrabass Demo (TUI in Action)

Contrabass is a terminal-first orchestrator for issue-driven agent runs, with an optional local web dashboard for live visibility.

Current scope

Today Contrabass ships with:

Requirements

From a fresh clone, run bun install once before using the JS/landing build and test commands.

Installation

Homebrew (macOS/Linux)

brew install junhoyeo/contrabass/contrabass

Download from GitHub Releases

Pre-built binaries for macOS and Linux (amd64/arm64) are available on the Releases page.

Build from source

git clone https://github.com/junhoyeo/contrabass.git
cd contrabass
bun install
make build

make build first builds packages/dashboard/dist/ and then embeds it into the Go binary.

Note: go install github.com/junhoyeo/contrabass/cmd/contrabass@latest works for the CLI and TUI, but the embedded web dashboard (--port) will be empty because go install does not run the JS build step.

Quick start

Run with the demo workflow

LINEAR_API_KEY=your-linear-token \
./contrabass --config testdata/workflow.demo.md

Run with the embedded web dashboard

LINEAR_API_KEY=your-linear-token \
./contrabass --config testdata/workflow.demo.md --port 8080

Then open http://localhost:8080.

Run headless

LINEAR_API_KEY=your-linear-token \
./contrabass --config testdata/workflow.demo.md --no-tui

CLI flags

--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)

Team subcommand flags

contrabass team run --config workflow.md [flags]

--worker-mode string   override worker mode (goroutine|tmux, default from config)

How Contrabass works

  1. Poll the configured tracker for candidate issues.
  2. Skip issues with unresolved BlockedBy dependencies (BlockedBy gating).
  3. Claim an eligible issue, recording the workspace HEAD SHA at claim time.
  4. Create or reuse a git worktree in workspaces/<issue-id> (falls back to plain directory when git is unavailable).
  5. Render the prompt body from WORKFLOW.md using issue data.
  6. Launch the configured agent runner.
  7. Stream agent events, classify agent stage (Exploration → Editing → Testing → Reviewing → Wrapping), track token consumption, and estimate completion ETAs.
  8. On completion, verify the workspace branch advanced beyond the claim HEAD before marking success.
  9. On failure, retry with deterministic exponential backoff + FNV-hash jitter.
  10. Recover orphaned claims on restart — issues marked Claimed but not actively running are reset to Unclaimed.
  11. Mirror state into the TUI, the Ziikoo dashboard (via SSE), and the JSON snapshot API.

Orchestrator features

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

Runtime notes

Team worker modes

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:

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}/.

Workflow file format

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.

Linear detail and timeline sync settings

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 mode

Template bindings

The current prompt renderer exposes:

Environment-variable interpolation

String values in YAML front matter can reference environment variables using $NAME syntax.

Examples:

Linear issue details and workflow timeline

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_level

OMC / OMX workflow sections

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: true
agent:
  type: omc
omc:
  binary_path: omc
  team_spec: 2:claude
  poll_interval_ms: 1200
  startup_timeout_ms: 21000

Notes:

Team configuration

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) | goroutine

Example workflow files

Supported integrations

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)

Trackers

Agent runners

Web dashboard (Ziikoo) and HTTP API

When --port is set, Contrabass serves Ziikoo — a React dashboard embedded in the Go binary — alongside a JSON/SSE API for programmatic access.

Dashboard features

Ziikoo uses a three-pane IDE-style layout:

  1. Left sidebar — queue navigation (running, backoff, todo, backlog, recently done, canceled) with live counts
  2. Main content — responsive data tables with aggregate metric cards
  3. Right detail sheet — slide-out panel with issue metadata, workflow timeline, and agent controls

Key capabilities:

HTTP API

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

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.

Development

Build and test

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.

Dashboard development

make dev-dashboard
make dev-landing

The 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.

Running from source

go run ./cmd/contrabass --config testdata/workflow.demo.md --port 8080

Docs and fixtures

Charm stack

Direct dependencies from the Charm v2 ecosystem:

Logo Library Import Path Purpose
   Bubble Tea Bubble Tea charm.land/bubbletea/v2 TUI framework (Elm architecture)
Lip Gloss Lip Gloss charm.land/lipgloss/v2 Styling & layout
Bubbles Bubbles charm.land/bubbles/v2 Reusable TUI components
Log Log github.com/charmbracelet/log Structured logging
X x github.com/charmbracelet/x x/mosaic for terminal image rendering

Plus:

Releasing

CI and release workflows run automatically via GitHub Actions:

To ship a new release:

git tag v0.4.1
git push origin v0.4.1

This 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.

Notes for contributors

For detailed contribution guidelines, see CONTRIBUTING.md.