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>
This commit is contained in:
@@ -37,6 +37,11 @@ agent:
|
||||
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 }}.
|
||||
|
||||
@@ -10,9 +10,10 @@
|
||||
"type": "object",
|
||||
"required": ["task_id", "worktree", "started_at"],
|
||||
"properties": {
|
||||
"task_id": { "type": "string" },
|
||||
"worktree": { "type": "string" },
|
||||
"started_at": { "type": "string", "format": "date-time" },
|
||||
"task_id": { "type": "string" },
|
||||
"session_id": { "type": ["string", "null"], "description": "Identifier for the orchestrator session that spawned this task. Other sessions reconcile via the heartbeat file when this session's TaskList does not include the task_id." },
|
||||
"worktree": { "type": "string" },
|
||||
"started_at": { "type": "string", "format": "date-time" },
|
||||
"issue_identifier": { "type": "string" }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user