Sketch of a Claude Code port of openai/symphony — a daemon-style orchestrator that polls an issue tracker, dispatches isolated worker agents per issue, and reconciles state across ticks. Tracker-agnostic by design: the project's CLAUDE.md documents how to talk to whatever tracker is in use (GitHub Issues, Gitea, tracker CLI, Linear, etc.); the plugin reads those instructions rather than shipping per-tracker adapters. Includes: - /symphony-init for first-time setup (detects gh/gitea/tracker, writes the tracker section into project CLAUDE.md, drops WORKFLOW.md, offers to schedule the tick) - /symphony-tick fired by /schedule cron entries (idempotent, silent, never prompts) - per-issue worker agent in isolated worktrees - on-disk state in .symphony/state.json (survives cron cold starts) Known divergences from the spec are documented in the plugin CLAUDE.md (no streaming agent telemetry; no codex.* knobs; hooks run inside the worker rather than the orchestrator). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5.5 KiB
symphony
Claude Code port of the 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. LoadsWORKFLOW.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 contractsymphony-initcopies into the repo.templates/state.schema.json— shape of the on-disk orchestrator state file.
User flow
- First time:
/symphony-initin the project root. Interview + write. - Steady state:
/symphony-tickis fired by a/schedulecron entry (also set up by init unless the user declines). - 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:
- List active issues — return id, identifier, title, description, state, labels, priority, branch_name, url, updated_at. Filtered by a list of "active" state names.
- Get one issue's current state — for reconciliation.
- Transition an issue's state — used by the worker, not the orchestrator.
- Comment on an issue — used by the worker.
Concretely, a project CLAUDE.md section looks like:
## 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 <number> --json state,title,body,labels`
- Transition: `gh issue edit <number> --add-label "in-review"` (we use labels, not state)
- Comment: `gh issue comment <number> --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/<issue>-<ts>.log |
Known divergences from the spec
- Tracker is open, not Linear-only.
tracker.kindis a free string; the project CLAUDE.md provides the integration. No built-in Linear GraphQL client. - No streaming agent telemetry.
Agentreturns one final message; per-turn token counts andlast_codex_eventare not surfaced. - No
codex.*knobs. Sandbox/approval policies are Codex-runtime concepts; Claude Code applies its own permission model. before_run/after_runhooks run inside the worker, not the orchestrator. Failure handling matches the spec (before_run aborts the attempt; after_run logs).