Run State Management
Contract
Iron law: no phase transition without an activity write — state.completed_activities[] is the source of truth for /flow:resume, and a missing activity makes resume skip ahead and lose work. Invoked when FLOW_RUN_STATE=create (gated by flow.runtime.enabled) by /flow:start, /flow:debug, /flow:address, /flow:review, /flow:merge, /flow:release at command entry (create), at every phase boundary (activity), and at completion (terminal transition); /flow:pr appends activities to the active start-issue run; /flow:resume reads its output. Returns .flow/runs/<id>/run.yaml, activities/<NNN>-<name>.yaml, events.jsonl, and a workflow-run journal artifact. Permitted skips: only when flow.runtime.enabled is false — then nothing under .flow/ is written.
Inputs
The invoking command MUST pass:
- Workflow id —
start-issue | debug | address-pr | review-pr | merge-pr | release (matches plugins/flow/workflows/<id>.workflow.yaml).
- Run id —
<ISO-8601-compact-timestamp>-<target-slug>, e.g. 2026-05-20T143000Z-issue-42.
- Context — repo, branch, issue/pr number, linked journal path, linked goal id (or
null).
- Phase — initial phase id (
preflight at creation; the workflow's phase order thereafter).
Outputs
.flow/runs/<id>/run.yaml — FlowRun conforming to schemas/v1/run.schema.json.
.flow/runs/<id>/activities/<NNN>-<name>.yaml — one FlowActivity per phase boundary, schemas/v1/activity.schema.json.
.flow/runs/<id>/events.jsonl — line-per-event ledger.
workflow-run artifact in the linked decision journal (bin/journal-record.sh --type workflow-run), updated with the final status at the terminal transition.
Exact document shapes and the per-workflow phase-order table: references/run-state-templates.md.
Workflow
- Create the FlowRun at command entry: write
run.yaml (state.status: active, current_phase = initial phase, completed_activities: [], events: [run_started]) by direct file write — race-free because the directory does not yet exist — and emit the workflow-run journal artifact with status=active.
- Record an activity at every phase boundary (and significant sub-steps): compose the FlowActivity YAML to a temp file, then
bin/flow-record-activity.sh --run-id <id> --activity-file <path>. The helper assigns the sequence number, validates against the schema, writes atomically (O_NOFOLLOW + flock + tempfile+rename), and appends to events.jsonl.
- Update
state.current_* after each activity: advance current_phase at a boundary, set current_activity, append the recorded id to completed_activities[]. Read-merge-write through bin/_journal_atomic.py (acquire_lock(run.yaml.lock) + atomic write) — never a bare overwrite.
- Terminal transition when the command ends:
state.status → completed (verdict PASS / action succeeded), blocked with blocked_reason (verdict FAIL or session ended mid-workflow), failed, or cancelled; then re-emit the workflow-run artifact with the final status.
- SessionEnd (
hooks/scripts/session-end-state.sh, not this skill): appends a session_end event to each active run's events.jsonl and prints flow: N active FlowRun(s) persisted — it does not mutate run.yaml; status changes are the user's decision via /flow:resume.
Rules
- Never write
run.yaml outside the helper except the initial create — concurrent updates need flock.
- Never advance
state.current_phase without writing an activity.
- Never mark
state.status: completed with an empty completed_activities[].
- Readers of
events.jsonl MUST skip un-parseable trailing lines (atomic at write, tolerant at read).
- Never auto-resume a
blocked run without surfacing blocked_reason first.
Reuse map
plugins/flow/skills/autonomous-workflow/SKILL.md — phase structure source of truth; this skill materializes its phase boundaries.
plugins/flow/bin/flow-record-activity.sh — atomic activity writer.
plugins/flow/bin/_journal_atomic.py — acquire_lock, _atomic_write for run.yaml updates.
plugins/flow/schemas/v1/run.schema.json, activity.schema.json — document schemas.
plugins/flow/references/decision-journal-schema.md — workflow-run and run-state-transition artifact rows.
plugins/flow/references/flow-runtime-state.md — .flow/ layout, gitignore policy, resumability.
1---2name: run-state-management3description: Manage FlowRun state at `.flow/runs/<ISO-timestamp-id>/run.yaml` — create runs at command entry, write activity records via `bin/flow-record-activity.sh` at phase boundaries, transition `state.status` (active → completed | blocked | cancelled), and persist resumable next-action hints to `events.jsonl`. Use when a flow command begins (creates the run), when a phase boundary completes (writes an activity), or when SessionEnd needs to mark a resumable next action. This skill MUST be consulted because runs without recorded activities cannot be resumed — `/flow:resume` reads `state.completed_activities[]` to identify the next safe action; an empty array forces the user to start over.4---56# Run State Management78## Contract910Iron law: no phase transition without an activity write — `state.completed_activities[]` is the source of truth for `/flow:resume`, and a missing activity makes resume skip ahead and lose work. Invoked when `FLOW_RUN_STATE=create` (gated by `flow.runtime.enabled`) by `/flow:start`, `/flow:debug`, `/flow:address`, `/flow:review`, `/flow:merge`, `/flow:release` at command entry (create), at every phase boundary (activity), and at completion (terminal transition); `/flow:pr` appends activities to the active `start-issue` run; `/flow:resume` reads its output. Returns `.flow/runs/<id>/run.yaml`, `activities/<NNN>-<name>.yaml`, `events.jsonl`, and a `workflow-run` journal artifact. Permitted skips: only when `flow.runtime.enabled` is `false` — then nothing under `.flow/` is written.1112## Inputs1314The invoking command MUST pass:15161. **Workflow id** — `start-issue | debug | address-pr | review-pr | merge-pr | release` (matches `plugins/flow/workflows/<id>.workflow.yaml`).172. **Run id** — `<ISO-8601-compact-timestamp>-<target-slug>`, e.g. `2026-05-20T143000Z-issue-42`.183. **Context** — repo, branch, issue/pr number, linked journal path, linked goal id (or `null`).194. **Phase** — initial phase id (`preflight` at creation; the workflow's phase order thereafter).2021## Outputs22231. `.flow/runs/<id>/run.yaml` — FlowRun conforming to `schemas/v1/run.schema.json`.242. `.flow/runs/<id>/activities/<NNN>-<name>.yaml` — one FlowActivity per phase boundary, `schemas/v1/activity.schema.json`.253. `.flow/runs/<id>/events.jsonl` — line-per-event ledger.264. `workflow-run` artifact in the linked decision journal (`bin/journal-record.sh --type workflow-run`), updated with the final status at the terminal transition.2728Exact document shapes and the per-workflow phase-order table: `references/run-state-templates.md`.2930## Workflow31321. **Create the FlowRun** at command entry: write `run.yaml` (`state.status: active`, `current_phase` = initial phase, `completed_activities: []`, `events: [run_started]`) by direct file write — race-free because the directory does not yet exist — and emit the `workflow-run` journal artifact with `status=active`.332. **Record an activity at every phase boundary** (and significant sub-steps): compose the FlowActivity YAML to a temp file, then `bin/flow-record-activity.sh --run-id <id> --activity-file <path>`. The helper assigns the sequence number, validates against the schema, writes atomically (O_NOFOLLOW + flock + tempfile+rename), and appends to `events.jsonl`.343. **Update `state.current_*`** after each activity: advance `current_phase` at a boundary, set `current_activity`, append the recorded id to `completed_activities[]`. Read-merge-write through `bin/_journal_atomic.py` (`acquire_lock(run.yaml.lock)` + atomic write) — never a bare overwrite.354. **Terminal transition** when the command ends: `state.status` → `completed` (verdict PASS / action succeeded), `blocked` with `blocked_reason` (verdict FAIL or session ended mid-workflow), `failed`, or `cancelled`; then re-emit the `workflow-run` artifact with the final status.365. **SessionEnd** (`hooks/scripts/session-end-state.sh`, not this skill): appends a `session_end` event to each active run's `events.jsonl` and prints `flow: N active FlowRun(s) persisted` — it does not mutate `run.yaml`; status changes are the user's decision via `/flow:resume`.3738## Rules3940- Never write `run.yaml` outside the helper except the initial create — concurrent updates need flock.41- Never advance `state.current_phase` without writing an activity.42- Never mark `state.status: completed` with an empty `completed_activities[]`.43- Readers of `events.jsonl` MUST skip un-parseable trailing lines (atomic at write, tolerant at read).44- Never auto-resume a `blocked` run without surfacing `blocked_reason` first.4546## Reuse map4748- `plugins/flow/skills/autonomous-workflow/SKILL.md` — phase structure source of truth; this skill materializes its phase boundaries.49- `plugins/flow/bin/flow-record-activity.sh` — atomic activity writer.50- `plugins/flow/bin/_journal_atomic.py` — `acquire_lock`, `_atomic_write` for run.yaml updates.51- `plugins/flow/schemas/v1/run.schema.json`, `activity.schema.json` — document schemas.52- `plugins/flow/references/decision-journal-schema.md` — `workflow-run` and `run-state-transition` artifact rows.53- `plugins/flow/references/flow-runtime-state.md` — `.flow/` layout, gitignore policy, resumability.