akm docs

Running Workflows

This guide walks through operating a workflow run day to day: starting or continuing one, checking on it, resuming it after it blocks, and abandoning one you no longer want. It assumes you already know what a workflow is; see Workflow Schema for the exact frontmatter and body syntax, and Architecture: The Workflow Engine for what happens under the hood (frozen plans, dispatch, resume-without-replay). For task/schedule-driven workflow runs — akm tasks and the OS scheduler — see Scheduling.

Start or continue a run

akm workflow run <run-id|workflows/ref> starts or continues a persisted run and executes it until completion, failure, verification rejection, interruption, or an explicit invocation limit. Run state is scoped to the current project directory (nearest .akm/config.json, git root, bundle root, or current directory), so the same workflow can run independently in separate projects.

Every new run/start compiles source IR v1 and freezes the durable plan v4 family's current executable format (irVersion: 5) before publishing the run. Markdown .md and GitHub-shaped .yml refs use the same freeze path.

akm workflow run workflows/ship-release --version 1.2.3
akm workflow run workflows/review --changed_files a.ts --changed_files b.ts
akm workflow run <run-id> --max-retries 2 --timeout 10m

Parameter flags must come after the target and exactly match declared params keys. Values are coerced through each parameter's JSON Schema: repeat an array flag, pass an object or whole array as JSON, and use a bare boolean flag for true. There are no hyphen/underscore aliases. Parameters are accepted only while creating a new run — a later invocation against an active run rejects parameter flags.

--max-steps <n> leaves a partial run active after at most n steps. --max-retries <n> retries a failed step on the same run up to n additional times (0 through 100). --timeout <duration> bounds the whole invocation and accepts N, Nms, Ns, or Nm; bare N is milliseconds. A timeout or signal abort releases the run's lock without advancing the active step, so the run remains resumable. Failed, gate-rejected, timed-out, and interrupted runs exit nonzero.

The run freezes its plan, exact models, execution limits, parameter snapshot, and verifier selection at creation — edits to source or config do not alter an in-flight run. See Architecture: Frozen plans for why.

Check status

akm workflow status shows the full run state — all step statuses, notes, and evidence — for a given run ID or workflow ref.

akm workflow status <run-id>
akm workflow status workflows/ship-release
# When given a ref, resolves to the most-recently-updated run in the current scope

Use this to inspect where a run is after a context window break, or to verify all steps completed cleanly before closing a PR.

--units — per-unit diagnostics. For an orchestrated run, add --units to also list the run's journaled unit rows — each unit's id, status, failure_reason, and any result/error diagnostic text the row carries:

akm workflow status <run-id> --units

This is a diagnostic surface, deliberately kept out of the deterministic artifact graph. A step's promoted artifact (what steps.x.output resolves to, and what a gate judges) keeps only a failed unit's structured failure_reason — never the raw error text — so step evidence stays reproducible across resumes. When you need the human-facing why behind a failure, --units reads the unit journal directly and shows it without ever feeding that text back into an artifact or input hash.

Child runs

A step that composes another workflow (see Workflow Schema: Child workflows) drives that child inline when the composing step runs. akm workflow status on a run that composes children renders a children: tree after its steps: block:

akm workflow status <run-id>
steps:
  - build [work] (completed)
  - dispatch [dispatch] (completed)
children:
  - ✓ 4f2b1a9c workflows/leaf [completed] (step "dispatch")

Each row shows the child's run id, its workflow ref, its status, and which of the parent's steps spawned it; a blocked child also shows its resume command. Nesting reflects composition depth — a child that itself composes a grandchild shows its own indented children: rows.

Recovering a blocked child. A blocked child blocks the composing step (and the parent run) too — akm will not resume a child for you. Resume the child first, then resume and re-run the parent; re-driving the parent is what advances it, because that re-enters the composing step and drives the now-resumed child:

akm workflow resume <childRunId>
akm workflow resume <parentRunId>
akm workflow run <parentRunId>

Nested blocks (composition depth 2+). The three-command sequence above clears a block exactly one level deep. When a grandchild blocks — a root workflow composes a child, and that child composes the grandchild that actually blocks — re-driving the root does not cascade down into re-driving the still-blocked grandchild: a composing step never re-drives a child whose own status is already blocked, so re-running the root just re-observes the child's block and re-blocks the root the same way, without ever reaching the grandchild. Resume every blocked run in the chain, deepest first, then re-run only the root:

akm workflow resume <grandchildRunId>
akm workflow resume <childRunId>
akm workflow resume <rootRunId>
akm workflow run <rootRunId>

The status tree prints resume/then commands on each blocked node, but those two commands alone only ever name that node and the root — they do not enumerate an intermediate chain. Read the tree top to bottom and resume every blocked row you see (deepest first) before re-running the root.

Child runs are hidden by default. akm workflow list excludes child runs so the list stays a view of the runs you started; pass --children to include them:

akm workflow list              # child runs excluded
akm workflow list --children   # child runs included

A child run id always works directly, listed or not — akm workflow status <childRunId>, resume, abandon, and run all operate on it exactly like any other run.

List runs in scope

akm workflow list shows workflow runs in the current scope.

akm workflow list              # All runs in this scope (any status)
akm workflow list --active     # Only status=active (executable) runs
akm workflow list --ref workflows/ship-release  # Runs for a specific workflow

--active filters to runs whose status is exactly active — currently executable work. A blocked run (parked awaiting a human akm workflow resume) or a failed/completed run is not active and is excluded, so a script that treats --active output as runnable never picks one up. Blocked runs remain listed by the unfiltered akm workflow list with their blocked status.

Example: see what is in flight

akm workflow list --active
# → lists runs by workflow ref, status, currentStepId, and updatedAt

Resume a blocked or failed run

akm workflow resume <run-id>

Flips a blocked or failed run back to active. Completed runs cannot be resumed. Use akm workflow list to find runs by status. Once resumed, akm workflow run <run-id> continues it — units already journaled as completed are skipped, not re-run (see Architecture: Resume skips completed units).

A resume consumes the frozen plan and journaled attempts; it does not re-read configuration or the index, and reads the authored workflow only to warn once when it changed since the run was frozen (the run continues on the frozen plan; akm workflow run <ref> --new picks up the edit). A stored plan this akm cannot decode is abandoned with a message naming how to start a new run.

Abandon a run

akm workflow abandon <run-id>

Marks a run failed so it stops counting as active. This only changes the run's status — resume can still reopen it later if you change your mind.

Follow a run's events

There is no akm workflow watch (0.9.0: dropped — a foreground polling daemon in a one-shot CLI). akm log --run <run-id> reads the same workflow_* / workflow_unit_* events from the general append-only events stream: --since '@offset:<id>' gives a durable row-id cursor a cooperating process can poll from, in place of watch --stream's in-process loop.

akm workflow run <run-id> &                                  # engine in one shell
akm log --run <run-id> --since '@offset:0'                   # backlog so far
akm log --run <run-id> --since '@offset:<nextOffset>'        # poll for more, from the prior call's nextOffset

Event metadata is ids/status/enums only — never workflow-authored content — so following a run's events is safe to pipe into logs or dashboards.

Security: workflow sources are executed code

Workflow steps that include shell commands run with the full filesystem and network access of the user invoking akm workflow run — same as if the user had typed those commands in their shell. There is no sandbox and no separation between trusted and untrusted workflows. An exec unit's environment is narrowed to an allowlist by default (Workflow Schema), which bounds accidental exposure of unrelated credentials — but a command that runs at all can still read those credentials off disk, so it is a hygiene boundary, not a containment one. AKM directly orchestrates workflows as a defined execution surface — it does not blindly execute arbitrary indexed content — but that orchestration still runs the workflow's own declared shell commands with your full access once you choose to run it.

This is by design: a workflow is a runbook authored by you or by a bundle maintainer you trust. The flexibility of "run any shell command, read any file, hit any network" is what makes workflows useful as automation.

The consequence is that you should treat workflow sources the same way you treat package dependencies:

If you operate a CI runner or shared host where untrusted workflows might be executed, scope the process: a dedicated user account with no secrets in its environment, ephemeral working directory, and a network/filesystem allowlist enforced outside akm.

See also