# symphony Claude Code port of the [openai/symphony](https://github.com/openai/symphony) orchestrator spec. A long-running, repo-owned automation that polls **the project's issue tracker**, spawns isolated worker agents for eligible issues, and reconciles state across ticks. The execution layer is Claude Code's `Agent` tool instead of `codex app-server`. Everything above that — workflow contract, polling, workspaces, retries, hooks — maps to existing primitives. **Tracker-agnostic by design.** Symphony's spec hardcoded Linear; this port does not. The plugin only knows the *shape* of an issue (id, title, state, etc.) and asks the consuming repo to provide the integration. See "Tracker contract" below. ## Layout - `commands/symphony-init.md` — one-time interactive setup. Detects tracker, writes CLAUDE.md tracker section, drops WORKFLOW.md, optionally schedules the tick. - `commands/symphony-tick.md` — slash command fired by `/schedule`. One tick = one pass. Silent and non-interactive; safe to fire on a cron. - `skills/symphony-init/SKILL.md` — the init/interview logic. - `skills/symphony/SKILL.md` — the dispatcher. Loads `WORKFLOW.md`, queries the tracker via the project's integration, decides what to start/stop/retry, writes state. - `agents/symphony-worker.md` — per-issue executor. Owns one issue, runs in an isolated worktree, hands off when done. - `templates/WORKFLOW.md` — workflow contract `symphony-init` copies into the repo. - `templates/state.schema.json` — shape of the on-disk orchestrator state file. ## User flow 1. **First time**: `/symphony-init` in the project root. Interview + write. 2. **Steady state**: `/symphony-tick` is fired by a `/schedule` cron entry (also set up by init unless the user declines). 3. **Re-config**: re-run `/symphony-init`; it detects drift and proposes updates. ## Tracker contract The plugin treats trackers as a capability the *project* provides, not something Symphony adapts to. To use the plugin, the consuming repo's `CLAUDE.md` MUST document, for the tracker named in `WORKFLOW.md`'s `tracker.kind`, how to: 1. **List active issues** — return id, identifier, title, description, state, labels, priority, branch_name, url, updated_at. Filtered by a list of "active" state names. 2. **Get one issue's current state** — for reconciliation. 3. **Transition an issue's state** — used by the worker, not the orchestrator. 4. **Comment on an issue** — used by the worker. Concretely, a project CLAUDE.md section looks like: ```markdown ## Issue tracker This project uses GitHub Issues. Symphony workers should: - List: `gh issue list --state open --json number,title,body,labels,state,updatedAt` - Get: `gh issue view --json state,title,body,labels` - Transition: `gh issue edit --add-label "in-review"` (we use labels, not state) - Comment: `gh issue comment --body "..."` Active label set: `ready`, `in-progress`. Terminal: closed issues. ``` …or for Gitea (`tracker.kind: gitea`), point at the gitea MCP. For the `tracker` CLI (`tracker.kind: tracker`), point at the `tracker-usage` skill. The skill and worker read these instructions when they need to talk to the tracker. If the project CLAUDE.md is silent on the tracker, the orchestrator errors with `tracker_integration_missing` and exits the tick. ## State Symphony's spec calls for in-memory orchestrator state. Cron-fired ticks are cold starts, so we persist to `.symphony/state.json` in the consuming repo: - `running` — issue_id → { worker_task_id, started_at, worktree_path } - `retry_attempts` — issue_id → { attempt, due_at_ms, last_error } - `claimed` — set of issue IDs reserved this tick (cleared on dispatch or release) Reconciliation reads the file at the top of every tick, queries `TaskList` to confirm which workers are actually still running, and rewrites the file before returning. ## Mapping to the spec | Symphony component | Claude Code mechanism | |---------------------------|--------------------------------------------------------| | Polling daemon | `/schedule` cron entry firing `/symphony-tick` | | Workflow loader | Skill body — reads & parses `WORKFLOW.md` | | Issue tracker client | Project CLAUDE.md instructions + whatever tools fit | | Orchestrator | Skill, invoked once per tick; state on disk | | Workspace manager | `EnterWorktree` per worker | | Agent runner | `Agent` tool with `subagent_type: symphony-worker` | | Hooks | Bash steps inside the worker prompt | | Concurrency cap | Lockfile + `TaskList` poll inside the dispatcher | | Retries with backoff | `retry_attempts` entries, due_at compared each tick | | Logging | `.symphony/logs/-.log` | ## Known divergences from the spec - **Tracker is open, not Linear-only.** `tracker.kind` is a free string; the project CLAUDE.md provides the integration. No built-in Linear GraphQL client. - **No streaming agent telemetry.** `Agent` returns one final message; per-turn token counts and `last_codex_event` are not surfaced. - **No `codex.*` knobs.** Sandbox/approval policies are Codex-runtime concepts; Claude Code applies its own permission model. - **`before_run` / `after_run` hooks run inside the worker**, not the orchestrator. Failure handling matches the spec (before_run aborts the attempt; after_run logs).