description: Hermes Kanban's lifecycle and dispatcher, with supercode's session lanes
params:
dispatch_in_gateway: true dispatch_interval_seconds: 60 dispatch_stale_timeout_seconds: null
failure_limit: 2 claim_ttl_seconds: 900 max_in_progress: null max_in_progress_per_profile: null
max_spawn: null
default_assignee: null
review_dispatch: true review_skills: [sdlc-review] auto_decompose: true
auto_decompose_per_tick: 3
reconcile_orphans: true block_recurrence_limit: 2 rate_limit_cooldown_seconds: 300 recent_success_seconds: 3600 active_pr_seconds: 86400 blocker_pattern: '(?i)\b(quota|rate[\s_-]?limit|429|403|auth\w*|unauthorized|forbidden|billing|subscription|access[\s_]denied|permission[\s_]denied|invalid[\s_]api[\s_]key)\b'
pr_pattern: '(?i)https?://github\.com/[^/\s]+/[^/\s]+/pull/\d+'
session_grace_seconds: 120 goal_mode_default: false goal_max_turns: 20 heartbeat_quiet_seconds: 1800 managers: [] machine_offline_grace_seconds: 3600 machines: {}
roles:
implementer: card.assignee ?? params.default_assignee
reviewer: card.assignee ?? params.default_assignee
creator: card.created_by
initial: - guard: "event.initial_status == 'blocked'"
target: blocked
actions: [{ assign: { field: goal_mode, value: "{{event.goal ?? (event.subtask_of == null && params.goal_mode_default)}}" } }]
- guard: event.triage
target: triage
actions: [{ assign: { field: goal_mode, value: "{{event.goal ?? (event.subtask_of == null && params.goal_mode_default)}}" } }]
- guard: "!card.parents.all(p, p.status in ['done', 'archived'])"
target: todo
actions: [{ assign: { field: goal_mode, value: "{{event.goal ?? (event.subtask_of == null && params.goal_mode_default)}}" } }]
- target: ready
actions: [{ assign: { field: goal_mode, value: "{{event.goal ?? (event.subtask_of == null && params.goal_mode_default)}}" } }]
on:
comment: [{ actions: [{ record: commented }] }]
assign:
- validate: [{ expr: "!(card.status in ['running', 'reviewing'])", message: "the card is running; reclaim it before reassigning" }]
guard: "event.assignee != card.assignee"
actions: [{ assign: { field: assignee, value: "{{event.assignee}}" } }, { reset: [failures] }, { assign: { field: last_failure_error, value: "{{null}}" } }, { record: assigned }]
- actions: [{ record: assigned }]
set_model: [{ actions: [{ assign: { field: model_override, value: "{{event.model}}" } }, { assign: { field: provider_override, value: "{{event.provider}}" } }, { record: model_override_set }] }]
set_goal: [{ actions: [{ assign: { field: goal_mode, value: "{{event.goal}}" } }, { assign: { field: goal_max_turns, value: "{{event.max_turns}}" } }, { record: goal_set }] }]
link: [{ actions: [{ link: "{{event.parent}}" }, { record: linked }] }]
unlink: [{ actions: [{ unlink: "{{event.parent}}" }, { record: unlinked }] }]
archive:
- target: archived
actions: [{ end_run: { outcome: reclaimed, summary: task archived with run still active } }, { record: archived }]
adopt: - by: [manager]
validate: [{ expr: "card.status in ['todo', 'ready', 'blocked', 'scheduled', 'triage', 'review']", message: "only an open card takes a session" }]
target: running
actions: [{ claim: implementer }]
statuses:
triage:
stored_as: triage
description: a rough idea waiting to be specified (Hermes asks its auxiliary model; here a session writes it)
on:
specify:
- target: todo
actions: [{ specify: "{{event}}" }, { record: specified }]
decompose:
- target: todo
actions: [{ decompose: "{{event}}" }, { record: decomposed }]
start:
- by: [platform]
guard: params.auto_decompose
target: triaging
actions: [{ claim: implementer }]
- by: [manager] target: triaging
actions: [{ claim: implementer }]
triaging: stored_as: running
run:
role: implementer
session: worker
prompt: |
Card {{card.id}} on the {{board}} board is in triage: a rough idea to split. Read it, then file the cards it
breaks into and make it wait on them, or write its spec.
{{doors}}
{{context}}
on:
specify: [{ by: [implementer], target: todo, actions: [{ end_run: { outcome: specified } }, { specify: "{{event}}" }, { record: specified }] }]
decompose: [{ by: [implementer], target: todo, actions: [{ end_run: { outcome: decomposed } }, { decompose: "{{event}}" }, { record: decomposed }] }]
block: [{ by: [implementer, manager], description: 'you cannot go on: `"<why>" --kind needs_input|capability|transient|dependency`', target: blocked, actions: [{ end_run: { outcome: blocked } }, { assign: { field: block_kind, value: "{{event.kind}}" } }, { record: blocked }] }]
heartbeat: [{ by: [implementer], description: 'still working on a long step, or waiting on something outside (a person, a run): the board leaves you be for a while, or until the time you name (`--note "<what>" [--until <time|2h>]`)', actions: [{ extend_claim: null }, { reset: [goal_turns] }, { record: heartbeat }] }]
spawn_failed: - by: [platform]
guard: "card.counters.failures + 1 >= (card.max_retries ?? params.failure_limit)"
target: blocked
actions: [{ end_run: { outcome: spawn_failed } }, { count: failures }, { record: spawn_failed }, { record: gave_up }]
- by: [platform]
target: triage
actions: [{ end_run: { outcome: spawn_failed } }, { count: failures }, { record: spawn_failed }]
session_idle: - by: [platform] guard: "card.last_heartbeat_at != null && now < (card.last_events.heartbeat.payload.until ?? card.last_heartbeat_at + params.heartbeat_quiet_seconds)"
- by: [platform]
guard: "event.turns >= (card.goal_max_turns ?? params.goal_max_turns)"
target: blocked
actions: [{ end_run: { outcome: blocked } }, { assign: { field: block_kind, value: needs_input } }, { record: { kind: blocked, payload: { reason: "its triage session went idle {{event.turns}} times without deciding; review it", kind: needs_input, source_status: triage } } }]
- by: [platform]
actions:
- count: goal_turns
- send_to_actor: "Card {{card.id}} ({{card.title}}) is waiting for you to decide ({{event.turns + 1}} of {{card.goal_max_turns ?? params.goal_max_turns}}).\n{{doors}}"
- record: { kind: decision_asked, payload: { turn: "{{event.turns + 1}}", budget: "{{card.goal_max_turns ?? params.goal_max_turns}}" } }
machine_offline: - by: [platform]
guard: "event.seconds >= params.machine_offline_grace_seconds"
target: blocked
actions: [{ end_run: { outcome: reclaimed } }, { assign: { field: block_kind, value: capability } }, { record: { kind: blocked, payload: { reason: "machine {{event.machine}} has been off the fleet for {{event.seconds}}s", kind: capability, source_status: triage } } }]
session_lost: [{ by: [platform], target: triage, actions: [{ end_run: { outcome: reclaimed } }, { record: session_lost }] }]
orphaned: [{ by: [platform], guard: params.reconcile_orphans, target: triage, actions: [{ end_run: { outcome: reclaimed } }, { record: reconciled }, { comment: "Requeued: the card was running with no claim and nothing live behind it." }] }]
exited: [{ by: [platform], target: triage, actions: [{ end_run: { outcome: crashed } }, { record: crashed }] }]
todo:
stored_as: todo
description: waiting on its parents
always:
- guard: "card.arc != null && card.arc.status in ['done', 'archived']" target: blocked
actions: [{ assign: { field: block_kind, value: needs_input } }, { record: { kind: blocked, payload: { reason: "its arc {{card.arc.id}} ended with it open", kind: needs_input, source_status: ready } } }]
- guard: "card.parents.all(p, p.status in ['done', 'archived'])" target: ready
actions: [{ record: promoted }]
on:
promote:
- validate: [{ expr: "event.force || card.parents.all(p, p.status in ['done', 'archived'])", message: "parents not done (force to override)" }]
target: ready
actions: [{ record: promoted_manual }]
schedule: [{ target: scheduled, actions: [{ record: scheduled }] }]
ready:
stored_as: ready
description: waiting for the dispatcher to start it
always:
- guard: "card.arc != null && card.arc.status in ['done', 'archived']" target: blocked
actions: [{ assign: { field: block_kind, value: needs_input } }, { record: { kind: blocked, payload: { reason: "its arc {{card.arc.id}} ended with it open", kind: needs_input, source_status: ready } } }]
- guard: "!card.parents.all(p, p.status in ['done', 'archived'])" target: todo
on:
start: - by: [platform]
validate:
- expr: "!(card.last_run.outcome == 'rate_limited' && now - card.last_run.ended_at < params.rate_limit_cooldown_seconds)"
message: rate_limit_cooldown
- expr: "!matches(card.last_failure_error, params.blocker_pattern)"
message: blocker_auth
- expr: "!(card.last_completed != null && now - card.last_completed.ended_at < params.recent_success_seconds && card.events_since(card.last_completed.ended_at, ['status', 'promoted', 'unblocked', 'reclaimed']).size() == 0)"
message: recent_success
- expr: "!card.comments_since(now - params.active_pr_seconds).exists(c, matches(c.body, params.pr_pattern))"
message: active_pr
target: running
actions: [{ claim: implementer }]
block:
- guard: "event.kind == 'dependency'"
target: todo
actions: [{ assign: { field: block_kind, value: "{{event.kind}}" } }, { record: dependency_wait }]
- guard: "card.block_kind == event.kind && card.counters.block_recurrences + 1 >= params.block_recurrence_limit"
target: triage
actions: [{ count: block_recurrences }, { record: block_loop_detected }]
- target: blocked
actions: [{ count: block_recurrences }, { assign: { field: block_kind, value: "{{event.kind}}" } }, { record: blocked }]
request_review:
- target: review
actions: [{ assign: { field: assignee, value: "{{event.reviewer ?? (card.last_reviewer in lanes ? card.last_reviewer : null) ?? card.assignee}}" } }, { record: review_requested }]
complete:
- validate: [{ expr: "card.parents.all(p, p.status in ['done', 'archived'])", message: "the card waits on parents that are not done" }]
target: done
actions: [{ end_run: { outcome: completed } }, { set_result: "{{event}}" }, { reset: [failures, block_recurrences] }, { assign: { field: last_failure_error, value: "{{null}}" } }, { assign: { field: block_kind, value: "{{null}}" } }, { cleanup_workspace: null }, { record: completed }, { check_references: "{{event}}" }]
schedule: [{ target: scheduled, actions: [{ record: scheduled }] }]
running:
stored_as: running
description: its implementer is working it
run:
role: implementer
session: worker
prompt: |
You are the session working card {{card.id}} on the {{board}} board. The board started you; updates to the
card and to the cards it waits on reach you as board notices.
{{doors}}
Leave a note on it with `supercode workflow comment {{card.id}} "<text>" {{where}}`; file a subtask with
`supercode workflow create "<title>" --subtask-of {{card.id}} {{where}}`.
{{context}}
limits:
runtime: card.max_runtime_seconds
stale: "run.surface == 'headless' ? params.dispatch_stale_timeout_seconds : null"
grace: params.session_grace_seconds
on:
heartbeat: [{ by: [implementer], description: 'still working on a long step, or waiting on something outside (a person, a run): the board leaves you be for a while, or until the time you name (`--note "<what>" [--until <time|2h>]`)', actions: [{ extend_claim: null }, { reset: [goal_turns] }, { record: heartbeat }] }]
complete:
- by: [implementer, manager]
description: 'the work is done: `--summary "<what you did and how it was verified>"`'
validate:
- { expr: "event.run_id == null || event.run_id == card.current_run_id", message: "that run is no longer the card's current run" }
- { expr: "card.parents.all(p, p.status in ['done', 'archived'])", message: "the card waits on parents that are not done" }
target: done
actions: [{ end_run: { outcome: completed } }, { set_result: "{{event}}" }, { reset: [failures, block_recurrences] }, { assign: { field: last_failure_error, value: "{{null}}" } }, { assign: { field: block_kind, value: "{{null}}" } }, { cleanup_workspace: null }, { record: completed }, { check_references: "{{event}}" }]
block:
- by: [implementer, manager]
description: 'you cannot go on and nothing you wait on will wake you (a wait with a known end is a heartbeat `--until`): `"<why>" --kind needs_input|capability|transient|dependency`'
guard: "event.kind == 'dependency'"
target: todo
actions: [{ end_run: { outcome: blocked } }, { assign: { field: block_kind, value: "{{event.kind}}" } }, { record: dependency_wait }]
- by: [implementer, manager]
guard: "card.block_kind == event.kind && card.counters.block_recurrences + 1 >= params.block_recurrence_limit"
target: triage
actions: [{ end_run: { outcome: blocked } }, { count: block_recurrences }, { record: block_loop_detected }]
- by: [implementer, manager]
target: blocked
actions: [{ end_run: { outcome: blocked } }, { count: block_recurrences }, { assign: { field: block_kind, value: "{{event.kind}}" } }, { record: blocked }]
request_review:
- by: [implementer, manager]
description: 'the work is ready for review: `--summary "<what to review>"`'
validate: [{ expr: "event.run_id == card.current_run_id || event.force", message: "the card is running under a live claim; its worker requests review, or pass --force" }]
target: review
actions: [{ end_run: { outcome: review_requested } }, { assign: { field: assignee, value: "{{event.reviewer ?? (card.last_reviewer in lanes ? card.last_reviewer : null) ?? card.assignee}}" } }, { record: review_requested }]
reclaim: [{ by: [manager], target: ready, actions: [{ end_run: { outcome: reclaimed } }, { reset: [failures] }, { record: reclaimed }] }]
schedule: [{ by: [manager], target: scheduled, actions: [{ end_run: { outcome: scheduled } }, { record: scheduled }] }]
exited:
- by: [platform]
guard: "event.code == 75"
target: ready
actions: [{ end_run: { outcome: rate_limited } }, { record: rate_limited }]
- by: [platform] guard: "event.code == 0"
target: blocked
actions: [{ end_run: { outcome: protocol_violation } }, { count: failures }, { record: crashed }, { record: gave_up }]
- by: [platform]
guard: "card.counters.failures + 1 >= (card.max_retries ?? params.failure_limit)"
target: blocked
actions: [{ end_run: { outcome: "{{event.code == 0 ? 'protocol_violation' : 'crashed'}}" } }, { count: failures }, { record: crashed }, { record: gave_up }]
- by: [platform]
target: ready
actions: [{ end_run: { outcome: "{{event.code == 0 ? 'protocol_violation' : 'crashed'}}" } }, { count: failures }, { record: crashed }]
spawn_failed: - by: [platform]
guard: "card.counters.failures + 1 >= (card.max_retries ?? params.failure_limit)"
target: blocked
actions: [{ end_run: { outcome: spawn_failed } }, { count: failures }, { record: spawn_failed }, { record: gave_up }]
- by: [platform]
target: ready
actions: [{ end_run: { outcome: spawn_failed } }, { count: failures }, { record: spawn_failed }]
timed_out:
- by: [platform]
guard: "card.counters.failures + 1 >= (card.max_retries ?? params.failure_limit)"
target: blocked
actions: [{ stop_run: null }, { end_run: { outcome: timed_out } }, { count: failures }, { record: timed_out }, { record: gave_up }]
- by: [platform]
target: ready
actions: [{ stop_run: null }, { end_run: { outcome: timed_out } }, { count: failures }, { record: timed_out }]
orphaned: [{ by: [platform], guard: params.reconcile_orphans, target: ready, actions: [{ end_run: { outcome: reclaimed } }, { record: reconciled }, { comment: "Requeued: the card was running with no claim and nothing live behind it." }] }]
stale: [{ by: [platform], target: ready, actions: [{ stop_run: null }, { end_run: { outcome: stale } }, { record: stale }] }]
session_lost: - by: [platform] guard: "event.never_came_up && card.counters.failures + 1 >= (card.max_retries ?? params.failure_limit)"
target: blocked
actions: [{ end_run: { outcome: reclaimed } }, { count: failures }, { record: session_lost }, { record: gave_up }]
- by: [platform]
guard: event.never_came_up
target: ready
actions: [{ end_run: { outcome: reclaimed } }, { count: failures }, { record: session_lost }]
- by: [platform]
target: ready
actions: [{ end_run: { outcome: reclaimed } }, { record: session_lost }]
machine_offline: - by: [platform]
guard: "event.seconds >= params.machine_offline_grace_seconds"
target: blocked
actions: [{ end_run: { outcome: reclaimed } }, { assign: { field: block_kind, value: capability } }, { record: { kind: blocked, payload: { reason: "machine {{event.machine}} has been off the fleet for {{event.seconds}}s", kind: capability, source_status: ready } } }]
session_idle: - by: [platform] guard: "card.last_heartbeat_at != null && now < (card.last_events.heartbeat.payload.until ?? card.last_heartbeat_at + params.heartbeat_quiet_seconds)"
- by: [platform]
guard: "(card.goal_mode || !event.worked) && event.turns >= (card.goal_max_turns ?? params.goal_max_turns)"
target: blocked
actions:
- end_run: { outcome: blocked }
- assign: { field: block_kind, value: needs_input }
- record: { kind: blocked, payload: { reason: "its session went idle {{event.turns}} times without deciding done or blocked; review it", kind: needs_input, source_status: ready } }
- by: [platform]
guard: card.goal_mode
actions:
- count: goal_turns
- send_to_actor: "Card {{card.id}} ({{card.title}}) is not done yet: keep working toward it (goal turn {{event.turns + 1}} of {{card.goal_max_turns ?? params.goal_max_turns}}).\n{{doors}}"
- record: { kind: goal_continued, payload: { turn: "{{event.turns + 1}}", budget: "{{card.goal_max_turns ?? params.goal_max_turns}}" } }
- by: [platform]
actions:
- reset: "{{event.worked ? ['goal_turns'] : []}}"
- count: goal_turns
- send_to_actor: "Card {{card.id}} ({{card.title}}) is still running and you are idle: decide ({{(event.worked ? 0 : event.turns) + 1}} of {{card.goal_max_turns ?? params.goal_max_turns}}). If there is more to do, keep working. A session that stays idle without deciding has its card blocked for its creator.\n{{doors}}"
- record: { kind: decision_asked, payload: { turn: "{{(event.worked ? 0 : event.turns) + 1}}", budget: "{{card.goal_max_turns ?? params.goal_max_turns}}" } }
review:
stored_as: review
description: its implementer asked for review
on:
start: - by: [platform]
guard: params.review_dispatch
validate:
- expr: "!(card.last_run.outcome == 'rate_limited' && now - card.last_run.ended_at < params.rate_limit_cooldown_seconds)"
message: rate_limit_cooldown
- expr: "!matches(card.last_failure_error, params.blocker_pattern)"
message: blocker_auth
target: reviewing
actions: [{ claim: reviewer }]
complete:
- by: [reviewer, manager]
description: 'the work is done: `--summary "<what you did and how it was verified>"`'
validate: [{ expr: "card.parents.all(p, p.status in ['done', 'archived'])", message: "the card waits on parents that are not done" }]
target: done
actions: [{ end_run: { outcome: completed, summary: "{{event.summary ?? 'Review approved without additional evidence.'}}" } }, { set_result: "{{event}}" }, { reset: [failures, block_recurrences] }, { assign: { field: last_failure_error, value: "{{null}}" } }, { assign: { field: block_kind, value: "{{null}}" } }, { cleanup_workspace: null }, { record: completed }, { check_references: "{{event}}" }]
request_changes: - by: [reviewer, manager]
target: "{{card.parents.all(p, p.status in ['done', 'archived']) ? 'ready' : 'todo'}}"
actions: [{ assign: { field: assignee, value: "{{card.implementer}}" } }, { record: changes_requested }]
reopen_review:
- by: [manager]
target: "{{card.parents.all(p, p.status in ['done', 'archived']) ? 'ready' : 'todo'}}"
actions: [{ assign: { field: assignee, value: "{{card.implementer}}" } }, { record: review_reopened }]
block:
- target: blocked
actions: [{ assign: { field: block_kind, value: "{{event.kind}}" } }, { record: blocked }]
reviewing:
stored_as: running
description: a reviewer is reviewing it
run:
role: reviewer
session: reviewer
prompt: |
You are reviewing card {{card.id}} on the {{board}} board: its implementer asked for review. Verify the
handoff against the card's acceptance, then give the verdict.
{{doors}}
{{context}}
skills: [sdlc-review]
limits:
runtime: card.max_runtime_seconds
grace: params.session_grace_seconds
exit: [{ close_session: reviewer }]
on:
heartbeat: [{ by: [reviewer], description: 'still working on a long step, or waiting on something outside (a person, a run): the board leaves you be for a while, or until the time you name (`--note "<what>" [--until <time|2h>]`)', actions: [{ extend_claim: null }, { reset: [goal_turns] }, { record: heartbeat }] }]
complete:
- by: [reviewer, manager]
description: 'the work is done: `--summary "<what you did and how it was verified>"`'
validate: [{ expr: "card.parents.all(p, p.status in ['done', 'archived'])", message: "the card waits on parents that are not done" }]
target: done
actions: [{ end_run: { outcome: completed } }, { set_result: "{{event}}" }, { reset: [failures, block_recurrences] }, { assign: { field: last_failure_error, value: "{{null}}" } }, { assign: { field: block_kind, value: "{{null}}" } }, { cleanup_workspace: null }, { record: completed }, { check_references: "{{event}}" }]
request_changes:
- by: [reviewer, manager]
description: 'send it back to its implementer: `"<what must change>"`'
target: "{{card.parents.all(p, p.status in ['done', 'archived']) ? 'ready' : 'todo'}}"
actions: [{ end_run: { outcome: changes_requested } }, { assign: { field: assignee, value: "{{card.implementer}}" } }, { record: changes_requested }]
block:
- by: [reviewer, manager]
description: 'you cannot go on and nothing you wait on will wake you (a wait with a known end is a heartbeat `--until`): `"<why>" --kind needs_input|capability|transient|dependency`'
target: blocked
actions: [{ end_run: { outcome: blocked } }, { assign: { field: block_kind, value: "{{event.kind}}" } }, { record: blocked }]
exited:
- by: [platform]
target: review
actions: [{ end_run: { outcome: crashed } }, { count: failures }, { record: crashed }]
spawn_failed: - by: [platform]
guard: "card.counters.failures + 1 >= (card.max_retries ?? params.failure_limit)"
target: blocked
actions: [{ end_run: { outcome: spawn_failed } }, { count: failures }, { record: spawn_failed }, { record: gave_up }]
- by: [platform]
target: review
actions: [{ end_run: { outcome: spawn_failed } }, { count: failures }, { record: spawn_failed }]
timed_out: [{ by: [platform], target: review, actions: [{ stop_run: null }, { end_run: { outcome: timed_out } }, { count: failures }, { record: timed_out }] }]
session_lost: [{ by: [platform], target: review, actions: [{ end_run: { outcome: reclaimed } }, { record: session_lost }] }]
orphaned: [{ by: [platform], guard: params.reconcile_orphans, target: review, actions: [{ end_run: { outcome: reclaimed } }, { record: reconciled }, { comment: "Requeued: the card was running with no claim and nothing live behind it." }] }]
session_idle: - by: [platform] guard: "card.last_heartbeat_at != null && now < (card.last_events.heartbeat.payload.until ?? card.last_heartbeat_at + params.heartbeat_quiet_seconds)"
- by: [platform]
guard: "event.turns >= (card.goal_max_turns ?? params.goal_max_turns)"
target: blocked
actions: [{ end_run: { outcome: blocked } }, { assign: { field: block_kind, value: needs_input } }, { record: { kind: blocked, payload: { reason: "its reviewer went idle {{event.turns}} times without deciding; review it", kind: needs_input, source_status: review } } }]
- by: [platform]
actions:
- count: goal_turns
- send_to_actor: "Card {{card.id}} ({{card.title}}) is waiting for you to decide ({{event.turns + 1}} of {{card.goal_max_turns ?? params.goal_max_turns}}).\n{{doors}}"
- record: { kind: decision_asked, payload: { turn: "{{event.turns + 1}}", budget: "{{card.goal_max_turns ?? params.goal_max_turns}}" } }
machine_offline: - by: [platform]
guard: "event.seconds >= params.machine_offline_grace_seconds"
target: blocked
actions: [{ end_run: { outcome: reclaimed } }, { assign: { field: block_kind, value: capability } }, { record: { kind: blocked, payload: { reason: "machine {{event.machine}} has been off the fleet for {{event.seconds}}s", kind: capability, source_status: review } } }]
blocked:
stored_as: blocked
description: waiting on a person, a capability, or a transient cause
always: - guard: "card.last_block_event != 'blocked' && card.counters.failures < (card.max_retries ?? params.failure_limit) && card.parents.all(p, p.status in ['done', 'archived'])"
target: "{{card.resume_status ?? 'ready'}}"
actions: [{ record: promoted }]
on:
unblock: - by: [implementer] description: 'what you waited on arrived: `--reason "<what arrived>"`'
guard: "card.counters.failures < (card.max_retries ?? params.failure_limit)"
target: "{{card.parents.all(p, p.status in ['done', 'archived']) ? (card.resume_status ?? 'ready') : 'todo'}}"
actions: [{ record: unblocked }]
- target: "{{card.parents.all(p, p.status in ['done', 'archived']) ? (card.resume_status ?? 'ready') : 'todo'}}"
actions: [{ reset: [failures] }, { assign: { field: last_failure_error, value: "{{null}}" } }, { record: unblocked }]
block: - by: [implementer, manager]
guard: "event.kind == 'dependency'"
target: todo
actions: [{ assign: { field: block_kind, value: "{{event.kind}}" } }, { record: { kind: dependency_wait, payload: { reason: "{{event.reason}}", kind: dependency, source_status: "{{card.resume_status}}" } } }]
- by: [implementer, manager]
description: 'the block is another: `"<why>" --kind needs_input|capability|transient|dependency`'
actions: [{ assign: { field: block_kind, value: "{{event.kind}}" } }, { record: { kind: blocked, payload: { reason: "{{event.reason}}", kind: "{{event.kind}}", source_status: "{{card.resume_status}}" } } }]
promote:
- validate: [{ expr: "event.force || card.parents.all(p, p.status in ['done', 'archived'])", message: "parents not done (force to override)" }]
target: ready
actions: [{ record: promoted_manual }]
complete:
- validate: [{ expr: "card.parents.all(p, p.status in ['done', 'archived'])", message: "the card waits on parents that are not done" }]
target: done
actions: [{ set_result: "{{event}}" }, { reset: [failures, block_recurrences] }, { assign: { field: last_failure_error, value: "{{null}}" } }, { assign: { field: block_kind, value: "{{null}}" } }, { record: completed }, { check_references: "{{event}}" }]
schedule: [{ target: scheduled, actions: [{ record: scheduled }] }]
scheduled:
stored_as: scheduled
description: set aside until someone brings it back
on:
unblock: [{ target: ready, actions: [{ record: unblocked }] }]
done:
stored_as: done
description: finished
on:
edit: [{ actions: [{ set_result: "{{event}}" }, { record: edited }] }]
reopen: - target: "{{card.parents.all(p, p.status in ['done', 'archived']) ? 'ready' : 'todo'}}"
actions: [{ invalidate_descendants: null }, { record: status }]
archived:
stored_as: archived
description: out of the way
on:
reopen:
- target: "{{card.parents.all(p, p.status in ['done', 'archived']) ? 'ready' : 'todo'}}"
actions: [{ invalidate_descendants: null }, { record: status }]
dispatch:
serve: params.dispatch_in_gateway
tick: params.dispatch_interval_seconds
start: start
eligible: "card.claim_lock == null && card.subtask_of == null && !(run.local && (host.memory_pressure == 'critical' || (host.memory_pressure == 'elevated' && tick.local_started >= 1)))"
order: ["card.status == 'review' ? 1 : 0", "-card.priority", "card.created_at"]
limits:
- description: the home runs this many cards at most across its boards (kanban.max_in_progress)
group: "'home'"
counts: "card.stored_as == 'running'"
max: params.max_in_progress ?? host.max_in_progress
reserve: { for: "card.status == 'review'", slots: 1 }
- description: the board runs this many cards at most (kanban.max_spawn)
group: card.board
counts: "card.stored_as == 'running'"
max: params.max_spawn
- description: the profile's capacity (worker.capacity, else kanban.max_in_progress_per_profile)
group: card.assignee
counts: "card.stored_as == 'running'"
max: lane.capacity ?? params.max_in_progress_per_profile
- description: the machine's capacity; an idle session on a done or blocked card holds its place until closed (ours)
group: card.machine
counts: "card.stored_as == 'running' || card.session_live"
max: "params.machines[card.machine].capacity"
- description: triage sessions started per tick (kanban.auto_decompose_per_tick)
group: "'triage'"
counts: "card.status == 'triaging' && card.started_this_tick"
max: params.auto_decompose_per_tick
notify:
creator:
completed: "✔ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} done — {{card.title}}{{event.summary ? '\n' + cut(event.summary, 300) : ''}}"
blocked: "⏸ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} blocked{{event.reason ? ': ' + cut(event.reason, 300) : ''}}"
gave_up: "✖ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} gave up after {{event.failures}} failures{{event.error ? '\n' + cut(event.error, 300) : ''}}"
crashed: "✖ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}}: its worker exited without completing it; the board will retry"
timed_out: "⏱ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} timed out (max_runtime {{event.limit_seconds}}s); the board will retry"
status: "🔄 {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} → {{event.status}}"
block_loop_detected: "🛑 {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} went to triage: blocked {{event.recurrences}}x for the same cause{{event.reason ? ': ' + cut(event.reason, 200) : ''}}"
review_requested: "👀 {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} ready for review — {{card.title}}{{event.summary ? '\n' + cut(event.summary, 300) : ''}}"
changes_requested: "🛑 card {{card.id}}: review asked for changes: {{cut(event.reason ?? 'reviewer feedback', 300)}}"
session_started: "▶ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} — {{card.title}}: the board started session {{event.session_id}} on {{event.machine}} (pane {{event.pane}}; {{event.address}})."
session_resumed: "▶ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} — {{card.title}}: the board resumed session {{event.session_id}} on {{event.machine}} (pane {{event.pane}}; {{event.address}})."
session_replaced: "▶ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} — {{card.title}}: the board could not resume the card's last session and started session {{event.session_id}} on {{event.machine}} (pane {{event.pane}}; {{event.address}})."
session_adopted: "▶ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} — {{card.title}}: the board found session {{event.session_id}} still live on {{event.machine}} and kept it ({{event.address}})."
session_lost: "✖ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}}: session {{event.session_id}} is gone from {{event.machine}}; the board resumes it, and replaces it from the card only when it cannot."
spawn_failed: "✖ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}}: its session did not start: {{cut(event.error, 300)}}"
worker:
commented: "💬 card {{card.id}}, {{event.author}}:\n{{comment.body}}"
assigned: "card {{card.id}} is now assigned to {{event.assignee ?? 'nobody'}}"
machine_set: "card {{card.id}} now runs on {{event.machine ?? 'no machine'}}"
blocked: "⏸ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} blocked{{event.reason ? ': ' + cut(event.reason, 300) : ''}}"
unblocked: "▶ card {{card.id}} unblocked"
completed: "✔ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} done — {{card.title}}{{event.summary ? '\n' + cut(event.summary, 300) : ''}}"
archived: "card {{card.id}} archived"
changes_requested: "🛑 card {{card.id}}: review asked for changes: {{cut(event.reason ?? 'reviewer feedback', 300)}}"
linked: "card {{card.id}}: dependency linked ({{event.parent}} → {{event.child}})"
unlinked: "card {{card.id}}: dependency unlinked ({{event.parent}} → {{event.child}})"
promoted_manual: "card {{card.id}} promoted to ready by {{event.actor}}"
subtask_added: "card {{card.id}} has a new subtask {{event.subtask}}: {{event.title}}"
session_adopted: "▶ card {{card.id}} — {{card.title}} is running again, with you: carry it on from where it stands (`supercode workflow show {{card.id}}` reads it)."
dependency:
completed: "✔ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} done — {{card.title}}{{event.summary ? '\n' + cut(event.summary, 300) : ''}}"
blocked: "⏸ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} blocked{{event.reason ? ': ' + cut(event.reason, 300) : ''}}"
unblocked: "▶ card {{card.id}} unblocked"
review_requested: "👀 {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} ready for review — {{card.title}}{{event.summary ? '\n' + cut(event.summary, 300) : ''}}"
changes_requested: "🛑 card {{card.id}}: review asked for changes: {{cut(event.reason ?? 'reviewer feedback', 300)}}"
commented: "💬 card {{card.id}}, {{event.author}}:\n{{comment.body}}"
chat:
completed: "✔ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} done — {{card.title}}{{event.summary ? '\n' + cut(event.summary, 300) : ''}}"
blocked: "⏸ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} blocked{{event.reason ? ': ' + cut(event.reason, 300) : ''}}"
gave_up: "✖ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} gave up after {{event.failures}} failures{{event.error ? '\n' + cut(event.error, 300) : ''}}"
crashed: "✖ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}}: its worker exited without completing it; the board will retry"
timed_out: "⏱ {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} timed out (max_runtime {{event.limit_seconds}}s); the board will retry"
status: "🔄 {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} → {{event.status}}"
block_loop_detected: "🛑 {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} went to triage: blocked {{event.recurrences}}x for the same cause{{event.reason ? ': ' + cut(event.reason, 200) : ''}}"
review_requested: "👀 {{card.assignee ? '@' + card.assignee + ' ' : ''}}card {{card.id}} ready for review — {{card.title}}{{event.summary ? '\n' + cut(event.summary, 300) : ''}}"
changes_requested: "🛑 card {{card.id}}: review asked for changes: {{cut(event.reason ?? 'reviewer feedback', 300)}}"