akm-cli 0.9.31 → 0.10.261001-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/CHANGELOG.md +409 -0
  2. package/STABILITY.md +99 -57
  3. package/dist/api-node.mjs +20 -0
  4. package/dist/api.d.ts +23 -0
  5. package/dist/api.js +45 -0
  6. package/dist/assets/hints/cli-hints-full.md +15 -29
  7. package/dist/assets/hints/cli-hints-short.md +2 -2
  8. package/dist/assets/improve-strategies/default.json +3 -3
  9. package/dist/assets/prompts/consolidate-pair.md +9 -7
  10. package/dist/assets/stash-skeleton/README.md +1 -1
  11. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +1 -1
  12. package/dist/cli/shared.js +31 -48
  13. package/dist/cli/unknown-flags.js +6 -43
  14. package/dist/cli.js +8 -34
  15. package/dist/commands/agent/contribute-cli.js +1 -1
  16. package/dist/commands/completions.js +1 -2
  17. package/dist/commands/config-cli.js +5 -28
  18. package/dist/commands/env/env-cli.js +5 -5
  19. package/dist/commands/env/secret-cli.js +2 -2
  20. package/dist/commands/feedback-cli.js +75 -49
  21. package/dist/commands/health/checks.js +41 -16
  22. package/dist/commands/health/plugin-staleness.js +52 -22
  23. package/dist/commands/improve/autonomy-gate.js +5 -3
  24. package/dist/commands/improve/consolidate/chunking.js +3 -10
  25. package/dist/commands/improve/consolidate/continuity-check.js +4 -4
  26. package/dist/commands/improve/consolidate/pair-pass.js +29 -10
  27. package/dist/commands/improve/consolidate.js +10 -65
  28. package/dist/commands/improve/distill/lesson-update.js +91 -0
  29. package/dist/commands/improve/distill-guards.js +1 -43
  30. package/dist/commands/improve/distill.js +148 -21
  31. package/dist/commands/improve/eligibility.js +14 -1
  32. package/dist/commands/improve/extract-cli.js +1 -1
  33. package/dist/commands/improve/extract-prompt.js +17 -7
  34. package/dist/commands/improve/extract.js +5 -7
  35. package/dist/commands/improve/improve-cli.js +92 -106
  36. package/dist/commands/improve/improve-strategies.js +8 -12
  37. package/dist/commands/improve/improve.js +134 -52
  38. package/dist/commands/improve/loop-stages.js +18 -5
  39. package/dist/commands/improve/planner.js +0 -1
  40. package/dist/commands/improve/preparation.js +40 -99
  41. package/dist/commands/improve/proactive-maintenance.js +2 -2
  42. package/dist/commands/improve/reflect-noise.js +3 -55
  43. package/dist/commands/improve/reflect.js +29 -34
  44. package/dist/commands/improve/retrieval-queries.js +37 -0
  45. package/dist/commands/improve/retrieval-scope.js +2 -2
  46. package/dist/commands/improve/salience.js +2 -9
  47. package/dist/commands/improve/session-asset.js +4 -1
  48. package/dist/commands/improve/stage.js +23 -12
  49. package/dist/commands/lint/index.js +5 -20
  50. package/dist/commands/observability-cli.js +3 -9
  51. package/dist/commands/proposal/drain.js +43 -6
  52. package/dist/commands/proposal/proposal-cli.js +11 -31
  53. package/dist/commands/proposal/repository.js +20 -14
  54. package/dist/commands/proposal/validators/proposal-quality-validators.js +0 -3
  55. package/dist/commands/read/knowledge.js +3 -3
  56. package/dist/commands/read/remember-cli.js +0 -13
  57. package/dist/commands/read/search-cli.js +29 -97
  58. package/dist/commands/read/search.js +0 -11
  59. package/dist/commands/read/show.js +0 -19
  60. package/dist/commands/sources/plugin-upgrade.js +164 -101
  61. package/dist/commands/sources/sources-cli.js +1 -15
  62. package/dist/commands/sources/stash-cli.js +6 -14
  63. package/dist/commands/tasks/tasks-cli.js +4 -115
  64. package/dist/commands/workflow/plan.js +11 -33
  65. package/dist/commands/workflow-cli.js +2 -7
  66. package/dist/core/adapter/adapter-ids.js +1 -2
  67. package/dist/core/adapter/adapters/agent-skills-adapter.js +0 -4
  68. package/dist/core/adapter/adapters/akm-adapter.js +9 -48
  69. package/dist/core/adapter/adapters/akm-lint.js +10 -26
  70. package/dist/core/adapter/adapters/akm-metadata.js +2 -72
  71. package/dist/core/adapter/adapters/akm-task-adapter.js +0 -5
  72. package/dist/core/adapter/adapters/akm-workflow-adapter.js +9 -28
  73. package/dist/core/adapter/adapters/dotenv-adapter.js +1 -20
  74. package/dist/core/adapter/adapters/generic-files-adapter.js +0 -10
  75. package/dist/core/adapter/adapters/llm-wiki-adapter.js +0 -4
  76. package/dist/core/adapter/adapters/tool-dir-shared.js +0 -24
  77. package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -4
  78. package/dist/core/adapter/recognize-match.js +3 -4
  79. package/dist/core/common.js +18 -2
  80. package/dist/core/config/config-schema.js +12 -15
  81. package/dist/core/config/config-types.js +6 -15
  82. package/dist/core/config/config-walker.js +6 -23
  83. package/dist/core/config/config.js +17 -80
  84. package/dist/core/config/engine-semantics.js +7 -3
  85. package/dist/core/config/schema/engines.js +7 -7
  86. package/dist/core/config/schema/improve-processes.js +10 -63
  87. package/dist/core/config/schema/improve.js +2 -21
  88. package/dist/core/config/schema/index-config.js +6 -39
  89. package/dist/core/env-secret-ref.js +2 -45
  90. package/dist/core/errors.js +1 -2
  91. package/dist/core/extra-params.js +2 -86
  92. package/dist/core/file-lock.js +2 -6
  93. package/dist/core/improve-result.js +13 -1
  94. package/dist/core/mutation-target.js +2 -2
  95. package/dist/core/paths.js +4 -0
  96. package/dist/core/recognition-util.js +4 -5
  97. package/dist/core/run-lock.js +1 -1
  98. package/dist/core/subprocess.js +49 -0
  99. package/dist/core/warn.js +21 -4
  100. package/dist/core/write-source.js +3 -3
  101. package/dist/execution/target-ref.js +1 -2
  102. package/dist/indexer/indexer.js +15 -6
  103. package/dist/indexer/passes/memory-inference.js +15 -6
  104. package/dist/indexer/search/db-search.js +49 -36
  105. package/dist/indexer/search/fts-query.js +0 -19
  106. package/dist/indexer/search/ranking.js +29 -1
  107. package/dist/indexer/usage/usage-events.js +21 -16
  108. package/dist/integrations/agent/engine-resolution.js +5 -2
  109. package/dist/integrations/agent/index.js +1 -1
  110. package/dist/integrations/agent/profiles.js +41 -98
  111. package/dist/integrations/agent/request-lowering.js +3 -3
  112. package/dist/integrations/harnesses/aider/index.js +5 -0
  113. package/dist/integrations/harnesses/amazonq/index.js +1 -0
  114. package/dist/integrations/harnesses/claude/config-import.js +2 -3
  115. package/dist/integrations/harnesses/claude/index.js +5 -0
  116. package/dist/integrations/harnesses/codex/index.js +1 -0
  117. package/dist/integrations/harnesses/copilot/index.js +1 -0
  118. package/dist/integrations/harnesses/gemini/index.js +1 -0
  119. package/dist/integrations/harnesses/index.js +18 -39
  120. package/dist/integrations/harnesses/opencode/agent-builder.js +60 -35
  121. package/dist/integrations/harnesses/opencode/config-import.js +121 -52
  122. package/dist/integrations/harnesses/opencode/index.js +22 -0
  123. package/dist/integrations/harnesses/opencode/plugin-layout.js +68 -0
  124. package/dist/integrations/harnesses/opencode/session-log-types.js +12 -0
  125. package/dist/integrations/harnesses/opencode/session-log-v1.js +72 -0
  126. package/dist/integrations/harnesses/opencode/session-log-v2.js +140 -0
  127. package/dist/integrations/harnesses/opencode/session-log.js +104 -115
  128. package/dist/integrations/harnesses/opencode/version.js +98 -0
  129. package/dist/integrations/harnesses/opencode-sdk/{harness.js → index.js} +5 -17
  130. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +105 -206
  131. package/dist/integrations/harnesses/opencode-sdk/v1-adapter.js +199 -0
  132. package/dist/integrations/harnesses/opencode-sdk/v2-adapter.js +254 -0
  133. package/dist/integrations/harnesses/opencode-sdk/wire.js +66 -0
  134. package/dist/integrations/harnesses/openhands/index.js +1 -0
  135. package/dist/integrations/harnesses/pi/index.js +1 -0
  136. package/dist/integrations/harnesses/shared.js +2 -0
  137. package/dist/integrations/harnesses/types.js +2 -0
  138. package/dist/llm/client.js +41 -11
  139. package/dist/output/cli-hints.js +1 -1
  140. package/dist/output/context.js +10 -19
  141. package/dist/output/format-exempt.js +5 -8
  142. package/dist/output/shapes/helpers.js +1 -22
  143. package/dist/output/shapes/passthrough.js +0 -1
  144. package/dist/output/shapes.js +3 -31
  145. package/dist/output/stdout.js +5 -1
  146. package/dist/output/text/command-format.js +6 -1
  147. package/dist/output/text/workflow-format.js +4 -22
  148. package/dist/scripts/akm-migrate-node.js +15745 -16711
  149. package/dist/scripts/akm-migrate.js +16318 -17280
  150. package/dist/setup/detect.js +10 -4
  151. package/dist/setup/harness-config-import.js +5 -14
  152. package/dist/setup/setup.js +1 -1
  153. package/dist/setup/steps/tasks.js +12 -13
  154. package/dist/sources/providers/git-stash.js +94 -13
  155. package/dist/storage/repositories/index-utility-repository.js +2 -32
  156. package/dist/storage/repositories/index-vec-repository.js +4 -0
  157. package/dist/storage/repositories/task-history-repository.js +0 -5
  158. package/dist/storage/repositories/workflow-runs-repository.js +5 -15
  159. package/dist/tasks/embedded.js +2 -1
  160. package/dist/tasks/run/run-command-task.js +9 -1
  161. package/dist/tasks/run/run-workflow-task.js +4 -3
  162. package/dist/tasks/run/task-history.js +0 -2
  163. package/dist/tasks/run/task-log.js +38 -1
  164. package/dist/tasks/schedule-gates.js +39 -0
  165. package/dist/tasks/scheduler-binding.js +0 -16
  166. package/dist/tasks/scheduler-sync.js +3 -97
  167. package/dist/tasks/source/bounded-document.js +1 -1
  168. package/dist/tasks/source/task-source-v4.js +2 -12
  169. package/dist/tasks/task-run-reserved-flags.js +2 -31
  170. package/dist/workflows/authoring/authoring.js +6 -6
  171. package/dist/workflows/compile.js +8 -65
  172. package/dist/workflows/exec/child-workflow.js +22 -3
  173. package/dist/workflows/exec/native-executor.js +12 -147
  174. package/dist/workflows/exec/run-workflow.js +31 -103
  175. package/dist/workflows/exec/step-work.js +9 -178
  176. package/dist/workflows/exec/unit-dispatch.js +0 -10
  177. package/dist/workflows/freeze/environment.js +3 -44
  178. package/dist/workflows/freeze/freeze.js +8 -41
  179. package/dist/workflows/freeze/targets/child-workflow.js +27 -63
  180. package/dist/workflows/freeze/targets/command.js +2 -16
  181. package/dist/workflows/freeze/targets/shell.js +0 -2
  182. package/dist/workflows/freeze/task-bindings.js +2 -28
  183. package/dist/workflows/ir/params.js +12 -2
  184. package/dist/workflows/parser.js +82 -232
  185. package/dist/workflows/plan.js +1 -1
  186. package/dist/workflows/renderer.js +10 -19
  187. package/dist/workflows/runtime/run-plan.js +11 -53
  188. package/dist/workflows/runtime/runs.js +4 -27
  189. package/dist/workflows/source-files.js +12 -16
  190. package/dist/workflows/source-semantics.js +1 -93
  191. package/docs/README.md +1 -1
  192. package/docs/agents/README.md +3 -3
  193. package/docs/agents/agent-install.md +1 -1
  194. package/docs/architecture/0.10-process-rules.md +827 -0
  195. package/docs/architecture/adapters.md +17 -31
  196. package/docs/architecture/architecture.md +4 -5
  197. package/docs/architecture/improvement.md +115 -76
  198. package/docs/architecture/internals/improve-workflow.md +22 -16
  199. package/docs/architecture/internals/indexing.md +1 -1
  200. package/docs/architecture/internals/search.md +1 -2
  201. package/docs/architecture/internals/storage-locations.md +6 -6
  202. package/docs/architecture/persisted-data-compat.md +13 -4
  203. package/docs/architecture/specs/0.9.0-decisions.md +6 -2
  204. package/docs/architecture/specs/improve-collapse-churn-detector-design.md +2 -2
  205. package/docs/architecture/specs/workflow-format-unification.md +1 -6
  206. package/docs/architecture/testing/manual-testing-checklist.md +21 -30
  207. package/docs/architecture/testing/task-workflow-manual-tests.md +106 -203
  208. package/docs/architecture/testing/testing-workflow.md +1 -1
  209. package/docs/architecture/workflow-engine.md +5 -56
  210. package/docs/guides/author-workflows.md +39 -69
  211. package/docs/guides/bundles.md +3 -1
  212. package/docs/guides/capture-knowledge.md +1 -1
  213. package/docs/guides/claude-code-vs-akm-workflows.md +1 -2
  214. package/docs/guides/discover-and-load.md +4 -4
  215. package/docs/guides/getting-started.md +1 -1
  216. package/docs/guides/improve-the-library.md +10 -9
  217. package/docs/guides/recipes/headless-install.md +2 -2
  218. package/docs/guides/run-workflows.md +1 -1
  219. package/docs/guides/scheduling.md +9 -1
  220. package/docs/guides/use-with-any-agent.md +49 -4
  221. package/docs/integration/bundling-akm.md +7 -6
  222. package/docs/maintainers/curate-workmap.md +1 -2
  223. package/docs/maintainers/release-checklist.md +33 -1
  224. package/docs/migration/README.md +2 -0
  225. package/docs/migration/release-notes/0.10.0.md +201 -0
  226. package/docs/migration/release-notes/README.md +1 -0
  227. package/docs/migration/v0.9-to-v0.10.md +507 -0
  228. package/docs/posts/akm-command-tour.md +3 -3
  229. package/docs/product-surface.md +1 -1
  230. package/docs/reference/README.md +1 -0
  231. package/docs/reference/api.md +65 -0
  232. package/docs/reference/asset-types.md +3 -5
  233. package/docs/reference/bundle-types.md +2 -4
  234. package/docs/reference/cli.md +160 -188
  235. package/docs/reference/configuration.md +101 -20
  236. package/docs/reference/data-and-telemetry.md +2 -2
  237. package/docs/reference/env-and-secrets.md +2 -2
  238. package/docs/reference/supported-formats.md +3 -4
  239. package/docs/reference/tasks.md +12 -19
  240. package/docs/reference/website-sources.md +1 -1
  241. package/docs/reference/workflow-schema.md +89 -317
  242. package/docs/reference/workflows.md +20 -48
  243. package/package.json +12 -2
  244. package/schemas/akm-config.json +2 -243
  245. package/schemas/akm-task.json +2 -3
  246. package/schemas/akm-workflow.json +47 -84
  247. package/dist/assets/improve-strategies/catchup.json +0 -41
  248. package/dist/assets/improve-strategies/proactive-maintenance.json +0 -24
  249. package/dist/assets/improve-strategies/quick.json +0 -17
  250. package/dist/assets/improve-strategies/reflect-distill.json +0 -29
  251. package/dist/assets/improve-strategies/thorough.json +0 -49
  252. package/dist/assets/prompts/retrieval-relevance-judge.md +0 -6
  253. package/dist/assets/tasks/improve/akm-improve-catchup.yml +0 -6
  254. package/dist/assets/tasks/improve/akm-improve-frequent.yml +0 -4
  255. package/dist/assets/tasks/improve/akm-improve-nightly.yml +0 -4
  256. package/dist/cli/retired-commands.js +0 -118
  257. package/dist/commands/improve/retrieval-gate.js +0 -132
  258. package/dist/commands/tasks/validate.js +0 -176
  259. package/dist/core/config/legacy-source-shape-shim.js +0 -117
  260. package/dist/indexer/feedback/utility-policy.js +0 -75
  261. package/dist/integrations/harnesses/ids.js +0 -28
  262. package/dist/tasks/source/task-source-v3-frozen.js +0 -424
  263. package/dist/tasks/source/task-to-v4.js +0 -863
  264. package/dist/workflows/exec/worktree.js +0 -372
  265. package/dist/workflows/freeze/child-output-references.js +0 -83
  266. package/dist/workflows/freeze/identity.js +0 -22
  267. package/dist/workflows/freeze/targets/script.js +0 -50
  268. package/dist/workflows/freeze/targets/task.js +0 -124
  269. package/dist/workflows/github-yaml.js +0 -506
  270. package/dist/workflows/runtime/run-outputs.js +0 -80
@@ -13,201 +13,75 @@ detail.
13
13
  - For operating a run day to day (`run`, `status`, `resume`, `abandon`), see
14
14
  [Running Workflows](https://github.com/itlackey/akm/blob/main/docs/guides/run-workflows.md).
15
15
 
16
- ## Source formats and shared IR
16
+ ## Source format
17
17
 
18
- Peer workflow sources include Markdown `.md` and GitHub-shaped YAML `.yml`.
19
- `.yaml` is not supported or recognized as a workflow source. Both adapters
20
- compile into the same strict source IR version 1 (`sourceIrVersion: 1`) before
21
- target resolution and durable freezing.
22
-
23
- The shipped `schemas/akm-workflow.json` frontmatter schema applies to and
24
- validates the Markdown source only. Markdown is not the sole or only workflow
25
- source; the GitHub-shaped YAML adapter has its own bounded parser and shares
26
- the source-IR decoder and semantic authorities.
27
-
28
- ### Markdown source
18
+ A workflow is a Markdown `.md` asset. Nothing else is a workflow source: the
19
+ GitHub-shaped `.yml` workflow format was removed in 0.10, and a stray `.yml` file
20
+ under `workflows/` is skipped at index time with a message saying it must use
21
+ `.md`. The shipped `schemas/akm-workflow.json` frontmatter schema
22
+ validates the source.
29
23
 
30
24
  A Markdown workflow is an ordinary AKM asset — the same envelope as every
31
25
  other Markdown type, OKF-conformant frontmatter plus a body — whose
32
26
  frontmatter carries the orchestration graph (params, and how each step
33
- dispatches, fans out, routes, and gates). Its body carries each step's
27
+ dispatches, fans out, and gates). Its body carries each step's
34
28
  instructions and gate rubric under plain headings, joined to the frontmatter
35
29
  by step id. The remainder of this page's frontmatter/body sections document
36
- that Markdown authoring format.
30
+ that authoring format.
37
31
 
38
- ## GitHub-shaped YAML subset
32
+ ## Child workflows
39
33
 
40
- A complete valid `on` plus `jobs` document is one workflow asset.
41
- It never creates a duplicate or second task asset. The root vocabulary is exactly
42
- `name`, `on`, and `jobs`:
34
+ > **Experimental.** Child workflows are part of the Experimental `akm workflow`
35
+ > surface (see `STABILITY.md`) and may change or be removed before 0.11.
36
+
37
+ A step can run another workflow as a **child run**. It declares the child in
38
+ its `unit:` bag:
43
39
 
44
40
  ```yaml
45
- name: Local checks
46
- on:
47
- schedule:
48
- - cron: "0 6 * * *"
49
- workflow_dispatch: {}
50
- jobs:
51
- checks:
52
- runs-on: [self-hosted]
53
- steps:
54
- - id: lint
55
- run: bun run lint
56
- - id: review
57
- uses: akm/command
58
- with:
59
- ref: commands/review
41
+ steps:
42
+ - id: investigate
43
+ unit:
44
+ workflow: workflows/research # canonical ref, or <bundle>//workflows/<name>
45
+ with:
46
+ topic: { from: params.topic } # a child param: a literal or { from: <reference> }
47
+ - id: report
48
+ inputs: [steps.investigate.output] # the child's last step output
60
49
  ```
61
50
 
62
- AKM's public position on this format: **AKM YAML uses a familiar
63
- GitHub-step-shaped syntax but is an AKM workflow format, executed by AKM's
64
- native engine.** It is not Marketplace-action-compatible, does not evaluate
65
- GitHub expressions or contexts, has no hosted runner images or service
66
- containers, and does not execute multiple jobs.
67
-
68
- The accepted 0.9.2 subset is deliberately closed:
69
-
70
- - `on` accepts five-field `schedule` entries and an empty or null
71
- `workflow_dispatch`; workflow_dispatch inputs are unsupported.
72
- - Service events are rejected.
73
- A rejected service event creates no watcher and no polling daemon.
74
- - **`jobs:` must contain exactly one job.** A document with zero, two, or
75
- more jobs fails at the adapter with reason `multi-job-unsupported`,
76
- surfaced as `UsageError` code `COMPOSITION_INVALID` when the workflow is
77
- frozen (`akm workflow run` or `akm workflow plan`). Split a multi-job
78
- document into separate single-job workflows and compose them with a
79
- child-workflow step (`uses: workflows/<ref>`) instead. A job's `needs:`
80
- must be empty — a non-empty `needs:` fails for the same reason, since a
81
- single job has nothing to depend on.
82
- - Each job requires exactly `runs-on: [self-hosted]`. `name`, `needs`, and
83
- `steps` are the remaining job fields.
84
- - Each step requires `id` and exactly one `uses` or `run`; optional fields are
85
- `name`, `with`, `env`, `shell`, and contained `working-directory`.
86
- - A `run` accepts only token-safe local command tokens.
87
- Shell expansion and operators are unsupported and rejected, even when a host shell is named.
88
- - `uses` is classified as a canonical asset ref (`commands/`, `scripts/`,
89
- `tasks/`, `workflows/`), plus the `akm/command` builtin. `akm/command`,
90
- command, script, task, and **child-workflow** composition are local
91
- targets — see [Child workflows](#child-workflows) for `uses:
92
- workflows/<ref>`.
93
- Local actions and Docker actions are unsupported and rejected (including
94
- `./` and `docker://`); a GitHub Action locator (`owner/repo[/path]@ref`,
95
- e.g. `actions/checkout@v4`) and every other unrecognized shape fail the
96
- same way — `unsupported-uses-target` — since AKM never acquires or
97
- executes a remote action. AKM does not recognize the locator grammar as a
98
- distinct case; it is simply not one of the four canonical asset-ref
99
- families or the `akm/command` builtin.
100
- - `with:` on a **task-composed** step (`uses: tasks/<ref>`) **binds** the
101
- target task source's declared `inputs:` — see
102
- [Typed inputs and output](tasks.md#typed-inputs-and-output). Each value is either a literal
103
- (validated against the input's declared schema at freeze) or a reference
104
- `{from: "steps.<id>.output(.<segment>)*"}`, resolved just before the unit
105
- dispatches and re-validated against the same schema then. The reference
106
- grammar also accepts `{from: "params.<name>"}`, naming a declared param of
107
- the *composing* workflow itself — but a composing step is only authorable
108
- in a GitHub-shaped document (this section), whose root keys are exactly
109
- `name`, `on`, and `jobs` with `workflow_dispatch` inputs rejected, so it
110
- can never declare `params:` of its own. In practice that reference form
111
- therefore always fails freeze here, with "does not name a declared
112
- workflow param; declared params: (none)". An unknown `with:` key, a
113
- missing required input with no default, or a reference naming a step that
114
- doesn't exist earlier in the job all fail at **freeze** with `UsageError`
115
- code `INPUT_BINDING_INVALID`, before the plan is ever published. If the
116
- target task declares **no** `inputs:` at all (a `version: 4` task with no
117
- `inputs:` key) — or the step targets
118
- `uses: commands/<ref>` / `uses: scripts/<ref>`, which are never binding
119
- surfaces — any authored
120
- `with:`, including an empty mapping (`with: {}`), is rejected at freeze
121
- with `UsageError` code `COMPOSITION_INVALID`, exit 2. Omitting `with:`
122
- entirely always freezes normally, regardless of target. `with:` on
123
- `uses: akm/command` is unaffected by any of this and is still required to
124
- supply the builtin action's arguments, as in the example above. `with:` on
125
- a **child-workflow** target (direct or task-wrapped) binds the child's
126
- declared `params:` instead of `inputs:` — see
127
- [Child workflows](#child-workflows).
128
- - GitHub expressions and contexts are unsupported and rejected anywhere in
129
- the parsed tree.
130
-
131
- ## Child workflows
132
-
133
- A step can compose another workflow, two ways. Composition is authored only
134
- through the GitHub-shaped `jobs.<id>.steps[].uses` surface — the composing
135
- (parent) document must be GitHub-shaped YAML, since the Markdown-frontmatter
136
- step schema has no `uses:` key at all (see [Source formats and shared
137
- IR](#source-formats-and-shared-ir)). The **child** workflow being composed
138
- may itself be authored in either format, Markdown or GitHub-shaped:
139
-
140
- - **Direct** — `uses: workflows/<ref>`:
141
-
142
- ```yaml
143
- - id: dispatch
144
- uses: workflows/release-checklist
145
- with:
146
- channel: stable
147
- ```
148
-
149
- - **Task-wrapped** — `uses: tasks/<ref>` where `<ref>` names a task source
150
- v4 document whose own target is a workflow. The task's own effective
151
- `inputs:` (its declared defaults plus whatever the *task's own* callers
152
- bound) supply the child's params; a `with:` authored on the *workflow
153
- step itself* binds on top of that, exactly as it would for a direct step.
154
-
155
- Both forms bind against the child workflow's own declared `params:`
156
- frontmatter key — **not** a task's `inputs:` contract, since a workflow has
157
- no `inputs:`. `with:` follows the same grammar as everywhere else in this
158
- document: each value is a literal (validated against the param's declared
159
- type at freeze) or a `{from: "steps.<id>.output(.<segment>)*"}` reference,
160
- resolved just before the unit dispatches. (The grammar also accepts
161
- `{from: "params.<name>"}`, naming a declared param of the *composing*
162
- workflow — but that form is unreachable here for the same reason noted
163
- above: the composing document is necessarily GitHub-shaped, so it never
164
- declares `params:` of its own.) An unknown key or an invalid reference
165
- fails at freeze with `UsageError` code `INPUT_BINDING_INVALID`, exactly like
166
- a task-composed step's `with:`.
51
+ The step needs a `## investigate` body section like any other step (it is
52
+ documentation for a human; the child's own steps carry the instructions).
53
+
54
+ - `workflow` names the child. A workflow unit reaches no engine and the child
55
+ carries its own environment, so `exec`, `engine`, `model`, `llm` and `env`
56
+ beside `workflow` are lint errors. `workflow` is not allowed inside
57
+ `map.unit`.
58
+ - `with:` binds the child's declared `params:` (a workflow has no `inputs:`).
59
+ Each value is a literal (validated against the param's declared type at
60
+ freeze) or `{from: "steps.<id>.output(.<segment>)*"}` or
61
+ `{from: "params.<name>"}`, resolved just before the unit dispatches. A child
62
+ param the step leaves out takes the child's own `default:`. An unknown key or
63
+ an invalid reference fails at freeze with `UsageError` code
64
+ `INPUT_BINDING_INVALID`.
65
+ - **The child's result is its last step's output**, exactly what
66
+ `steps.<child-step>.output` would be for that step inside the child. A later
67
+ step reads it as `steps.<composing-step>.output` (sub-paths legal). A
68
+ workflow has no separate `outputs:` key.
167
69
 
168
70
  ### Frozen before publication
169
71
 
170
- Composing a child workflow is not a runtime call — it is a **freeze-time**
171
- resolution. When the parent workflow's plan is frozen, AKM loads the child's
172
- source, compiles it, validates it, and freezes the child's own complete plan
173
- *before the parent run is published*. The result is embedded whole inside
174
- the parent step's frozen target (`kind: "child-workflow"`); nothing about the
175
- child is re-read at dispatch time. Concretely:
176
-
177
- - Editing the child's source **after** the parent run has started has no
178
- effect on that run — the parent already carries its own frozen copy of the
179
- child's plan.
180
- - Editing the child's source in the narrow window **between** the parent's
181
- freeze and its publication fails the whole parent publication atomically,
182
- with no run row written — the same source-race protection that already
183
- covers the parent's own command/script/task sources extends to every
184
- transitive child source file.
185
- - The child's *own* source files (its workflow document plus every
186
- command/script/task it in turn resolves) become part of the parent run's
187
- guarded source read set, exactly like any other source the parent
188
- workflow depends on.
189
-
190
- See [Architecture: The Workflow Engine](https://github.com/itlackey/akm/blob/main/docs/architecture/workflow-engine.md#child-workflows)
191
- for how an embedded child plan is decoded (`irVersion`, `planHash`, and
192
- `contentHash` are recorded provenance, not re-verified).
193
-
194
- ### Composition limits
195
-
196
- Three bounds are enforced at **freeze**, before the parent run is published,
197
- each failing with `UsageError` code `COMPOSITION_INVALID` (exit 2):
198
-
199
- | Limit | Value | Message names |
200
- | --- | --- | --- |
201
- | Composition depth | 8 levels below the root | the limit and the ref path, e.g. `Workflow step <id> cannot compose <ref>: workflow composition is limited to 8 levels. Path: <a -> b -> …>.` |
202
- | Cycle detection | a workflow (direct or task-wrapped) reaching itself through any chain | the cycle path, e.g. `Workflow step <id> cannot compose <ref>: that would create a composition cycle. Path: <a -> tasks/w -> b -> a>.` |
203
- | Aggregate embedded plan bytes | 1 MiB total, summed across every embedded descendant of one root freeze | the cap and the running total, e.g. `Workflow step <id> cannot compose <ref>: the embedded child plans would total <N> bytes, over the <cap>-byte limit for one workflow run.` |
72
+ Composing a child workflow is a **freeze-time** resolution. When the parent
73
+ workflow's plan is frozen, AKM loads the child's source, compiles it,
74
+ validates it, and freezes the child's complete plan *before the parent run is
75
+ published*. The result is embedded whole inside the parent step's frozen target
76
+ (`kind: "child-workflow"`); nothing about the child is re-read at dispatch
77
+ time. Editing the child's source after the parent run has started has no effect
78
+ on that run.
204
79
 
205
- The same workflow reached twice through disjoint branches (a diamond, not a
206
- cycle) is not a violation — each occurrence embeds its own independent copy;
207
- deduplicating identical embedded plans is not implemented. A step whose
208
- `uses: workflows/<ref>` (or task-wrapped equivalent) does not resolve to a
209
- real asset fails with the ordinary asset-resolution error, unchanged by any
210
- of this.
80
+ A workflow that reaches itself through any chain of children fails at freeze
81
+ with `UsageError` code `COMPOSITION_INVALID` (exit 2), naming the cycle path.
82
+ The same workflow reached twice through disjoint branches (a diamond) is not a
83
+ cycle; each occurrence embeds its own copy. A `workflow:` ref that does not
84
+ resolve fails with the ordinary asset-resolution error.
211
85
 
212
86
  ### Child execution
213
87
 
@@ -225,7 +99,7 @@ The child's final status maps onto the composing step and the parent run:
225
99
 
226
100
  | Child status | Composing step | Parent run |
227
101
  | --- | --- | --- |
228
- | `completed` | completes; its output is the child's exported result — its declared `outputs:` (see [What a step's output is](#what-a-steps-output-is)), or `{runId, status}` when the child declares none | continues |
102
+ | `completed` | completes; its output is the child's last step output | continues |
229
103
  | `failed` | `failed` | `failed` |
230
104
  | `blocked` | `blocked` | `blocked` |
231
105
  | aborted mid-drive (parent cancelled/timed out) | left unfinished, not finalized | active and resumable |
@@ -245,14 +119,9 @@ akm workflow resume <parentRunId>
245
119
  akm workflow run <parentRunId>
246
120
  ```
247
121
 
248
- **Nested blocks (composition depth 2+).** The three-command sequence above
249
- clears a block exactly one level deep. It does **not** generalize to a
250
- grandchild block (root composes child, child composes grandchild, grandchild
251
- blocks): re-driving the root does not cascade down into re-driving the
252
- still-blocked grandchild, because a composing step never re-drives a child
253
- whose own status is already `blocked` — re-running the root just re-observes
254
- the child's own block and re-blocks the root the same way, without the
255
- grandchild ever being reached. Resume every blocked run in the chain,
122
+ **Nested blocks.** The three-command sequence above clears a block exactly
123
+ one level deep. With a grandchild block (root composes child, child composes
124
+ grandchild, grandchild blocks), resume every blocked run in the chain,
256
125
  **deepest first**, then re-run only the root:
257
126
 
258
127
  ```sh
@@ -262,31 +131,24 @@ akm workflow resume <rootRunId>
262
131
  akm workflow run <rootRunId>
263
132
  ```
264
133
 
265
- The status tree's own `resume`/`then` commands on a deeply nested node still
266
- name only that node and the root, never an intermediate ancestor — read the
267
- tree and resume every `blocked` row before re-running the root.
268
-
269
134
  **Identity and retries.** A child run's identity is keyed by the parent run,
270
135
  the parent unit, and that unit's input hash — publishing is idempotent, so
271
136
  re-driving the composing step (an explicit `akm workflow resume` +
272
- `akm workflow run`, or a `retry:` policy on the step) finds and continues
273
- the **same** child run rather than starting a new one. Only a change to the
274
- composing step's own inputs (params, upstream step outputs it reads, or gate
275
- feedback from a rejected verification loop) produces a different child.
137
+ `akm workflow run`) finds and continues the **same** child run rather than
138
+ starting a new one. Only a change to the composing step's own inputs (params,
139
+ upstream step outputs it reads, or gate feedback from a rejected verification
140
+ loop) produces a different child.
276
141
 
277
142
  **Visibility.** `akm workflow status` on a run that composes children
278
143
  renders a `children:` tree — every descendant run's ref, status, and, for a
279
- blocked child, its resume command — recursively to the same 8-level
280
- composition-depth bound described above. Child runs are excluded from `akm
144
+ blocked child, its resume command. Child runs are excluded from `akm
281
145
  workflow list` by default; pass `--children` to include them. A child run
282
146
  id always works directly with `akm workflow status`/`resume`/`abandon`/`run`,
283
147
  listed or not. See
284
148
  [Running Workflows: Child runs](https://github.com/itlackey/akm/blob/main/docs/guides/run-workflows.md#child-runs) for a
285
149
  worked example, and
286
150
  [Architecture: The Workflow Engine](https://github.com/itlackey/akm/blob/main/docs/architecture/workflow-engine.md#child-workflows)
287
- for the dispatch seam and why reusing the top-level run engine (rather than a
288
- second executor) is what makes the identity and abort-propagation rules above
289
- hold for free.
151
+ for the dispatch seam.
290
152
 
291
153
  ## Frontmatter keys
292
154
 
@@ -294,8 +156,9 @@ Frontmatter is the standard AKM asset envelope (`type`, `description`, `tags`,
294
156
  `when_to_use`, `xrefs`, `updated`/`timestamp`, and the OKF v0.2 trust/lifecycle
295
157
  families) plus the orchestration keys:
296
158
 
297
- - `params` — name → `{ type, description }` (JSON-Schema-typed, unlike a bare
298
- description string).
159
+ - `params` — name → `{ type, description, default }` (JSON-Schema-typed, unlike a bare
160
+ description string). A param the run does not supply takes its `default:`; an explicit
161
+ value wins. Params are never required.
299
162
  - `defaults` — run-level dispatch defaults (`engine`, `model`, `llm`,
300
163
  `timeout`, `on_error`), overridable per unit. `llm:` tuning reaches an engine
301
164
  of any kind, and a document-level `llm:` reaches EVERY step. An LLM engine
@@ -305,20 +168,16 @@ families) plus the orchestration keys:
305
168
  There is no per-step opt-out (`llm: {}` is a no-op and `llm: null` is a parse
306
169
  error), so in a mixed document put `llm:` on the `unit:` of each step it is
307
170
  meant for instead of in `defaults:`.
308
- - `outputs` — name → `{ from, schema? }`, a run-level export projected from
309
- a step's own artifact (Markdown-only; see [Workflow
310
- outputs](#workflow-outputs) below).
311
171
  - `budget` — run-lifetime ceilings (`max_units`, `max_tokens`; see
312
172
  [Budget ceilings](#budget-ceilings) below).
313
173
  - `steps` — an ordered list. Each step has an `id`
314
- (`[A-Za-z_][A-Za-z0-9_-]*` — no dots) and **at most one** of `unit`, `map`,
315
- or `route`. A step with neither is **still a unit step** — bare
174
+ (`[A-Za-z_][A-Za-z0-9_-]*` — no dots) and **at most one** of `unit` or `map`.
175
+ A step with neither is **still a unit step** — bare
316
176
  `- id: validate` is the complete minimal declaration. `unit:` is the
317
177
  optional dispatch-override bag (`exec`, `engine`, `model`, `llm`, `timeout`,
318
- `retry`, `on_error`, `env`, `isolation`; see
319
- [Exec (shell) units](#exec-shell-units),
320
- [Failure policy](#failure-policy) and
321
- [Worktree isolation](https://github.com/itlackey/akm/blob/main/docs/architecture/workflow-engine.md#worktree-isolation)).
178
+ `retry`, `on_error`, `env`; see
179
+ [Exec (shell) units](#exec-shell-units) and
180
+ [Failure policy](#failure-policy)).
322
181
  - `inputs` — on a `unit`/`map` step, the prior-step artifacts this step
323
182
  consumes, as bare reference strings (sub-paths legal:
324
183
  `steps.x.output.issues`, not just `steps.x.output`). This is how a step's
@@ -345,10 +204,9 @@ Checked by `akm lint --type workflows`:
345
204
  frontmatter, exactly — no titles, no `Step:`/`Step ID:` lines, no
346
205
  `# Workflow:` prefix on the H1. (Fenced code blocks are skipped when
347
206
  scanning for headings.)
348
- 2. A `unit` or `map` step **must** have a body section — its instructions,
207
+ 2. Every step **must** have a body section — its instructions,
349
208
  or its per-item template for a map step, byte-exact to the next H2 or
350
- EOF. A `route` step **may** have one (documentation, plus a gate rubric
351
- if it is gated). Everything before the first H2 is free preamble —
209
+ EOF. Everything before the first H2 is free preamble —
352
210
  indexed for search, shown in `akm show`, never dispatched.
353
211
  3. Inside a step's section, an optional `### gate` sub-heading starts that
354
212
  step's gate rubric, running to the section end — the format's **single
@@ -395,12 +253,12 @@ attached to this unit as input. Fix any failures before proceeding.
395
253
 
396
254
  ## Richer example
397
255
 
398
- Fan-out, routing, retries, gates, and a run budget:
256
+ Fan-out, retries, gates, and a run budget:
399
257
 
400
258
  ```markdown
401
259
  ---
402
260
  type: workflow
403
- description: Review changed files and route the outcome
261
+ description: Review changed files and triage the outcome
404
262
  params:
405
263
  changed_files: { type: array, description: Files to review }
406
264
  defaults: { engine: reviewer, model: balanced, timeout: 10m, on_error: fail }
@@ -418,7 +276,6 @@ steps:
418
276
  timeout: 5m
419
277
  retry: { max: 1, on: [timeout, llm_rate_limit] }
420
278
  on_error: continue
421
- isolation: worktree
422
279
  output: { type: object, properties: { file: { type: string }, verdict: { type: string } }, required: [file, verdict] }
423
280
  # `output` here describes the REDUCER RESULT, not one unit's result: the
424
281
  # default `collect` reducer folds per-item unit results into an array.
@@ -428,10 +285,7 @@ steps:
428
285
  inputs: [steps.review.output]
429
286
  output: { type: object, properties: { verdict: { type: string } }, required: [verdict] }
430
287
  - id: triage
431
- route:
432
- input: steps.aggregate.output.verdict
433
- when: [{ match: pass, step: ship }, { match: fail, step: rework }]
434
- default: manual-triage
288
+ inputs: [steps.aggregate.output]
435
289
  - id: ship
436
290
  - id: rework
437
291
  - id: manual-triage
@@ -467,8 +321,9 @@ Combine the per-file review verdicts — attached to this unit as input via
467
321
 
468
322
  ## triage
469
323
 
470
- Routes on the verdict `aggregate` reported: `pass` proceeds to `ship`, `fail`
471
- proceeds to `rework`, anything else goes to `manual-triage`.
324
+ Read the verdict `aggregate` reported (attached as input) and decide whether
325
+ the change should ship, be reworked, or go to manual triage; later steps act on
326
+ that decision.
472
327
 
473
328
  ## ship
474
329
 
@@ -477,7 +332,7 @@ Ship the change.
477
332
  ## rework
478
333
 
479
334
  Address the review findings. Confirming the fix is a fresh `akm workflow run`
480
- of this workflow, not a step this run routes back to.
335
+ of this workflow, not a step this run goes back to.
481
336
 
482
337
  ## manual-triage
483
338
 
@@ -490,13 +345,12 @@ Workflow prose is **never templated** — there is no `${{ … }}`/`{{ … }}`
490
345
  interpolation anywhere in a workflow's body, and no escape syntax to learn,
491
346
  because there are no delimiters in prose to escape.
492
347
 
493
- Bare reference strings appear in exactly three frontmatter positions, each an
348
+ Bare reference strings appear in exactly two frontmatter positions, each an
494
349
  unquoted-style YAML string:
495
350
 
496
351
  | Position | What it names |
497
352
  | --- | --- |
498
353
  | `map.over` | The list a map step fans out over. |
499
- | `route.input` | The value a route step matches on. |
500
354
  | `inputs` (each entry) | A prior step's artifact this step consumes. |
501
355
 
502
356
  Every reference resolves against exactly two roots:
@@ -595,70 +449,6 @@ the advisory was removed: it fired on essentially every step, could not be
595
449
  enforced (advisories never reach `--fail-on-flagged`), and flagged a state
596
450
  that is legitimate.
597
451
 
598
- ## Workflow outputs
599
-
600
- A workflow can declare a run-level export: the values a **completed run**
601
- promotes, projected from its steps' own artifacts. This is a Markdown
602
- frontmatter key, `outputs:`:
603
-
604
- ```yaml
605
- outputs:
606
- report:
607
- from: steps.summarize.output
608
- changed_count:
609
- from: steps.collect.output.total
610
- schema: { type: integer, minimum: 0 }
611
- ```
612
-
613
- Each entry is a mapping of exactly `from` (required) and `schema`
614
- (optional):
615
-
616
- - `from` is a `steps.<id>.output(.<segment>)*` reference into a step's own
617
- artifact — the same reference grammar `with:`/`inputs:` use elsewhere in
618
- this document — naming a declared step. `from: params.<name>` is
619
- rejected: a run's exports come from what its steps produced, not from its
620
- input params verbatim.
621
- - `schema`, when present, is validated against the same [enforced JSON
622
- Schema subset](#the-enforced-json-schema-subset) as a step's own `output:`
623
- schema, and is bound by the same limit: at most 256 KiB.
624
- - Output names follow the same pattern as params, `^[A-Za-z_][A-Za-z0-9_]*$`,
625
- and a workflow may declare at most 64.
626
-
627
- **This is a Markdown-only key.** GitHub-shaped YAML's root is a closed set —
628
- `name`, `on`, `jobs` — with no extension surface, the same reason a
629
- GitHub-shaped workflow cannot declare `params:` either. Composition itself
630
- is unaffected: the *composing* (parent) document must still be GitHub-shaped
631
- (only `jobs.<id>.steps[].uses` composes — see [Child
632
- workflows](#child-workflows)), and the *composed* (child) may be either
633
- format — but a child that wants to export more than `{runId, status}` must
634
- be authored in Markdown.
635
-
636
- **Different from a step's own `output:` schema.** A step's `output:` (see
637
- [Typed step artifacts](#typed-step-artifacts) below) is a typed-artifact
638
- contract on *one step*, enforced and retryable through that step's own gate
639
- loop. A workflow's `outputs:` is a *run-level export projection* over
640
- already-promoted step artifacts, resolved once, after every step has
641
- finished. They compose freely: a step can declare `output:` and the
642
- workflow's `outputs:` can project that same step's artifact under an export
643
- name.
644
-
645
- **Resolution happens once, at run completion**, over the run's persisted
646
- step evidence — never re-evaluated afterward. If a declared `from` cannot
647
- be resolved, reads a step artifact that was too large to persist in full, or
648
- its resolved value fails its declared `schema`, run completion itself
649
- rolls back: the run stays `active`, its final step stays `pending`, and no
650
- event is emitted. Fix the declaration (or the step that produces the value)
651
- and the next completion attempt resolves outputs again — a bad `outputs:`
652
- entry cannot silently strand a run as `completed` with missing exports.
653
-
654
- **What a completed run exports.** A run with an `outputs:` declaration
655
- exports exactly those resolved values. A run with none exports
656
- `{runId, status}` instead — synthesized whenever it is read, never
657
- persisted. This is what a parent step composing this workflow as a child
658
- sees at `steps.<id>.output`: see [Child execution](#child-execution) for how
659
- a composing step's own reference into a child's exports is checked at
660
- freeze time.
661
-
662
452
  ## Typed step artifacts
663
453
 
664
454
  When a step declares `output`, the promoted step artifact (the unit's
@@ -781,12 +571,10 @@ Bounds: 1–64 argv entries, each a non-empty string of at most 4096 bytes.
781
571
  ### `cwd`
782
572
 
783
573
  Optional and **relative**. It resolves inside the unit's working directory —
784
- the engine invocation's working directory normally, or the unit's fresh
785
- detached worktree under `isolation: worktree`. Absolute paths, Windows drive
574
+ the engine invocation's working directory. Absolute paths, Windows drive
786
575
  letters, `~`, and `..` segments are rejected by the parser *and* by the
787
576
  frozen-plan decoder, and containment is re-checked against the *resolved* base
788
- (symlinks included) immediately before the command is spawned. An exec unit
789
- cannot step outside the tree its isolation promised.
577
+ (symlinks included) immediately before the command is spawned.
790
578
 
791
579
  ### The output rule
792
580
 
@@ -989,7 +777,7 @@ akm deliberately does **not** transparently spill an oversized context to a file
989
777
  and pass a path instead. That would make the `AKM_INPUTS` contract conditional
990
778
  on the size of the data — sometimes JSON, sometimes a filename — so every
991
779
  command would have to handle both shapes, and the spill file would have to be
992
- placed, isolated and cleaned up inside a unit's worktree. A stable contract plus
780
+ placed and cleaned up. A stable contract plus
993
781
  an explicit error is the smaller, more predictable surface.
994
782
 
995
783
  ### The child's environment is an allowlist
@@ -1061,7 +849,7 @@ The historical `inherit_env` spelling is unsupported.
1061
849
  and **no `engine`/`model`** — an exec unit names no engine, so reporting the
1062
850
  workflow's `defaults.engine` there would describe a dispatch that never
1063
851
  happens. Field presence is the discriminator, the same way `fanOut` marks a
1064
- `map` step and `route` marks a route step:
852
+ `map` step:
1065
853
 
1066
854
  ```json
1067
855
  {
@@ -1227,30 +1015,14 @@ changing the defaults above never alters a run that is already in flight or
1227
1015
  being resumed — it keeps the widths it froze. The new defaults apply only to
1228
1016
  runs started after the upgrade.
1229
1017
 
1230
- ## Routing
1231
-
1232
- A `route` step makes classify-and-dispatch first-class: the engine resolves
1233
- the explicit `input:` expression, selects the matching `when:` branch (or
1234
- `default:`), and auto-skips the unselected branch targets as the spine
1235
- reaches them. **Routes are forward-only**: every target (each `when.step`
1236
- and `default`) must be a step declared *later* in the workflow than the
1237
- routing step, and a step never routes to itself — this keeps the plan a DAG,
1238
- so termination is structural rather than a runtime budget's job. A
1239
- `default:` that names an earlier step is a lint error, not a loop. An
1240
- unroutable value with no `default` fails the step rather than letting every
1241
- branch run.
1018
+ ## Going back
1242
1019
 
1243
- **"Go back and fix it" is a gate, not a backward route.** A failed gate
1020
+ **"Go back and fix it" is a gate.** A failed gate
1244
1021
  re-runs its *own* step with the judge's feedback, bounded by `gate.max_loops`
1245
1022
  — and a declared `output:` schema the promoted artifact fails is specifically
1246
- the error a gate loop retries through. A workflow that used to describe "loop
1247
- back to an earlier step until this passes" expresses that as a bounded gate
1248
- on the step doing the work, not as routing.
1249
-
1250
- Route decisions are journaled, so a resumed run replays the same choice.
1251
- Skips cascade: when a route step is itself skipped (it was the unselected
1252
- target of an earlier route), its own branch targets are skipped too — a
1253
- router that never decided selects nothing.
1023
+ the error a gate loop retries through. A workflow that wants to "loop back to an
1024
+ earlier step until this passes" expresses that as a bounded gate on the step
1025
+ doing the work.
1254
1026
 
1255
1027
  ## Failure policy
1256
1028