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>
This commit is contained in:
66
symphony/templates/WORKFLOW.md
Normal file
66
symphony/templates/WORKFLOW.md
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
# Pick the tracker your project uses. Symphony does not care which —
|
||||
# it asks the project's CLAUDE.md how to talk to it.
|
||||
#
|
||||
# github — `gh` CLI
|
||||
# gitea — gitea MCP server
|
||||
# tracker — local `.tracker/` CLI (see tracker-usage skill)
|
||||
# linear — Linear MCP / GraphQL
|
||||
# <yours> — anything, as long as your CLAUDE.md documents it
|
||||
tracker:
|
||||
kind: github
|
||||
active_states: [open]
|
||||
terminal_states: [closed]
|
||||
|
||||
polling:
|
||||
interval_ms: 60000
|
||||
|
||||
workspace:
|
||||
root: ~/.symphony/workspaces
|
||||
|
||||
hooks:
|
||||
after_create: |
|
||||
git fetch origin
|
||||
git checkout -b "{{ issue.branch_name | default: issue.identifier }}"
|
||||
before_run: |
|
||||
# project-specific install/setup
|
||||
test -f package.json && npm install --silent || true
|
||||
test -f pyproject.toml && uv sync || true
|
||||
after_run: |
|
||||
# advisory — never blocks the tick
|
||||
true
|
||||
timeout_ms: 120000
|
||||
|
||||
agent:
|
||||
max_concurrent_agents: 3
|
||||
max_turns: 30
|
||||
max_retry_backoff_ms: 600000
|
||||
max_concurrent_agents_by_state:
|
||||
"in progress": 2
|
||||
---
|
||||
|
||||
You are working on issue **{{ issue.identifier }}** — {{ issue.title }}.
|
||||
|
||||
{% if attempt %}This is retry attempt {{ attempt }}. Review prior worktree commits and
|
||||
pick up where the last attempt stopped.{% endif %}
|
||||
|
||||
## Issue body
|
||||
|
||||
{{ issue.description }}
|
||||
|
||||
## Labels
|
||||
|
||||
{{ issue.labels | join: ", " }}
|
||||
|
||||
## Your job
|
||||
|
||||
1. Implement the change on branch `{{ issue.branch_name | default: issue.identifier }}`.
|
||||
2. Add or update tests covering the new behavior. Run them.
|
||||
3. Commit referencing `{{ issue.identifier }}`.
|
||||
4. Push the branch and open a PR.
|
||||
5. Comment on the issue with the PR link, then transition the issue to a "review" state.
|
||||
Use the commands documented in this project's CLAUDE.md under the issue tracker
|
||||
section — the plugin does not know your tracker's quirks.
|
||||
|
||||
If you cannot finish in your turn budget, leave a status comment on the issue summarizing
|
||||
progress and stop. Do not transition state.
|
||||
44
symphony/templates/state.schema.json
Normal file
44
symphony/templates/state.schema.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Symphony orchestrator state",
|
||||
"type": "object",
|
||||
"required": ["running", "retry_attempts", "claimed", "completed"],
|
||||
"properties": {
|
||||
"running": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"required": ["task_id", "worktree", "started_at"],
|
||||
"properties": {
|
||||
"task_id": { "type": "string" },
|
||||
"worktree": { "type": "string" },
|
||||
"started_at": { "type": "string", "format": "date-time" },
|
||||
"issue_identifier": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"retry_attempts": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"required": ["attempt", "due_at_ms"],
|
||||
"properties": {
|
||||
"attempt": { "type": "integer", "minimum": 1 },
|
||||
"due_at_ms": { "type": "integer" },
|
||||
"last_error": { "type": ["string", "null"] }
|
||||
}
|
||||
}
|
||||
},
|
||||
"claimed": { "type": "array", "items": { "type": "string" } },
|
||||
"completed": { "type": "array", "items": { "type": "string" } },
|
||||
"totals": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ticks": { "type": "integer" },
|
||||
"dispatched": { "type": "integer" },
|
||||
"completed": { "type": "integer" },
|
||||
"failed": { "type": "integer" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user