Files
claude-plugins/symphony/skills/symphony/SKILL.md
movq cd3032458c 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

5.3 KiB

name, description
name description
symphony Orchestrator skill for the Symphony plugin. Runs one tick — loads WORKFLOW.md, polls the project's issue tracker, reconciles in-flight workers, dispatches new ones up to the concurrency cap, persists state. Use when the user runs /symphony-tick, asks to "run a Symphony tick", or wants to set up the cron entry. Not for one-off issue work — that's the symphony-worker agent.

Symphony orchestrator (one tick)

You are the dispatcher. Each invocation runs one poll-and-dispatch pass and exits. Spec mapping is in ../../CLAUDE.md.

Inputs

  • ./WORKFLOW.md in the consuming repo (REQUIRED, error class missing_workflow_file).
  • ./.symphony/state.json (auto-created if absent).
  • The project's CLAUDE.md — provides the tracker integration for whatever WORKFLOW.md's tracker.kind names (see "Tracker contract" in the plugin CLAUDE.md). If the project CLAUDE.md does not document the tracker named in tracker.kind, exit with tracker_integration_missing.

Tick steps

1. Load workflow

Read ./WORKFLOW.md. Parse YAML front matter (everything between leading --- markers); the body after the second --- is the prompt template. If front matter is absent, body is the whole file and config is {}.

Normalize:

  • workspace.root: expand ~, resolve relative paths against the WORKFLOW.md directory, normalize to absolute.
  • tracker.active_states / terminal_states: lowercase for comparison.
  • tracker.kind: free-form string (e.g. github, gitea, tracker, linear). The project CLAUDE.md is responsible for documenting how to talk to it. Any auth/endpoint config the tracker needs lives in the project's setup, not Symphony's front matter.

Validation errors abort dispatch but do not kill running workers — they finish on their own.

2. Read state

.symphony/state.json
{
  "running":   { "<issue_id>": { "task_id": "...", "worktree": "...", "started_at": "..." } },
  "retry_attempts": { "<issue_id>": { "attempt": 2, "due_at_ms": 1714000000000, "last_error": "..." } },
  "claimed":   ["<issue_id>", ...],
  "completed": ["<issue_id>", ...]
}

Create empty defaults if missing. Always rewrite atomically (write state.json.tmp, rename).

3. Reconcile

For each entry in running:

  • Call TaskList (or TaskGet by id). If the task is no longer running and reported success, move issue to completed, drop from running.
  • If the task failed, move into retry_attempts with exponential backoff (capped by agent.max_retry_backoff_ms).
  • Re-fetch the issue's current state from the tracker. If state is in terminal_states or no longer in active_states, call TaskStop on the worker and drop from running.

For each entry in retry_attempts whose due_at_ms <= now, treat as eligible for dispatch (the issue ID is still claimed; do not dispatch a duplicate).

4. Query tracker

Read the project's CLAUDE.md. Find the section that documents how to list issues for the tracker named in tracker.kind. Run the listed command(s) — typically a CLI like gh issue list --json ..., an MCP tool like mcp__gitea__list_issues, or the project's own tracker CLI. Parse the result into the normalized issue shape (id, identifier, title, description, state, labels, priority, branch_name, url, updated_at).

If the project CLAUDE.md does not document this tracker, exit the tick with tracker_integration_missing. Do not guess at commands; a wrong query against a real tracker is worse than a clean failure.

Filter to active_states. Exclude:

  • issues in running
  • issues in retry_attempts with due_at_ms > now
  • issues in completed (bookkeeping; not strict)

Sort by priority (lower number = higher), then updated_at desc.

5. Dispatch

Compute slots = agent.max_concurrent_agents - len(running). If agent.max_concurrent_agents_by_state is set, also enforce per-state caps.

For each candidate up to slots:

  1. Render the workflow's prompt template with { issue, attempt }. Strict rendering — unknown vars/filters fail this issue's dispatch (template_render_error), not the tick.
  2. Spawn the worker:
    Agent({
      subagent_type: "symphony-worker",
      description: "<issue.identifier>: <issue.title>",
      prompt: "<rendered template>\n\n---\nIssue payload:\n<json>",
      isolation: "worktree",
      run_in_background: true
    })
    
  3. Record running[issue.id] = { task_id, worktree, started_at: now }.
  4. Drop from retry_attempts if present.

6. Persist & exit

Write .symphony/state.json. Append a structured log line per dispatched/reconciled issue to .symphony/logs/tick-<YYYYMMDD>.jsonl. Return a one-line summary:

tick: dispatched=N reconciled=M retried=K running=R

Error classes (per spec §5.5, plus port-specific)

  • missing_workflow_file — log and exit; do not fall back to a default prompt.
  • workflow_parse_error / workflow_front_matter_not_a_map — same.
  • template_parse_error / template_render_error — fail the affected issue only.
  • tracker_integration_missing (port-specific) — project CLAUDE.md does not document how to talk to the tracker named in tracker.kind. Exit the tick; the user has to add a tracker section to their CLAUDE.md.

Tick errors must be non-fatal at the cron level: a bad WORKFLOW.md means the next tick sees the fix and recovers. Never exit non-zero in a way that disables the cron entry.