# Migrating from akm 0.9.1 to 0.9.2

AKM 0.9.2 makes **task source v4 the only executable task source** and
introduces peer Markdown and GitHub-shaped YAML workflow sources, compiled
through a shared source IR to durable plan **`irVersion` 5**. Authored
task-v2 and task-v3 files require an explicit, fail-closed migration —
`akm migrate apply` now runs both generations in one pass. Durable workflow
plans created by 0.9.1, and any plan frozen before this release's
`irVersion` 5, cannot resume in 0.9.2 — see
[Before you upgrade](#before-you-upgrade) below for the exact recovery
steps; no run data is lost.

The released 0.9.1 state ledger already includes historical migration 018, so
the normal 0.9.1→0.9.2 database step is additive and automatic. If a pre-release
database has an exact prefix before 018, ordinary commands stop before its
destructive dead-lane cleanup. Run `akm migrate apply` (or `akm upgrade`, which
runs it after its install step regardless of whether an install happened): AKM
creates and verifies a sibling pre-018 SQLite safety copy before applying that
immutable released migration. Its locked ledger recheck, snapshot, and
migration share one writer-exclusion window, so another WAL writer cannot
commit between the copy and 018. Unknown or divergent ledgers remain
unsupported. See [Bundling akm](../integration/bundling-akm.md) for the full
migration contract.

## Before you upgrade

1. **Check for workflow runs in flight** and either let them finish or
   abandon them deliberately — a run frozen at a pre-`irVersion`-5 plan
   cannot `resume`/`next`/`complete`/`run` after the upgrade (it can still
   be inspected and abandoned). See
   [Pre-`irVersion`-5 stored plans](#pre-irversion-5-stored-plans-complete-or-abandon-before-upgrading)
   below for the exact recovery sequence if you upgrade with runs still
   active.

   ```sh
   akm workflow list --active
   ```

2. **Commit or otherwise snapshot your authored task and workflow bundles**
   before running any migrator — both the task-v2→v3 and v3→task-source-v4
   generations back up every file they touch, but a snapshot at the repo
   level is cheap insurance regardless.

3. **Do not downgrade below 0.9.2 once you've recorded task history on
   it.** Task run history's `target.kind` vocabulary changed in this
   release, tagged with a metadata marker a pre-0.9.2 akm's decoder does
   not recognize — a pre-0.9.2 akm reading a row this release (or later)
   wrote **throws** rather than misreading it. Upgrading is always safe (a
   0.9.2-or-later akm reads older rows correctly); only downgrading, or
   otherwise pointing an older binary at a `state.db` a newer one has
   already written to, is the hazard. See
   [Task history result vocabulary](#task-history-result-vocabulary-targetkind)
   below.

Everything else below can happen after the binary upgrade, at your own
pace: task sources keep failing closed with an actionable hint until you
migrate them (nothing silently breaks or half-runs), and pre-`irVersion`-5
runs stay inspectable indefinitely.

## Task sources

akm 0.9.2 has shipped three task source generations. Only the newest,
**task source v4**, is accepted by `src/` in this release.

**Before — 0.9.1 task v2:**

```yaml
version: 2
name: Contract review
schedule: "15 4 * * 1"
prompt: Review the execution contract.
enabled: false
engine: reviewer
model: exact-model-id
timeoutMs: 45000
redact: [REVIEW_TOKEN]
```

**An intermediate generation — task v3** (shipped earlier in the 0.9.x
line; also no longer accepted):

```yaml
version: 3
name: Contract review
uses: akm/command
with:
  content: Review the execution contract.
akm:
  schedule: "15 4 * * 1"
  enabled: false
  engine: reviewer
  model: exact-model-id
  timeout: 45000
  redact: [REVIEW_TOKEN]
```

**After — 0.9.2 task source v4**, the migration destination:

```yaml
version: 4
name: Contract review
uses: akm/command
with:
  content: Review the execution contract.
schedule:
  - cron: "15 4 * * 1"
engine: reviewer
model: exact-model-id
timeout: 45000
redact: [REVIEW_TOKEN]
```

What changed between v3 and v4 (v2's changes to v3 are unchanged from
earlier 0.9.x releases and are summarized further down):

- **The `akm:` options bag is gone.** Every field it carried is a top-level
  key instead: `akm.description` → `description`, `akm.when_to_use` →
  `when_to_use`, `akm.tags` → `tags`, `akm.agent` → `agent`, `akm.engine` →
  `engine`, `akm.model` → `model`, `akm.inference` → `inference`,
  `akm.outputSchema` → `output`, `akm.tools` → `tools`, `akm.timeout` →
  `timeout`, `akm.redact` → `redact`, `akm.maxSteps` → `maxSteps`,
  `akm.maxRetries` → `maxRetries`.
- **The `on:` trigger block is gone**, and with it the second scheduling
  syntax. `akm.schedule` → the string-shorthand top-level `schedule:`;
  `on.schedule` (a list of `{cron}` records) → the list-form `schedule:`,
  with every ordinal preserved; `on.workflow_dispatch` (with no
  `on.schedule`) **drops silently** — a v3 document whose only trigger was
  manual dispatch simply has no `schedule:` key in v4, since every v4 task
  is always runnable manually with `akm task run` regardless of whether it
  has a schedule. The migrator emits an informational notice when it makes
  this specific drop, naming the file.
- **Scheduling is now optional.** A v4 document with no `schedule:` at all
  parses, runs with `akm task run`, and is silently skipped by
  `akm task sync` (zero bindings, zero failures) — it never has to declare
  a trigger just to be a valid document.
- **Source-owned enablement is removed.** Current task source v4 carries no
  `enabled` field. `akm-migrate` removes v2/v3/v4 source flags and seeds the
  host-local `scheduler.enabled` allow-list only from native scheduler
  bindings it can prove are currently enabled. A source cannot activate
  itself merely by being installed from a bundle.
- **Typed `inputs:` with defaults and `required:`.** v4 tasks can declare
  named, bounded-JSON-Schema parameters, each optionally carrying a
  `default` or `required: true` (mutually exclusive). `akm task run`
  accepts one exact-name flag per declared input; a `schedule:` entry can
  supply literal `inputs:` too. A `required: true` input may not carry a
  `default:`, and a scheduled firing supplies no flags, so every `schedule:`
  entry must name a value for each such input — a document that leaves one
  unsatisfied is rejected at parse (`TASK_SOURCE_INVALID`) instead of
  installing a schedule that fails at every fire. See
  [Tasks: Typed inputs and output](../reference/tasks.md#typed-inputs-and-output)
  for the full grammar.
- **A single bounded `output:` schema** replaces v3's `akm.outputSchema`.
- **The GitHub-action `uses:` target is removed outright.** v3 recognized
  (and always rejected before dispatch) an `owner/repo[/path]@ref` spelling
  such as `owner/repo@v1`; v4 does not recognize that shape as a `uses:`
  target at all — it fails at parse alongside every other unrecognized
  value. See
  [GitHub Action locators are no longer recognized anywhere](#github-action-locators-are-no-longer-recognized-anywhere)
  under Workflow cutover.
- **`with:` narrows to `uses: akm/command` only.** Every other target
  (`commands/`, `scripts/`, `workflows/`, `run:`) uses `inputs:` for typed
  parameters instead.
- `akm task add` now authors task source v4 directly (`--params` renders
  typed `inputs:` with `default:` values instead of a `with:` bag;
  `--disabled` now requires `--schedule`).

See [Tasks](../reference/tasks.md) for the complete grammar reference,
including the retired v3 grammar (kept purely so you can read an old file
while migrating it).

## The migration procedure

`akm migrate status` and `akm migrate apply [--dry-run]` run **both**
generations in one pass against the same tree: task-v2 → task-v3 first,
then task-v3 → task source v4 against the resulting files. Each generation
keeps its own lock, `O_EXCL` backup, prevalidation, TOCTOU recheck, atomic
replace, and reverse rollback — a file blocked in generation 1 does not
stop generation 2 from converting files that are already `version: 3`.

1. Commit or otherwise snapshot your authored bundles (see
   [Before you upgrade](#before-you-upgrade)).
2. Preview the pure migration plan. This performs no source write:

   ```sh
   akm migrate apply --dry-run
   ```

3. Review every input file. The report is stable and names an exact status
   for each file, per generation: `changed`, `skipped`, or `blocked`.
4. Resolve every blocked file manually, then preview again.
5. Apply the same planner:

   ```sh
   akm migrate apply
   ```

For each `changed` file, AKM validates the complete replacement bytes
before writing. At apply time it rechecks the planned generation, backs up
that file immediately before replacement, preserves its file mode, and
then installs the validated replacement. A race or validation failure
stops instead of applying stale output.

### v2 → v3 blocked cases

A v2 argv array has no unambiguous v3 shell-string equivalent:

```yaml
version: 2
schedule: "@daily"
command: [node, scripts/release.js, "--exact value"]
```

Shell-sensitive strings — assignments, shell builtins, reserved words, and
similar constructs — are also blocked when translating them would invent
shell semantics. The original bytes remain untouched, and `akm migrate
status`/`apply` names the block as `argv-array-has-no-portable-shell-string`
(or a sibling shell-safety reason) rather than guessing: this is manual
conversion, not a case the migrator can be re-run to fix. Rewrite the file
by hand using this field mapping:

| v2 | v4 |
|---|---|
| `command:` (array, argv style) | `run:` (string) plus `shell:` |
| `timeoutMs:` | `timeout:` |
| `enabled:` (document level) | removed — use host-local `scheduler.enabled` (`akm task enable` / `disable`) |
| `schedule:` (cron string) | still accepted as a bare string, or as the list form above |

validate it, and rerun the preview. You can either write the replacement
directly as `version: 4` (generation 1 then reports it `already-v4` and
leaves it alone) or as a `version: 3` `run:` string, letting the second
generation carry it the rest of the way to v4 in the same `akm migrate
apply` run.

### Migrating task v3 to task source v4

The second generation converts an eligible `version: 3` task source to
`version: 4`. It translates structure, never intent: it never invents an
`inputs:` declaration on a file's behalf, regardless of how inferable a
`with:` value's shape looks — declaring `inputs:` (and rewriting a step to
bind it) is left to the person editing the migrated file by hand.

Common blocked reasons and what to do about each:

| Reason | Meaning | Fix |
|---|---|---|
| `github-action-target-removed` | The task's `uses:` is a GitHub Action locator (`owner/repo[/path]@ref`); that spelling has no task source v4 equivalent. | Rewrite the target as `commands/`, `scripts/`, `workflows/`, or `akm/command` by hand. |
| `with-on-non-command-target` | A `with:` block is authored on a target other than `uses: akm/command`. | Declare `inputs:` on the task instead; a workflow step composing it binds them with its own `with:`. |
| `ambiguous-scheduling-source` | The document declares both `akm.schedule` and `on:`. | Pick one; the migrator will not guess which one wins. |
| `read-only-source` | The owning source or file is not writable. | Move or re-source the file somewhere writable, or edit it by hand. |
| `invalid-v3-task` | The v3 document itself is structurally invalid (unknown fields, missing selector, malformed trigger, etc). | Fix the underlying v3 document first — the migrator translates structure, it does not repair it. |

The migrator is also installed as its own executable, `akm-migrate`, with the
same `status` / `apply [--dry-run]` surface as `akm migrate`. A tree that is
already all `version: 3` simply reports the first generation as current and
runs this one.

## Task history result vocabulary (`target.kind`)

This is about **run history**, not task source files — nothing here is a
document you author or migrate. `akm task history` and `akm task run`'s
result envelope both carry a `target.kind` field, and its vocabulary
changed:

| Old (0.9.1) | New (0.9.2) | Meaning |
|---|---|---|
| `"prompt"` | `"command"` | A prepared command dispatched to an agent/LLM engine. |
| `"command"` (shared) | `"shell"` or `"script"` | A native shell or script execution — previously one shared `"command"` label for both. |

`"prompt"` mislabeled an LLM-routed dispatch as a literal prompt, and one
shared `"command"` conflated two materially different execution shapes.
0.9.2 renames the vocabulary going forward and, at the same time, adds a
per-row marker (`targetVocab: 2`, stored in each row's metadata) so a
reader can always tell which generation a row belongs to.

**The read side is a permanent legacy mapping, not a one-time conversion.**
`task_history` is an append-only log — there is no "convert existing rows
in place" step, and there never will be. `akm task history` keeps reading
BOTH generations correctly forever: a row with no `targetVocab` marker
(written before this release) is read with the OLD meaning — `"prompt"` →
`{kind: "command", engine}`, `"command"` → `{kind: "shell"}` — and a row
carrying `targetVocab: 2` is read with the NEW meaning directly. You do not
need to do anything for your existing history; this mapping is built in and
will not be removed.

**Mixed-fleet ordering hazard (one-way, hard failure).** The metadata
decoder rejects any field it does not recognize — it is a closed allowlist,
not a permissive parser that ignores extras. A **pre-0.9.2** akm's decoder
does not have `targetVocab` in that allowlist, because the field did not
exist yet. If a task's history lives in a `state.db` that both a 0.9.2 (or
later) akm and a pre-0.9.2 akm read — for example, a global install and a
pinned `npx akm@<old>` pointed at the same state directory, or a downgrade
— the OLDER binary throws `invalid task_history metadata_json: unknown
fields: targetVocab` (an uncaught error, not a handled `UsageError`) the
first time it tries to read a row a 0.9.2-or-later akm wrote. A
**0.9.2-or-later** akm has no such problem: it reads a legacy (unmarked)
row correctly using the mapping above, so upgrading is always safe in that
direction. Only downgrading, or otherwise pointing an older binary at a
state directory a newer one has already written to, is the hazard. Keep
one akm version reading a given `state.db` at a time; do not alternate
versions against the same state directory, and do not downgrade below
0.9.2 once a 0.9.2-or-later akm has recorded task history there.

## Harness id rename: `claude-code` -> `claude`

0.9.2 also renamed the Claude Code harness id from `claude-code` to
`claude` — the id used for both agent dispatch and the per-session
extraction ledger (`state.db`'s `extract_sessions_seen.harness` and
`workflow_runs.agent_harness`). The 0.9.2 release did not carry a state
migration for this rename, so any row a pre-0.9.2 akm wrote stayed
keyed under `claude-code`, invisible to anything querying by the new
name — a script or dashboard filtering `extract_sessions_seen` or
`workflow_runs` on `harness = 'claude-code'` (or
`agent_harness = 'claude-code'`) sees those rows disappear from that
query, not deleted, once you're on a release carrying the 0.9.12 fix.
0.9.12 adds state migration
`027-extract-sessions-seen-harness-rename`, which runs automatically
on the next managed `state.db` open (no separate command needed) and
renames every such row to `claude` in place — conflict-tolerant
against a session already recorded under `claude`, which is kept as
the authoritative row. After upgrading to 0.9.12 or later, re-point
any external query at `harness = 'claude'` / `agent_harness = 'claude'`.

## Workflow cutover

Markdown `.md` and GitHub-shaped `.yml` are peer workflow source formats in
0.9.2 and compile to source IR version 1. New workflow starts freeze durable
plan **`irVersion` 5**, the sole executable plan format. A resume does not
re-read authored workflow/command/agent source, configuration, model maps, or
the asset index.

The `inherit_env` removal is breaking because every new start rejects it.
Use named environment bindings and `pass_env` as the bounded replacement for fixed, secret, and per-machine values.
There is no compatibility reader for the historical flag.

The GitHub-shaped source format is intentionally local and bounded: **AKM
YAML uses a familiar GitHub-step-shaped syntax but is an AKM workflow
format, executed by AKM's native engine.** It accepts schedule and empty
`workflow_dispatch` triggers, `runs-on: [self-hosted]`, and the documented
local step subset. Full expressions, contexts, remote/local or Docker
actions, service-event automation, and arbitrary runners remain
unsupported. A step composing another workflow (`uses: workflows/<ref>`, or a
task whose own target is a workflow) is new in 0.9.2 — see
[Child workflows](#child-workflows) below.

### Multi-job YAML is rejected at the adapter boundary

A GitHub-shaped document whose `jobs:` map does not contain exactly one job
now fails to compile at all — it never reaches lint, plan, or run as a
partially-valid document. Before this release, a multi-job document parsed
and ordered its jobs cleanly, and was refused only much later, in two
different places, with two different shapes:

```
# 0.9.1: parsed clean, then refused at freeze with a bare thrown error
Multi-job workflow cannot execute until job boundaries and needs have a
durable runtime representation.
```

```
# 0.9.2: refused at compile, with the offending job count and a `line`
AKM workflow YAML requires exactly one job; this document declares 2.
AKM's YAML is an AKM workflow format executed by AKM's native engine, not
GitHub Actions — split the jobs into separate workflows.
```

surfaced from `akm workflow run` (and `akm workflow plan`) as `UsageError`
code `COMPOSITION_INVALID`, exit 2. **Fix:** split a multi-job document into
separate single-job workflows and compose them with a child-workflow step
(`uses: workflows/<ref>`) — see [Child workflows](#child-workflows).

### GitHub Action locators are no longer recognized anywhere

A workflow step's `uses: owner/repo[/path]@ref` (e.g.
`uses: actions/checkout@v4`) used to be recognized and rejected with a
locator-specific message:

```
# 0.9.1
Remote action acquisition is out of scope for "actions/checkout@v4".
```

In 0.9.2 the locator grammar itself is gone from native classification; the
same value now fails the same way any other unrecognized `uses:` shape does
— the canonical target-ref classifier's own rejection:

```
# 0.9.2
Target ref "actions/checkout@v4" must be a canonical commands/, scripts/,
tasks/, or workflows/ asset ref.
```

with code `unsupported-uses-target`. Nothing acquired or executed a remote
action in any akm release, so this is a message and classification change,
not a capability removal. A task's own `uses:` GitHub Action locator fails
the same way, at parse, with `TASK_SOURCE_INVALID` — see
[Task sources](#task-sources) above. The migrator still names the target
explicitly when it blocks a v3 → v4 conversion
(`github-action-target-removed`).

### Pre-`irVersion`-5 stored plans: complete or abandon before upgrading

Every 0.9.1 (and pre-P3a 0.9.2-alpha) durable plan was frozen at an older
`irVersion`. Upgrading does not delete or migrate those runs, but it does
retire them as **executable**:

- `akm workflow status <id>`, `akm workflow list`, and
  `akm workflow abandon <id>` keep working exactly as before — nothing about
  those runs is deleted, and abandoning one leaves its step spine untouched.
- `akm workflow resume`, `next`, `complete`, and a bare `run` against that
  run id now fail closed with:

  ```
  Workflow run <id> was frozen as workflow plan irVersion <n>; pre-irVersion-5
  plans cannot execute after the 0.9.2 upgrade. Complete them before upgrading,
  or run 'akm workflow abandon <id>' and start a new run from the authored
  workflow. 'akm workflow status' and 'akm workflow list' still work on this run.
  ```

  as a `UsageError` with code `WORKFLOW_IR_VERSION_UNSUPPORTED`, exit 2.

There is no second executor and no compatibility replay layer for an old
plan version — a blocked run's only way forward is a fresh start.

**Before upgrading**, check for runs in flight and let them finish, or
abandon them deliberately (see [Before you upgrade](#before-you-upgrade)).

**After upgrading**, if a run is blocked by this policy, recover it with the
two-command sequence the error message itself names:

```sh
akm workflow abandon <id>
akm workflow run <ref>
```

No data is lost either way: the blocked run's row, its step spine, and its
journaled events all remain readable through `akm workflow status`/`list`
indefinitely — only `resume`/`next`/`complete`/`run` against that specific
run id are refused.

## `with:` on a task-composed step now binds — or rejects

A workflow step's `uses: tasks/<ref>` target with an authored `with:` mapping
used to decode without error and then have that mapping silently dropped when
the workflow was frozen — the authored inputs never reached the task, with no
error and no warning. 0.9.2 makes this fail closed, and, where the target
supports it, actually deliver the mapping:

- If the target task source declares `inputs:` (task source v4), `with:`
  now **binds** them — a literal value, or a `{from: "steps.<id>.output…"}`
  reference resolved just before the unit dispatches. An unknown key, a
  missing required input, or a reference to a step that doesn't exist
  earlier in the job fails at freeze with `UsageError` code
  `INPUT_BINDING_INVALID`. (The reference grammar also accepts
  `{from: "params.<name>"}`, naming a declared param of the *composing*
  workflow — but a composing step is only authorable in a GitHub-shaped
  document, whose root keys can never include `params:`, so that form is
  not reachable in this release. `{from: "steps.<id>.output…"}` is the one
  reference form you can actually use today.)
- If the target task declares **no** `inputs:` at all (a `version: 4` task
  with no `inputs:` key), freezing the step now throws

  ```
  Workflow step <id> cannot pass with: to task target <ref>; <ref> declares no inputs.
  ```

  as a `UsageError` with code `COMPOSITION_INVALID` (exit 2). This fires for
  any authored `with:` shape that survives decode — including an empty
  mapping (`with: {}`) — not just a non-empty one.
- The same `COMPOSITION_INVALID` rejection now also fires for a `with:`
  authored on `uses: commands/<ref>` or `uses: scripts/<ref>` — neither is a
  binding surface, and this authored mapping used to be silently dropped too.
  Remove `with:` from any such step:

  ```diff
     - id: dispatch
       uses: commands/review
  -    with:
  -      scope: all
  ```

A step targeting any task with no `with:` at all is unaffected and keeps
freezing exactly as before. `with:` on `uses: akm/command` is a different,
unaffected path — it is still required to supply the builtin action's
arguments and continues to work as documented. `with:` on a
**child-workflow** target (direct or task-wrapped) is different again: it
binds the child's declared `params:` — see [Child workflows](#child-workflows).

**Resume identity.** A reference binding's *resolved* value is part of the
unit's durable input-identity hash, alongside the reference's own text
(`from: "steps.discover.output.files"`) inside the frozen target: any unit
whose target carries bindings hashes the effective values it actually
receives (the same values delivered through the `## Task inputs` prompt
block and `AKM_TASK_INPUTS`). In the ordinary case this changes nothing —
a frozen plan never re-reads source, so the same reference resolves to the
same journaled upstream output on every attempt, the recomputed hash
matches, and a resume reuses completed rows exactly as before. What it
closes is the stale-reuse hole: journaled `workflow_run_units` rows are
still read-only durable state, and if an earlier, already-completed step's
journaled output is altered before a resume, a later bound unit now
recomputes a *different* input hash and the run fails loudly with the
executor's replay-divergence error ("journaled with different inputs")
instead of silently reusing a row bound to the now-stale value. Units
without bindings are unaffected.

See [`with:` on a task-composed step](../reference/workflow-schema.md#github-shaped-yaml-subset)
for the full binding grammar, delivery surfaces (`AKM_TASK_INPUTS`, the
`## Task inputs` prompt block), and `akm task explain` for inspecting what a
task-composed step would actually receive.

## Child workflows

Nothing to migrate here in the strict sense: composing a child workflow is
itself new in 0.9.2, so no run from before this release ever has a stored
child. There is no pre-existing child-run data to convert or backfill. But
if you are restructuring a multi-job document to work around the new
one-job limit (above), this is the mechanism you restructure into.

**Two authoring forms**, both lowering to the same child-workflow target:

- **Direct**: a step's `uses: workflows/<ref>` composes another workflow
  directly. `with:` on the step binds the child's declared `params:`.
- **Task-wrapped**: a step's `uses: tasks/<ref>` composes a task whose own
  target is itself `uses: workflows/<ref>`. The task's own effective
  `inputs:` (its declared defaults plus whatever the composing step's
  `with:` bound against the task's contract) are re-bound, by name, against
  the child workflow's declared `params:`.

Composition is bounded, checked entirely at **freeze**, before the parent
run is published, and failing with `UsageError` code `COMPOSITION_INVALID`
when violated:

- **Depth**: the root workflow plus 8 descendant levels (a 9th fails).
- **Cycles**: a composition cycle (a workflow composing itself, directly or
  transitively) fails before any durable mutation.
- **Aggregate size**: the sum of every embedded child plan's canonical-JSON
  bytes across one root freeze is capped at half the single-plan byte
  ceiling (currently 1 MiB total).

The child workflow is compiled, validated, and frozen **completely** — its
own complete plan embedded inside the parent's — before the parent run
exists, so editing the child's source afterward cannot affect an
already-frozen parent, and the child's transitive sources join the
parent's guarded source read set.

**Execution.** Running a step whose target is a child workflow drives that
child to completion (or as far as it gets) inline, in the parent's own
process, with the same engine `akm workflow run` uses on the child's frozen
plan — not a separately scheduled job. Publication is idempotent, so a
retried or resumed composing step reuses the same child rather than
starting a new one.

**Status mapping.** The child's final status maps onto the composing step
and the parent run: `completed` promotes the child's declared `outputs:`
(see [Workflow outputs](#workflow-outputs)) — or `{runId, status}` when it
declares none — as the step's output, and the parent continues; `failed`
fails the step and the run; `blocked` blocks the step and the run.

**Cancellation propagates because the drive is inline.** Whatever aborts
the parent's own dispatch — `Ctrl-C`, a `--timeout`, a budget ceiling, or
the parent losing its run lease — also aborts the child drive, since it is
the same process. Both runs are left resumable, never partially torn down.

**Independent resume.** A blocked child blocks its composing step; AKM does
not resume a child for you, because a gate is a gate for a child workflow
too. The step's notes name the exact three-command sequence — resume the
**child** first, then resume and re-run the **parent**, since re-driving
the parent is what re-enters the composing step and drives the now-resumed
child:

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

A child run id always works directly with `status`/`resume`/`abandon`/`run`,
whether or not it is listed. `akm workflow status` on a run that composes
children renders a `children:` tree. `akm workflow list` excludes child
runs by default now that they exist at all — pass `--children` to include
them.

See [Workflow Schema: Child workflows](../reference/workflow-schema.md#child-workflows)
and [Workflow Schema: Child execution](../reference/workflow-schema.md#child-execution)
for the complete grammar, status-mapping table, and nested-block recovery
sequence, and
[Running Workflows: Child runs](https://github.com/itlackey/akm/blob/main/docs/guides/run-workflows.md#child-runs)
for an operational walkthrough.

## Workflow outputs

A workflow may declare a run-level export in its Markdown frontmatter:

```yaml
outputs:
  summary:
    from: steps.review.output.summary
  fileCount:
    from: steps.scan.output.files
    schema: { type: array }
```

Up to 64 entries, each `{from: steps.<id>.output(.<segment>)*, schema?}`,
resolved **once**, from persisted step evidence, at run completion. An
unresolvable reference, a truncated step artifact, or a schema violation
rolls the completion back — `UsageError` code `WORKFLOW_OUTPUT_INVALID`:
the run stays `active` and its final step stays `pending` rather than
completing with missing exports. A run with no `outputs:` declaration
exports `{runId, status}` instead. When this run is itself a composed
child, its exported result (whichever of the two shapes above) becomes the
composing parent step's own output — see
[Child workflows](#child-workflows).

This is a Markdown-frontmatter-only key — a GitHub-shaped workflow's closed
root key set (`name`, `on`, `jobs`) has no extension surface for it, the
same reason it cannot declare `params:` either. See
[Workflow Schema: Workflow outputs](../reference/workflow-schema.md#workflow-outputs).

## New commands

Both are read-only and zero-write — neither spawns anything, writes
history, or publishes a run. `akm workflow plan` is secret-free **by
construction**: it never prints a resolved reference value, request
content, script byte, or credential, because that data never reaches the
command in the first place. `akm task explain` instead **redacts**
secret-shaped input values on a best-effort heuristic basis (see below) —
a value that doesn't match the heuristic (short, low-entropy, or
unusually named) can still print unredacted, so don't treat its output as
a guaranteed-safe paste target.

**`akm workflow plan <ref>`** compiles, resolves, and freezes a workflow
exactly as starting a run would, then stops. It prints the canonical step
graph, per-step frozen target kinds, task/child expansion, input bindings,
the source read set, and freeze-time lowering notices.

```sh
akm workflow plan release --format json
```

Use it before committing to a run — especially after restructuring a
multi-job document into a composed set of workflows (above) — to confirm
the plan looks the way you expect, including which children it would
compose.

**`akm task explain <ref> [input flags]`** prints a task's source path and
version, its declared `inputs:` (with defaults — a secret-shaped default
prints as `<redacted>`), the supplied values with provenance
(`default` | `flag` | `schedule-binding`, likewise redacted when
secret-shaped), the resolved target kind/ref, effective execution settings
with field-level provenance, and schedule bindings.

```sh
akm task explain nightly-review --scope all  # doclint:ignore
```

Its default output (no `--format` flag) is the same raw JSON as
`--format json`, byte for byte. `--format text` is a separate renderer: it
flattens the envelope into `dotted.path=value` lines instead of printing
JSON. See [Tasks: `akm task explain`](../reference/tasks.md#akm-task-explain).

## Diagnostics

`INVALID_FLAG_VALUE` is now rare in task or workflow domain failures, with
two named exceptions (below). Every OTHER task-source, workflow-source,
target-classification, and composition failure now reports a
phase-specific code:

| Code | Domain |
|---|---|
| `TASK_SOURCE_INVALID` | A task document's field- or semantic-level validation failure, or a malformed/oversized/too-deep YAML front end failure. |
| `TASK_SCHEMA_VERSION_UNSUPPORTED` | A task document's `version:` is not `4` and is recognizable as a legacy generation (`3` or `2`). |
| `TARGET_REF_INVALID` | A value is not a canonical `commands/`, `scripts/`, `tasks/`, or `workflows/` asset ref (malformed shapes, GitHub locators, other asset families). |
| `WORKFLOW_SOURCE_INVALID` | A workflow-source compile failure other than the one below. |
| `COMPOSITION_INVALID` | A composition-policy rejection: a rejected `with:`, a multi-job document, a composition cycle/depth/size violation. |
| `INPUT_BINDING_INVALID` | A `with:` binding, or a task's declared `inputs:` flag, fails its schema or names something that doesn't exist. |
| `TASK_TARGET_UNSUPPORTED` | A recognized-but-unsupported task-execution construct (e.g. an interpreter task source v4 does not support). |
| `WORKFLOW_OUTPUT_INVALID` | A declared `outputs:` entry could not be resolved at run completion. |
| `WORKFLOW_IR_VERSION_UNSUPPORTED` | A stored plan predates `irVersion` 5 (see [Workflow cutover](#workflow-cutover)). |

Two failures are deliberately **not** re-coded and still report
`INVALID_FLAG_VALUE`, so an existing pinned test's code and message stay
byte-unchanged: a task's workflow-target `env:` composition rejection (a
`uses: workflows/<ref>` task that also authors `env:`), and a workflow
child-ref asset-resolution failure (`Workflow source target <ref> was not
found.`). Beyond those two, the remaining `INVALID_FLAG_VALUE` sites in the
task/workflow domains (38 total, across `src/tasks/**` and
`src/workflows/**`) are scalar CLI-argument parsing (a cron expression, a
task id, a workflow parameter flag) and one code-allowlist membership entry
— genuine flag-value validation, not a re-codable task/workflow source or
composition failure. **Scripts branching on `code` for a task/workflow
domain error should switch on the specific code above** rather than
assuming `INVALID_FLAG_VALUE` — except for the two named exceptions, which
still report `INVALID_FLAG_VALUE`. Exit codes are unchanged — every code
above is exit 2, same as before.

## Improve triage judgment

`improve.strategies.<name>.processes.triage.judgment` now accepts an explicit
boolean or object. Set `judgment: false` to disable the judgment pass without
requiring an engine or credentials. Object form defaults to enabled for newly
authored configuration and rejects unknown object keys. The standalone
`akm proposal ... --judgment` option remains an explicit invocation override;
it does not silently rewrite the configured strategy.

## Validate the upgrade

After migration:

```sh
akm index
akm task sync
akm task doctor
akm health
```

Review scheduler changes before activation. Use `akm task sync --rebind` only
when the installed runtime path intentionally changed. Spot-check a
migrated task or a restructured workflow with the two new read-only
commands before trusting it in production:

```sh
akm task explain <migrated-task-ref>
akm workflow plan <restructured-workflow-ref>
```

See [Tasks](../reference/tasks.md), [Workflow schema](../reference/workflow-schema.md),
and the [0.9.2 terminal migration note](release-notes/0.9.2.md).
