Valid states: open → in_progress → completed / blocked / failed. Who can drive each transition, what writes to the audit log, and how to query historical state changes.
Trace an issue from creation to resolution through every state transition, understand what records are created at each step, and build a timeline view from the database.
CREATED
│ INSERT INTO issues (status='open')
▼
OPEN
│ Heartbeat claims it: UPDATE status='claimed'
▼
CLAIMED
│ Adapter invoke starts: UPDATE status='in_progress'
▼
IN_PROGRESS
│
├─ Adapter succeeds ──────────► RESOLVED
│ UPDATE status='resolved', result=..., resolved_at=now()
│ INSERT INTO cost_events (...)
│
├─ Adapter fails, retry ──────► OPEN (attempt_count++)
│ UPDATE status='open'
│
├─ Max attempts exceeded ─────► ESCALATED
│ UPDATE status='escalated'
│ INSERT INTO approvals (trigger='task_failed', ...)
│
└─ Manual cancel ─────────────► CANCELLED
UPDATE status='cancelled'
| Transition | Database records created |
|---|---|
open → claimed |
issues.claimed_at set |
claimed → in_progress |
heartbeat_runs row updated with this issue |
in_progress → resolved |
issues.result set; cost_events row inserted |
in_progress → escalated |
approvals row inserted |
SELECT
'Created' AS event,
i.created_at AS occurred_at,
NULL AS details
FROM issues i WHERE i.id = 'issue-id'
UNION ALL
SELECT
'Claimed by heartbeat',
i.claimed_at,
'Heartbeat run: ' || i.heartbeat_run_id::text
FROM issues i WHERE i.id = 'issue-id' AND i.claimed_at IS NOT NULL
UNION ALL
SELECT
'Resolved',
i.resolved_at,
'Cost: $' || ce.cost_usd::text
FROM issues i
JOIN cost_events ce ON ce.issue_id = i.id
WHERE i.id = 'issue-id' AND i.resolved_at IS NOT NULL
ORDER BY occurred_at;
When an adapter call fails (exception thrown or non-zero exit), the heartbeat:
issues.status = 'open' and increments attempt_countheartbeat_runs.error_detail (JSON array)attempt_count < max_attempts)This means a failed issue is retried on the next heartbeat tick, not immediately. For a 5-minute heartbeat with max_attempts = 3, a repeatedly-failing issue will be retried at T+5m and T+10m before escalating.
-- Give a complex issue more retries
UPDATE issues
SET max_attempts = 5
WHERE id = 'issue-id';
-- Reset a stuck issue
UPDATE issues
SET status = 'open', attempt_count = 0
WHERE id = 'issue-id';