symphony-beads
v0.1.7
Published
Symphony implementation for Beads issue tracker
Readme
symphony-beads
Autonomous coding orchestrator for Beads issues.
It polls issues, creates per-issue workspaces, runs the configured coding runner (default: pi) to implement work, opens PRs, and reacts to PR review outcomes.
Built on Beads and Bun, with pi as the default runner. Based on the Symphony spec.
Table of contents
- Requirements
- Install
- Quick start
- First-run checklist
- Daily operator workflow
- Creating tickets via agents
- CLI reference
- Kanban
- Runtime isolation and instance IDs
- WORKFLOW.md configuration
- Model routing and per-ticket model selection
- Issue lifecycle and backlog
- Validate and doctor
- Troubleshooting
- JSON output quick reference
- Running multiple projects
- Contributing and quality gates
Requirements
- Bun >= 1.0
- Beads (
bdCLI) - Dolt (required by Beads)
- pi (default runner used by
symphony init) - A coding runner available in PATH for your configured
runner.command(you can replacepi) - gh (for PR creation/monitoring)
- git
Install
Global install (Bun)
bun add -g symphony-beadsFrom source
git clone https://github.com/ahkohd/symphony-beads.git
cd symphony-beads
bun install
bun link # installs the `symphony` command globallyQuick start
cd your-project
# Ask your coding agent to initialize Beads in this repo
# Option A: export full clone URL
export REPO_URL="https://github.com/owner/repo.git"
# Option B: set workspace.repo in WORKFLOW.md (owner/repo)
symphony init
symphony validate --strict
symphony doctor
# Ask your coding agent to create the first ticket in Beads
symphony start
symphony status
symphony logs -fFirst-run checklist
Before first start, confirm:
- Clone source is configured
REPO_URLenv var, orworkspace.repoinWORKFLOW.md.
gh auth statussucceeds.workspace.rootis unique for this project (not overlapping another running instance root).symphony validate --strictpasses.symphony doctoris healthy.
Daily operator workflow
# Start (daemon by default)
symphony start
# Observe
symphony status
symphony instances
symphony logs -f
# Stop one instance
symphony stop --id <instance-id-or-unique-prefix>
# Stop all
symphony stop --allCreating tickets via agents
Beads operations are agent-facing in this workflow. Humans are expected to ask their coding agent to create and update tickets.
Example requests to your agent:
- "Create a P1 bug ticket in Beads: Fix flaky CI test suite. Description: Intermittent failure in parser tests."
- "Create a feature ticket for Migrate auth flow and set metadata model=claude-opus-4-6."
- "Create a ticket for docs cleanup and set it to deferred (backlog)."
- "For issue bd-42, set metadata model=claude-opus-4-6."
- "For issue bd-42, clear metadata model."
When model metadata is present, Symphony uses it as the highest-priority model routing signal (see model routing section below).
CLI reference
symphony <command> [flags]
Commands:
start Start the orchestrator (daemonizes by default)
status Show current issue status from beads
validate Validate WORKFLOW.md configuration
init Create a new WORKFLOW.md
instances List all running symphony instances
doctor Verify dependencies, config, and runtime state
logs Tail the symphony log file
stop Stop a running symphony instance
kanban Interactive kanban board
Flags:
--json Output as JSON
--workflow PATH Workflow file (default: WORKFLOW.md)
--verbose Verbose output
-h, --help Show help
-v, --version Show version
Start flags:
-f, --foreground Run in foreground
Logs flags:
-f, --follow Follow the log file
-n, --lines N Number of lines to show (default: 50)
Stop flags:
--all Stop all registered symphony instances
--id ID Stop a specific instance by ID or unique ID prefix
Validate flags:
--strict Treat warnings as errors
Doctor flags:
--fix Apply safe automatic repairs before checks
--dry-run Preview fixes without applying changes (requires --fix)Kanban
Launch the TUI board:
symphony kanbanKanban is an operator view for triage and monitoring. Ticket creation remains agent-driven (n shows guidance to ask your coding agent).
j/kor arrows: move selectiong/G: jump to top/bottom in active columnm/M: move status forward/backwardb/B: defer/promote backlogr: refreshq: quit
Kanban screenshot:
Runtime isolation and instance IDs
Isolation model
- Per project:
.symphony.lockprevents duplicate local starts. - Global:
~/.symphony/instances/tracks live instances. - Workspace collision guard: overlapping roots are blocked (exact match and parent/child overlap).
Example overlap (invalid):
- Instance A:
/tmp/symphony - Instance B:
/tmp/symphony/project-b
Deterministic instance IDs
Instance IDs are deterministic from absolute project path.
start --json: top-levelinstance_idstatus --json:service.instance_idinstances --json:instances[].id
Prefix behavior for stop --id:
- exact ID match wins
- otherwise unique prefix works
- ambiguous prefix fails with
instance_id_ambiguous
WORKFLOW.md configuration
WORKFLOW.md has YAML front-matter and prompt body.
Front-matter example (aligned with symphony init defaults)
---
tracker:
kind: beads
project_path: "."
workspace:
root: ./workspaces
repo: $SYMPHONY_REPO
remote: origin
agent:
max_concurrent: 5
max_turns: 20
runner:
command: pi --no-session
turn_timeout_ms: 3600000
stall_timeout_ms: 300000
polling:
interval_ms: 30000
hooks:
after_create: |
set -e
if [ -n "$REPO_URL" ]; then
git clone "$REPO_URL" .
elif [ -n "$SYMPHONY_REPO" ] && [ "$SYMPHONY_REPO" != '$SYMPHONY_REPO' ]; then
if command -v gh >/dev/null 2>&1; then
gh repo clone "$SYMPHONY_REPO" .
else
git clone "https://github.com/$SYMPHONY_REPO.git" .
fi
else
echo "No repository source configured. Set REPO_URL or workspace.repo." >&2
exit 1
fi
bun install 2>/dev/null || npm install 2>/dev/null || true
before_run: |
DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/$SYMPHONY_REMOTE/HEAD 2>/dev/null | sed "s|refs/remotes/$SYMPHONY_REMOTE/||" || echo "master")
git fetch $SYMPHONY_REMOTE $DEFAULT_BRANCH 2>/dev/null || true
git fetch $SYMPHONY_REMOTE issue/$SYMPHONY_ISSUE_ID 2>/dev/null || true
if git rev-parse --verify $SYMPHONY_REMOTE/issue/$SYMPHONY_ISSUE_ID >/dev/null 2>&1; then
git checkout -B issue/$SYMPHONY_ISSUE_ID $SYMPHONY_REMOTE/issue/$SYMPHONY_ISSUE_ID
else
git checkout -B issue/$SYMPHONY_ISSUE_ID $SYMPHONY_REMOTE/$DEFAULT_BRANCH
fi
git clean -fd 2>/dev/null || true
log:
file: ./symphony.log
---Hook environment variables
| Variable | Description |
|---|---|
| SYMPHONY_ISSUE_ID | Current issue identifier |
| SYMPHONY_PROJECT_PATH | Absolute project path |
| SYMPHONY_REMOTE | workspace.remote value |
| SYMPHONY_REPO | workspace.repo value (owner/repo) |
| REPO_URL | Optional full clone URL |
Prompt template variables
| Variable | Description |
|---|---|
| {{ issue.identifier }} | Issue ID |
| {{ issue.title }} | Title |
| {{ issue.description }} | Description |
| {{ issue.priority }} | Priority |
| {{ issue.labels }} | Comma-separated labels |
| {{ issue.state }} | Current state |
| {{ attempt }} | Retry attempt |
| {{ review_feedback }} | PR review feedback on rework |
Model routing and per-ticket model selection
Symphony supports per-ticket model routing.
Resolution order (highest first):
issue.metadata.modelrunner.models.<issue_type>(for examplebug,feature,chore)runner.models.P0..P4runner.models.default
To enable dynamic model selection, ensure your runner command consumes $SYMPHONY_MODEL:
runner:
command: pi --no-session --model $SYMPHONY_MODEL
models:
default: claude-sonnet-4-5-20250929
P0: claude-opus-4-6
bug: claude-opus-4-6
chore: claude-haiku-4-5-20251001Notes:
- For non-
pirunners, use an equivalent command that accepts a model argument (still via$SYMPHONY_MODEL). - Legacy fixed model is still supported via
runner.modelwhenrunner.modelsis not set.
To set a per-issue model override, ask your agent to update issue metadata in Beads (for example: set model=claude-opus-4-6 on bd-42).
Issue lifecycle and backlog
open/in_progress -> review -> closed
^ |
| | changes requested
+----------+
open <-> deferred (backlog)| State | Orchestrator behavior |
|---|---|
| open, in_progress | dispatch / keep running |
| review, blocked, deferred | do not dispatch; running agent is stopped |
| closed, cancelled, duplicate | terminal; workspace cleaned |
Backlog helpers (via agent requests):
- "Move bd-42 to deferred (backlog)."
- "Move bd-42 back to open."
Kanban backlog shortcuts:
b: move selected issue to backlog (deferred)B: promote selected issue from backlog (open)
Validate and doctor
symphony validate
- validates known config semantics
- warns on unknown sections/keys (typo detection)
- warns when clone bootstrap likely has no source (
workspace.repoandREPO_URLmissing) --strictturns warnings into non-zero exit (CI mode)
symphony doctor
Checks dependencies + runtime health, including workspace overlap risk.
When overlap is detected, doctor includes actionable hints such as:
symphony instancessymphony stop --id <instance-id>
symphony doctor --fix
Safe repairs before checks:
- remove stale project lock
- ensure workspace root exists
- prune stale/invalid global registry entries
Use preview mode first:
symphony doctor --fix --dry-runTroubleshooting
No repository source configured
No repository source configured. Set REPO_URL or workspace.repo.
Configure one of:
export REPO_URL=...workspace.repo: owner/repo
Then run:
symphony validate --strictWorkspace overlap failure
Run:
symphony instances
symphony stop --id <conflicting-id>Then set distinct workspace.root paths per project.
gh auth failures
gh auth login
symphony doctorBeads DB missing
Ask your coding agent to initialize Beads, then re-run:
symphony doctorLogs JSON
logs --json and logs --follow are mutually exclusive.
JSON output quick reference
start --json: includesinstance_id,pid,log_filestatus --json: includesservice,issues,by_stateinstances --json: includesinstances[]withid,pid,workspace_rootstop --json: structured stop result; errors include typed codes (instance_not_found,instance_id_ambiguous,stop_flag_conflict)validate --json:valid,errors,warnings,strictdoctor --json:checks[](fixsection when using--fix)
Running multiple projects
cd ~/projects/project-a && symphony start
cd ~/projects/project-b && symphony start
symphony instances
symphony stop --id <instance-id>Use non-overlapping workspace.root values across projects.
