Contrabass

repository·main·Indexed 41 days ago

https://github.com/junhoyeo/contrabass

A terminal-first, project-level orchestrator for AI coding agents that manages the lifecycle of issue-driven agent runs. It provides coordination, workspace provisioning, and visibility via a TUI or the Ziikoo web dashboard. Contrabass supports trackers like Linear and GitHub Issues, and integrates with agent runners including Codex, OpenCode, OMX (oh-my-codex), and OMC (oh-my-claudecode).

Tokens
27.9K
Snippets
85
Records
131
Agent score
53%

What's inside Contrabass

  1. Use environment variables in configuration

    main

    Contrabass supports environment variable expansion within the YAML front matter. A string that consists entirely of $VARIABLE will be replaced by the corresponding environment variable value. This expansion works for nested structs, lists, and map values.

    Note: Environment variable expansion is not applied to the Markdown prompt body.

    tracker:
      token: $GITHUB_TOKEN
    oh_my_opencode:
      plugins:
        - $OH_MY_OPENCODE_PLUGIN
      agents:
        reviewer:
          model: $REVIEW_MODEL
  2. Tool Call Handling in internal/agent

    main

    The internal/agent package manages various types of tool interactions:

    • Dynamic Tool Calls: The server supports executing supported dynamic tool calls and returning results. It also rejects unsupported dynamic tool calls without stalling the process.
    • Tool Input Prompts: For both freeform and option-based tool input prompts, the app server can be configured to send generic non-interactive answers.
    • Failure Reporting: When a supported tool fails, the app server emits a tool_call_failed event.
  3. Use workspace hooks for automation in internal/workspace

    main

    The internal/workspace package supports lifecycle hooks to automate tasks during workspace operations:

    • after_create hook: Can be used to implement workspace bootstrapping. If the hook fails or times out, the workspace surfaces these errors. If no hook is configured, an empty directory is created.
    • before_remove hook: Runs before a workspace is deleted. If this hook fails (even with large output), the remove operation is designed to continue regardless.
    • Hook Capabilities: Hooks support multiline YAML scripts and are executed at specific lifecycle boundaries.
  4. Understand Orchestrator polling and dispatch logic

    main

    The orchestrator uses a polling mechanism to manage tasks:

    • Immediate Polling: The orchestrator is designed to trigger an immediate poll cycle shortly after startup.
    • Poll Cycles: A poll cycle includes a countdown to the next refresh; this countdown resets after a check is performed.
    • Dispatch Ordering: When dispatching tasks, the orchestrator sorts them by priority, and then by the oldest created_at timestamp.
  5. How contrabass ensures isolation from omx teams

    main

    Contrabass provides logical isolation between different omx teams, even if they are running on the same machine. It achieves this through three primary mechanisms:

    1. Per-issue git worktrees: Every issue gets a unique worktree in workspaces/<issue-id>/. omx workers create nested subdirectories under <workspace>/.omx/team/<team-name>/worktrees/worker-N/, ensuring no two issues share a working tree.
    2. Unique team names: Contrabass captures the exact team name printed by omx team (which includes a unique hash suffix, e.g., cb-3-add-godoc-commen-82cddf69) and uses this name for all subsequent omx team api or omx team shutdown calls. This prevents contrabass from accidentally interacting with manually started omx teams.
    3. Self-healing handoff: Contrabass does not attempt to manage the lifecycle of omx workers (like RestartDeadWorkers). It allows omx's own supervisor to handle worker health, preventing contrabass from interfering with healthy omx turns.
  6. Understand the Codex App-Server Wire Protocol

    main

    The Codex app-server uses a "JSON-RPC lite" protocol over stdio (standard input/output).

    Key Protocol Characteristics:

    • Framing: The protocol uses newline-delimited JSON (NDJSON). Each individual JSON message is contained on a single line. You must buffer incoming data until a newline (\n) is encountered before attempting to decode the JSON.
    • JSON-RPC Variant: It is a "lite" version that omits the standard "jsonrpc": "2.0" field from the wire objects.
    • Transport: The default transport is stdio (invoked via --listen stdio://).
    • Message Types: The protocol supports four main object shapes:
      1. Request: Includes an id and expects a response.
      2. Response: A successful result matching a request id.
      3. Error response: An error object matching a request id.
      4. Notification: A message with a method but no id (no response expected).

    Implementation Note

    Do not use Content-Length framing or LSP headers. Use line-based reading (e.g., BufReader::lines() in Rust or line-mode ports in Elixir).

    // Example Request
    {"id":1,"method":"initialize","params":{"clientInfo":{"name":"client","version":"1.0"}}}
    
    // Example Response
    {"id":1,"result":{"userAgent":"..."}}
    
    // Example Error
    {"id":7,"error":{"code":-32001,"message":"Server overloaded; retry later."}}
    
    // Example Notification
    {"method":"turn/completed"}
  7. Prompt Building and Template Rendering in internal/agent

    main

    The prompt builder within internal/agent is responsible for constructing LLM prompts using workflow templates. Key behaviors include:

    • Variable Rendering: It renders values (such as issue and attempt) from the workflow template. It uses strict variable rendering, meaning it will raise an explicit error for malformed templates or missing prerequisites.
    • Data Normalization: The builder normalizes nested date-like values, maps, and structs found within issue fields to ensure compatibility.
    • Default Templates: If the workflow prompt is blank, the builder uses a sensible default template. This default template is designed to handle cases where the issue body might be missing.
    • Error Reporting: The system distinguishes between workflow load failures and template parse errors, reporting them separately to aid debugging.
    • Retry Guidance: The prompt builder automatically adds continuation guidance to prompts when a retry is triggered.
  8. Understanding Team worker modes

    main

    When running teams, you can choose between two worker modes via team.worker_mode in the workflow file or the --worker-mode CLI flag:

    tmux mode (Default)

    Provides process isolation by running each worker in a separate tmux pane. It uses cross-process IPC via JSONL events, file-based heartbeats, and advisory file locking (flock(2)) for safe concurrency. This mode is recommended for robust crash recovery and governance.

    goroutine mode

    Runs all workers as goroutines within the single Contrabass process using Go's errgroup and sync.Mutex. It is lighter weight and has no external dependencies (like tmux), but workers share the same process address space.

  9. Workspace lifecycle and state management in internal/workspace

    main

    The internal/workspace package manages issue-specific directories and their lifecycles. Key behaviors include:

    • Issue State Transitions:
      • When an issue enters a non-active state, the agent stops running, but the workspace is not cleaned to prevent unintended state transitions.
      • When an issue enters a terminal state, the agent stops running and the workspace is cleaned.
      • For a closed issue identifier, the system removes all associated workspaces.
    • Directory Management:
      • Workspace paths are deterministic based on the issue identifier.
      • The system reuses existing issue directories without deleting local changes.
      • If a stale path exists that is not a directory, the workspace replaces it.
      • The system rejects symlink escapes that attempt to go outside the configured root.
      • Removing the workspace root itself is rejected with a specific error.
      • If a directory is missing during a remove operation, the system returns error information regarding the missing directory.
  10. How Team Mode state is managed

    main

    Team mode state is persisted using a file-based storage system. This allows for tracking activity and state across restarts. The following file types are used:

    • Heartbeat Files: Track worker liveness and activity.
    • Task Files: Store task definitions and their current status.
    • Event Log: An append-only stream of all team activity events.
    • Mailbox Files: Per-worker queues for inter-worker messaging.
    • Config Files: Store team and worker configurations.
  11. Configure polling backoff strategies

    main

    The polling.backoff_strategy determines how the system handles retries.

    • exponential: Uses the backoff.* settings to grow delays. The multiplier is used to grow failure_base_ms by the number of attempts.
    • linear: Grows failure_base_ms by the number of attempts.

    Unknown values for backoff_strategy will fall back to exponential.

    polling:
      backoff_strategy: exponential
    
    backoff:
      continuation_ms: 1000
      failure_base_ms: 10000
      multiplier: 2
      jitter_percent: 10
  12. Agent Runner Lifecycle and Configuration

    main

    The agent runner manages the execution flow of the agent within a workspace:

    • Workspace Persistence: Upon a successful Codex run, the agent runner maintains the workspace state.
    • Turn Limits: The runner enforces a maximum number of turns. Once agent.max_turns is reached, the runner stops continuing the execution.
    • Startup Configuration: The app server starts using the workspace's current working directory (CWD) and an expected startup command. This startup command can support Codex argument overrides defined in the workflow configuration.
    • Sandbox and Approval Settings: The startup payload incorporates configurable approval and sandbox settings derived from the workflow configuration.