feat(symphony): tracker-agnostic orchestrator plugin
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>
2026-04-29 21:03:21 -05:00
|
|
|
# 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.
|
2026-04-29 21:51:23 -05:00
|
|
|
2. **Steady state**: `/symphony-tick` is fired by a `/schedule` entry inside the long-lived
|
|
|
|
|
orchestrator session (also set up by init unless the user declines).
|
|
|
|
|
3. **Other triggers**: ticks can also be fired by remote channels (Discord DM, webhook,
|
|
|
|
|
another session). The reconciliation logic handles cross-session liveness via worker
|
|
|
|
|
heartbeats.
|
|
|
|
|
4. **Re-config**: re-run `/symphony-init`; it detects drift and proposes updates.
|
feat(symphony): tracker-agnostic orchestrator plugin
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>
2026-04-29 21:03:21 -05:00
|
|
|
|
|
|
|
|
## 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 <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.
|
|
|
|
|
|
2026-04-29 21:51:23 -05:00
|
|
|
## Runtime model
|
feat(symphony): tracker-agnostic orchestrator plugin
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>
2026-04-29 21:03:21 -05:00
|
|
|
|
2026-04-29 21:51:23 -05:00
|
|
|
The orchestrator runs inside a **long-lived Claude Code session**. `/schedule` doesn't
|
|
|
|
|
cold-start each tick — it sets the prompt for the running session. Tasks spawned in
|
|
|
|
|
tick N stay visible to `TaskList` / `TaskGet` in tick N+1 within that session, so the
|
|
|
|
|
session itself is the primary store of "what's running right now."
|
feat(symphony): tracker-agnostic orchestrator plugin
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>
2026-04-29 21:03:21 -05:00
|
|
|
|
2026-04-29 21:51:23 -05:00
|
|
|
Symphony stays open to **other trigger sources** too: a Discord channel could DM the
|
|
|
|
|
session and ask for a tick, a webhook hook could trigger one, and a separate user
|
|
|
|
|
session could conceivably spawn tasks against the same project. To keep all three
|
|
|
|
|
modes honest, two layers carry state:
|
|
|
|
|
|
|
|
|
|
1. **`TaskList` / `TaskGet`** — fast path, authoritative for liveness *within this session*.
|
|
|
|
|
2. **`.symphony/state.json` + worker heartbeats** — durable record across sessions and
|
|
|
|
|
restarts. Authoritative when `TaskList` doesn't know about a task ID.
|
|
|
|
|
|
|
|
|
|
### Reconciliation logic
|
|
|
|
|
|
|
|
|
|
For each entry in `state.json`'s `running` map:
|
|
|
|
|
|
|
|
|
|
- **In our `TaskList`** → it's our task; check status via `TaskGet`; reconcile normally.
|
|
|
|
|
- **Not in our `TaskList`** → another session may own it, or the session that spawned it
|
|
|
|
|
exited. Read the worker's heartbeat file (`.symphony/heartbeats/<issue-id>.json`).
|
|
|
|
|
- **Heartbeat fresh** (within `agent.heartbeat_stale_ms`, default 5min) → leave alone;
|
|
|
|
|
another session is owning it.
|
|
|
|
|
- **Heartbeat stale** → consider abandoned; mark for retry, drop from `running`.
|
|
|
|
|
- **No heartbeat** → ditto.
|
|
|
|
|
|
|
|
|
|
This keeps the single-session case simple (TaskList wins) while supporting
|
|
|
|
|
multi-session and crash recovery cleanly.
|
|
|
|
|
|
|
|
|
|
### State file shape
|
|
|
|
|
|
|
|
|
|
- `running` — issue_id → { task_id, session_id, started_at, worktree_path }
|
feat(symphony): tracker-agnostic orchestrator plugin
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>
2026-04-29 21:03:21 -05:00
|
|
|
- `retry_attempts` — issue_id → { attempt, due_at_ms, last_error }
|
|
|
|
|
- `claimed` — set of issue IDs reserved this tick (cleared on dispatch or release)
|
2026-04-29 21:51:23 -05:00
|
|
|
- `completed` — set of issue IDs (bookkeeping)
|
|
|
|
|
|
|
|
|
|
### Worker heartbeats
|
feat(symphony): tracker-agnostic orchestrator plugin
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>
2026-04-29 21:03:21 -05:00
|
|
|
|
2026-04-29 21:51:23 -05:00
|
|
|
Workers write `.symphony/heartbeats/<issue-id>.json` with `{ timestamp, status, task_id, session_id }`
|
|
|
|
|
every minute (configurable via `agent.heartbeat_interval_ms`). On graceful exit they
|
|
|
|
|
remove the file. Stale heartbeats — and stale `running` entries with no heartbeat at
|
|
|
|
|
all — are how Symphony detects abandonment.
|
feat(symphony): tracker-agnostic orchestrator plugin
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>
2026-04-29 21:03:21 -05:00
|
|
|
|
|
|
|
|
## Mapping to the spec
|
|
|
|
|
|
|
|
|
|
| Symphony component | Claude Code mechanism |
|
|
|
|
|
|---------------------------|--------------------------------------------------------|
|
2026-04-29 21:51:23 -05:00
|
|
|
| Polling daemon | `/schedule` firing `/symphony-tick` in a long-lived session |
|
feat(symphony): tracker-agnostic orchestrator plugin
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>
2026-04-29 21:03:21 -05:00
|
|
|
| Workflow loader | Skill body — reads & parses `WORKFLOW.md` |
|
|
|
|
|
| Issue tracker client | Project CLAUDE.md instructions + whatever tools fit |
|
2026-04-29 21:51:23 -05:00
|
|
|
| Orchestrator | Skill, runs each tick within the same session |
|
feat(symphony): tracker-agnostic orchestrator plugin
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>
2026-04-29 21:03:21 -05:00
|
|
|
| Workspace manager | `EnterWorktree` per worker |
|
|
|
|
|
| Agent runner | `Agent` tool with `subagent_type: symphony-worker` |
|
|
|
|
|
| Hooks | Bash steps inside the worker prompt |
|
2026-04-29 21:51:23 -05:00
|
|
|
| Concurrency cap | `TaskList` count + `state.json` cross-check |
|
feat(symphony): tracker-agnostic orchestrator plugin
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>
2026-04-29 21:03:21 -05:00
|
|
|
| Retries with backoff | `retry_attempts` entries, due_at compared each tick |
|
2026-04-29 21:51:23 -05:00
|
|
|
| Liveness across sessions | Worker heartbeats in `.symphony/heartbeats/` |
|
feat(symphony): tracker-agnostic orchestrator plugin
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>
2026-04-29 21:03:21 -05:00
|
|
|
| Logging | `.symphony/logs/<issue>-<ts>.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).
|