@ghostlygawd/symphony
v0.1.0
Published
Claude Code-native orchestration: turn an issue tracker into autonomous, isolated implementation runs. Runs on a Claude subscription — no API key required.
Downloads
43
Maintainers
Readme
Symphony
Claude Code-native orchestration — turn an issue board into autonomous, isolated implementation runs.
Symphony watches an issue tracker, gives every open task its own isolated workspace and a Claude Code agent, lets those agents work autonomously, and surfaces the results for you to review. You manage work, not coding agents.
It runs on your Claude subscription through the claude CLI you already use. No ANTHROPIC_API_KEY required.
This is an independent, Claude-native implementation of the open-source Symphony spec (originally published by OpenAI for Codex, Apache-2.0). It is not affiliated with OpenAI or Anthropic. See NOTICE.
Why
The original Symphony is an orchestration spec: Linear is the state machine, the coding agent is the worker, PRs are the output. Symphony polls the board, spins up an isolated run per ticket, restarts agents that crash, and keeps going until the work leaves the active states.
This implementation keeps that model but is built for Claude Code:
- 🎫 Subscription-native. It drives the same
claudeCLI you use interactively, so it authenticates with your Pro/Max subscription. The init event literally reportsapiKeySource: "none". No API keys, no separate billing. - 📦 Ships and runs out of the box. Zero runtime dependencies.
npx-able. A built-in local file tracker means you can go from clone to a running orchestrator in under a minute with nothing but Claude Code installed. - 🧩 Faithful to the spec. Workflow loader (
WORKFLOW.md), polling orchestrator with bounded concurrency, per-issue workspaces with lifecycle hooks, exponential-backoff retries, stall detection, tracker-state reconciliation, dynamic reload, and an optional status dashboard. - 🔌 Pluggable trackers. Local Markdown board (default), GitHub Issues, or Linear.
Quickstart
Prerequisites: Node.js ≥ 18 and Claude Code installed and logged in (claude auth login).
Once published, the fastest path is npx — no clone, no install:
# 1. Scaffold a board + WORKFLOW.md in your project
cd /path/to/your/project
npx @ghostlygawd/symphony init
# 2. Check your environment (Claude Code present? logged in? config valid?)
npx @ghostlygawd/symphony doctor
# 3. Run it — opens a live dashboard at http://127.0.0.1:4173
npx @ghostlygawd/symphony start --port 4173Prefer to run from a clone of this repo:
# 1. Get Symphony (from this repo)
git clone https://github.com/ghostlygawd/symphony-clone.git
cd symphony-clone
npm link # makes the `symphony` command available (optional)
# 2. Scaffold a board + WORKFLOW.md in your project
cd /path/to/your/project
symphony init
# 3. Check your environment (Claude Code present? logged in? config valid?)
symphony doctor
# 4. Run it — opens a live dashboard at http://127.0.0.1:4173
symphony start --port 4173No global install? Run it directly:
node /path/to/symphony-clone/bin/symphony.js start --port 4173That's it. Symphony will pick up every Todo issue from .symphony/issues/, give each its own workspace and a Claude Code agent, and run them in parallel. Edit an issue's state: (or let the agent do it) to move it through your workflow.
See it work in one shot
symphony start --once runs a single tick, lets the dispatched agents finish, and prints a snapshot — perfect for trying it out or for CI/cron:
symphony start --onceThis is exactly the flow used to verify Symphony end-to-end: a real Claude Code agent picked up SYM-1, created WELCOME.md in its isolated workspace, transitioned the issue to In Review, and the orchestrator recorded the token usage and cost — all on a subscription with no API key.
How it works
WORKFLOW.md (YAML config + prompt template)
│ (hot-reloaded on change)
▼
┌──────────────┐ ┌───────────────────────────────────────────────┐
│ Issue tracker │◀────│ Orchestrator │
│ local/github/ │ │ poll → reconcile → retry queue → dispatch │
│ linear │────▶│ (bounded concurrency, stall detection) │
└──────────────┘ └───────────────┬───────────────────────────────┘
▲ state │ one worker per issue
│ transitions ▼
│ ┌──────────────────────┐
│ │ Workspace Manager │ per-issue dir +
│ │ after_create/before_ │ lifecycle hooks
│ │ run/after_run hooks │
│ └──────────┬────────────┘
│ ▼
│ ┌──────────────────────┐ claude -p
└───── agent edits ─────│ Claude Code agent │ --output-format
board / opens PR │ (subscription auth) │ stream-json
└──────────────────────┘Each tick the orchestrator:
- Reloads
WORKFLOW.mdif it changed on disk (no restart needed). - Reconciles running workers: stall detection + refresh each issue's tracker state (terminal → stop & clean up the workspace).
- Processes the retry queue: due continuations (1s after a clean finish) and exponential-backoff retries after failures.
- Validates config; if invalid it keeps reconciling but pauses dispatch and surfaces the error.
- Fetches candidates and dispatches eligible issues within the concurrency limits.
A worker, in turn, creates the workspace, runs before_run, then runs Claude Code turn-by-turn (resuming the same session for continuations) — re-checking the tracker between turns — until the issue leaves the active states or max_turns is hit.
Handoff, not "Done". A successful run typically ends at a review state (e.g.
In Review), notDone. The agent does the work and hands it back to a human. Symphony stops working an issue the moment it leaves the active states.
Configuration: WORKFLOW.md
A single Markdown file with YAML front matter (the config) and a Markdown body (the prompt template). The full default is created by symphony init; here's the shape:
---
tracker:
kind: local # local | github | linear
dir: .symphony/issues
active_states: [Todo, "In Progress"]
terminal_states: [Done, Cancelled]
polling:
interval_ms: 10000
workspace:
root: .symphony/workspaces
hooks:
after_create: | # runs once when a workspace is created
[ -n "$SYMPHONY_PROJECT_DIR" ] && cp -R "$SYMPHONY_PROJECT_DIR"/. .
before_run: | # runs before each agent attempt
npm install
timeout_ms: 120000
agent:
max_concurrent_agents: 3
max_turns: 8
permission_mode: bypassPermissions # full autonomy (sandboxed envs only)
claude:
command: claude
# model: sonnet
stall_timeout_ms: 300000
---
You are an autonomous engineer working on {{ issue.identifier }}: {{ issue.title }}.
{{ issue.description }}
{% if attempt %}This is a continuation (turn {{ attempt }}).{% endif %}
When finished, edit `{{ issue_file }}` and set its `state:` to `In Review`, then stop.The prompt template supports {{ variable }}, {% if %}/{% elsif %}/{% else %}, {% for x in list %}, and filters (default, upcase, downcase, join, length, truncate). Available variables: issue (all fields + labels/blocked_by), attempt, turn, max_turns, issue_file, board_dir, project_dir.
Run symphony validate to check it. See docs/configuration.md for every setting and default.
Context your hooks and agents get
Every hook and agent run is given these environment variables: SYMPHONY_ISSUE_ID, SYMPHONY_ISSUE_IDENTIFIER, SYMPHONY_ISSUE_TITLE, SYMPHONY_ISSUE_STATE, SYMPHONY_ISSUE_FILE, SYMPHONY_BOARD_DIR, SYMPHONY_PROJECT_DIR, SYMPHONY_WORKSPACE.
Trackers
| Tracker | State lives in | Auth | Out of the box? |
|----------|----------------|------|-----------------|
| local (default) | Markdown files in .symphony/issues/ | none | ✅ yes |
| github | GitHub issue + a status:<state> label | GITHUB_TOKEN | needs a token |
| linear | Linear workflow states | LINEAR_API_KEY | needs a key |
With the local tracker, each issue is a Markdown file with front matter (identifier, title, state, priority, labels, blocked_by) and a Markdown body (the description). State transitions are just edits to state: — which the agent performs as part of its run. Fully local, no external service. See docs/trackers.md.
All three trackers can transition an issue, not just read it: the agent (or symphony move) swaps a GitHub status:<state> label and closes/reopens the issue, or moves a Linear workflow state. For a real-repo clone → branch → PR workflow driven by GitHub Issues, see the worked example in examples/github-pr/.
Authentication: subscription, not API key
Symphony never asks for an API key. It launches claude exactly as you would interactively, so it uses whatever the CLI is logged in with:
- Recommended: a Claude Pro/Max subscription via
claude auth login(orclaude setup-token). - If
ANTHROPIC_API_KEYhappens to be set in your environment, Claude Code will use it (API billing). To force subscription usage, unset it — or setclaude.unset_api_key: trueinWORKFLOW.mdand Symphony will drop it for the agent processes.
symphony doctor tells you which mode you're in. More in docs/claude-code.md.
Safety & the trust boundary
Autonomous agents that edit files and run commands are powerful — and a footgun outside a sandbox. Symphony makes the trust boundary explicit:
permission_mode: acceptEdits(the code default) auto-accepts file edits but limits command execution.permission_mode: bypassPermissionsgives the agent full autonomy (the default in the scaffoldedWORKFLOW.md). Use it only in isolated/sandboxed environments — containers, CI runners, VMs, or throwaway machines. When Symphony runs as root in a container it automatically setsIS_SANDBOX=1so this works.- Workspaces are always per-issue, name-sanitized, and confined under
workspace.root; the agent's working directory is validated before launch. - Secrets use
$VARindirection and are never logged.
Recommended hardening: run Symphony in a container with scoped credentials and a restricted network, filter which labels/projects are eligible, and keep max_concurrent_agents sane for your budget.
Run in a container (sandboxed)
The repo ships a Dockerfile and docker-compose.yml so you get that sandbox out of the box — Node 22, the Claude Code CLI, and Symphony, with IS_SANDBOX=1 set so bypassPermissions is honored safely inside the container:
# Mounts the current repo into /work and your Claude login (read-only)
docker compose up # dashboard at http://127.0.0.1:4173Use your Claude subscription (mount ~/.claude read-only or claude setup-token); keep ANTHROPIC_API_KEY unset. Full guide — credentials and network-egress posture — in docs/sandbox.md.
Commands
symphony init [dir] Scaffold WORKFLOW.md + an example local board
symphony start Run the orchestrator (poll the tracker, dispatch agents)
--port <n> Serve the live dashboard
--host <addr> Dashboard bind address (default 127.0.0.1; non-loopback requires a token)
--once Run a single tick, wait for workers, print a snapshot, exit
--verbose Debug logging
symphony status Show a runtime snapshot (from a running server, or one-shot)
symphony validate Load and validate WORKFLOW.md
symphony doctor Check your environment (Claude Code, auth, config)
symphony move <id> <st> Transition an issue's state — local, github, or linear (handoff helper)
symphony version | help
--workflow <path> Use a specific WORKFLOW.md (default ./WORKFLOW.md)Watch it from your phone. Set
SYMPHONY_DASHBOARD_TOKEN(orserver.auth_tokeninWORKFLOW.md) to put a login on the dashboard, then expose127.0.0.1:4173through a tunnel (ngrok / Cloudflare / Tailscale) and openhttps://<tunnel-url>/?token=YOUR_TOKEN. Symphony refuses to bind a non-loopback address without a token. Full recipe: docs/remote-access.md.
How this maps to OpenAI's Symphony
| OpenAI Symphony | This project |
|---|---|
| Coding agent: Codex app-server subprocess | Claude Code: claude -p --output-format stream-json |
| Auth: Codex / API | Claude subscription via the CLI (no API key) |
| Reference impl: Elixir/BEAM | Node.js (zero deps), tested with node --test |
| Tracker: Linear | local (default) · GitHub · Linear |
| linear_graphql client-side tool | agent edits the board via an auto --add-dir, or symphony move |
| WORKFLOW.md, polling loop, concurrency, retries, stall detection, reconciliation, dynamic reload, snapshot API | ✅ all implemented |
See SPEC.md for the Claude-native specification.
Development
npm test # run the full suite (node --test), zero dependenciesThe suite is the gate. A git pre-push hook (.githooks/pre-push) runs node --test before every push and blocks on failure; npm install wires it automatically (or run git config core.hooksPath .githooks once). GitHub Actions (.github/workflows/test.yml) is manual-only (Actions-minutes), triggerable from the Actions tab.
The codebase is small and readable: src/yaml.js (mini YAML), src/workflow.js (config), src/template.js (prompt rendering), src/trackers/*, src/workspace.js, src/agent/claude.js (the Claude Code adapter), src/runner.js, src/orchestrator.js, src/server.js, src/cli.js.
Roadmap
The original follow-up backlog — GitHub & Linear state-transition write paths, a real-repo clone→branch→PR example, a publishable npx release, a sandboxed container image, and a gated live test (BL-1…BL-6) — is now implemented. See docs/BACKLOG.md for per-item status and the remaining nice-to-haves (durable runtime state, per-issue cost caps, webhook-driven polling, dashboard per-issue drill-down).
License
Apache-2.0. Not affiliated with OpenAI or Anthropic; see NOTICE.
