Files
movq ee4e0b236e fix(symphony): correct runtime model — long-lived session, with multi-session support
Earlier framing assumed each /schedule-fired tick was a cold start
needing on-disk reconciliation. The actual model is a long-lived
orchestrator session where /schedule fires ticks within the running
session — TaskList sees prior-tick tasks just fine. Reframed
accordingly, while leaving the door open to other trigger sources
(Discord channel, webhook, second user session) which DO need
cross-session reconciliation.

Reconciliation logic now has a fast path and a slow path:

- Fast path: task in our TaskList → it's ours → reconcile via TaskGet.
- Slow path: task NOT in our TaskList (another session, or our session
  restarted) → check the worker's heartbeat file. Fresh → another
  session owns it, leave alone. Stale or missing → consider abandoned,
  retry; do not call TaskStop on a task we don't own.

Workers now write .symphony/heartbeats/<issue-id>.json on start,
refresh it at heartbeat_interval_ms cadence (default 60s), and delete
it on graceful exit. Stale heartbeats are how abandonment is detected
across sessions and after crashes.

Also added session_id to state.json running entries so cross-session
reconcilers know who spawned what.

WORKFLOW.md gains heartbeat_interval_ms and heartbeat_stale_ms knobs
under agent: with documented defaults.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:51:23 -05:00

72 lines
2.3 KiB
Markdown

---
# 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
# Worker heartbeat cadence — affects how quickly other Symphony sessions
# (Discord triggers, webhooks, separate user sessions) detect abandoned
# workers. Defaults are safe; tighten if you have many concurrent sessions.
heartbeat_interval_ms: 60000 # workers refresh the heartbeat at most this often
heartbeat_stale_ms: 300000 # reconciler treats heartbeats older than this as abandoned
---
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.