akm-cli 0.9.0 → 0.9.1-beta.2

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 (140) hide show
  1. package/CHANGELOG.md +724 -0
  2. package/README.md +28 -63
  3. package/STABILITY.md +4 -2
  4. package/dist/cli/parse-args.js +7 -1
  5. package/dist/commands/agent/contribute-cli.js +1 -1
  6. package/dist/commands/env/child-env.js +14 -0
  7. package/dist/commands/feedback-cli.js +7 -1
  8. package/dist/commands/health/llm-usage.js +2 -1
  9. package/dist/commands/health/surfaces.js +4 -77
  10. package/dist/commands/health.js +65 -11
  11. package/dist/commands/improve/distill/quality-gate.js +6 -1
  12. package/dist/commands/improve/eligibility.js +7 -1
  13. package/dist/commands/improve/eval-cases.js +2 -0
  14. package/dist/commands/improve/improve.js +126 -10
  15. package/dist/commands/improve/locks.js +7 -0
  16. package/dist/commands/improve/memory/memory-improve.js +9 -0
  17. package/dist/commands/improve/run-context.js +5 -0
  18. package/dist/commands/improve/session-asset.js +4 -0
  19. package/dist/commands/lint/base-linter.js +31 -7
  20. package/dist/commands/lint/index.js +205 -51
  21. package/dist/commands/lint/types.js +22 -1
  22. package/dist/commands/proposal/repository.js +17 -1
  23. package/dist/commands/sources/add-cli.js +8 -2
  24. package/dist/commands/sources/info.js +12 -2
  25. package/dist/commands/sources/installed-stashes.js +6 -1
  26. package/dist/commands/sources/migration-help.js +12 -3
  27. package/dist/commands/sources/self-update.js +9 -1
  28. package/dist/commands/tasks/tasks.js +8 -2
  29. package/dist/commands/workflow-cli.js +17 -11
  30. package/dist/core/abort-deadline.js +28 -0
  31. package/dist/core/adapter/adapters/agent-skills-adapter.js +83 -5
  32. package/dist/core/adapter/adapters/akm-adapter.js +13 -10
  33. package/dist/core/adapter/adapters/akm-lint.js +78 -22
  34. package/dist/core/adapter/adapters/akm-task-adapter.js +43 -20
  35. package/dist/core/adapter/adapters/dotenv-adapter.js +21 -0
  36. package/dist/core/adapter/adapters/tool-dir-shared.js +5 -3
  37. package/dist/core/asset/frontmatter.js +10 -1
  38. package/dist/core/common.js +147 -9
  39. package/dist/core/concurrent.js +32 -0
  40. package/dist/core/config/config-io.js +5 -45
  41. package/dist/core/config/schema/engines.js +14 -3
  42. package/dist/core/config/schema/workflow.js +11 -0
  43. package/dist/core/errors.js +25 -0
  44. package/dist/core/events.js +30 -24
  45. package/dist/core/extra-params.js +11 -0
  46. package/dist/core/file-lock.js +7 -1
  47. package/dist/core/fs-txn.js +15 -2
  48. package/dist/core/improve-result.js +5 -0
  49. package/dist/core/json-schema.js +344 -9
  50. package/dist/core/loopback.js +89 -0
  51. package/dist/core/migration-operation.js +17 -2
  52. package/dist/core/path-access.js +107 -0
  53. package/dist/core/paths.js +16 -2
  54. package/dist/core/redaction.js +86 -18
  55. package/dist/core/spawn-env.js +234 -0
  56. package/dist/core/state-db-scope.js +134 -0
  57. package/dist/core/state-db.js +1 -0
  58. package/dist/core/subprocess.js +181 -37
  59. package/dist/core/write-provenance.js +85 -0
  60. package/dist/core/write-source.js +33 -2
  61. package/dist/indexer/db/graph-db.js +17 -6
  62. package/dist/indexer/ensure-index.js +10 -3
  63. package/dist/indexer/index-written-assets.js +17 -2
  64. package/dist/indexer/indexer.js +86 -21
  65. package/dist/indexer/passes/memory-inference.js +4 -0
  66. package/dist/indexer/search/db-search.js +25 -17
  67. package/dist/indexer/walk/walker.js +6 -1
  68. package/dist/integrations/agent/detect.js +13 -1
  69. package/dist/integrations/agent/engine-resolution.js +24 -11
  70. package/dist/integrations/agent/model-aliases.js +1 -1
  71. package/dist/integrations/agent/profiles.js +9 -1
  72. package/dist/integrations/agent/spawn.js +15 -87
  73. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +21 -0
  74. package/dist/integrations/lockfile.js +55 -2
  75. package/dist/llm/client.js +14 -19
  76. package/dist/llm/embedder.js +23 -3
  77. package/dist/llm/embedders/remote.js +27 -2
  78. package/dist/output/html-render.js +40 -1
  79. package/dist/output/text/lint-format.js +17 -4
  80. package/dist/runtime.js +23 -1
  81. package/dist/scripts/akm-migrate-node.js +1714 -836
  82. package/dist/scripts/akm-migrate.js +1682 -804
  83. package/dist/setup/setup.js +22 -7
  84. package/dist/sources/providers/git-install.js +25 -2
  85. package/dist/sources/providers/git-stash.js +19 -0
  86. package/dist/sources/providers/git.js +1 -1
  87. package/dist/sources/snapshot-fetchers/content-extract.js +63 -1
  88. package/dist/sources/snapshot-fetchers/website-ingest.js +126 -20
  89. package/dist/storage/database.js +71 -7
  90. package/dist/storage/engines/sqlite-migrations.js +61 -2
  91. package/dist/storage/managed-db.js +19 -0
  92. package/dist/storage/repositories/index-connection.js +39 -4
  93. package/dist/storage/repositories/index-entries-repository.js +6 -1
  94. package/dist/storage/repositories/index-meta-repository.js +11 -0
  95. package/dist/storage/repositories/index-schema.js +17 -2
  96. package/dist/storage/repositories/index-vec-repository.js +43 -5
  97. package/dist/storage/repositories/workflow-runs-repository.js +66 -13
  98. package/dist/storage/sqlite-pragmas.js +12 -1
  99. package/dist/tasks/log-redaction.js +156 -0
  100. package/dist/tasks/parser.js +82 -5
  101. package/dist/tasks/runner.js +222 -17
  102. package/dist/tasks/scheduler-invocation.js +19 -0
  103. package/dist/tasks/schema.js +86 -1
  104. package/dist/text-import-hook.mjs +1 -1
  105. package/dist/workflows/concurrency-policy.js +95 -1
  106. package/dist/workflows/exec/dispatch-redaction.js +114 -0
  107. package/dist/workflows/exec/exec-unit.js +542 -0
  108. package/dist/workflows/exec/frozen-judge.js +114 -42
  109. package/dist/workflows/exec/native-executor.js +465 -238
  110. package/dist/workflows/exec/param-secrets.js +4 -3
  111. package/dist/workflows/exec/run-workflow.js +424 -219
  112. package/dist/workflows/exec/step-work.js +506 -167
  113. package/dist/workflows/exec/unit-dispatch.js +31 -1
  114. package/dist/workflows/exec/unit-writer.js +53 -13
  115. package/dist/workflows/exec/worktree.js +454 -41
  116. package/dist/workflows/ir/compile.js +26 -2
  117. package/dist/workflows/ir/freeze.js +82 -15
  118. package/dist/workflows/ir/schema.js +105 -20
  119. package/dist/workflows/parser.js +242 -19
  120. package/dist/workflows/program/schema.js +24 -0
  121. package/dist/workflows/renderer.js +32 -4
  122. package/dist/workflows/resource-limits.js +182 -0
  123. package/dist/workflows/runtime/runs.js +146 -6
  124. package/dist/workflows/validate-summary.js +17 -2
  125. package/docs/README.md +74 -32
  126. package/docs/migration/release-notes/0.9.0.md +2 -1
  127. package/docs/migration/v0.7-to-v0.8.md +2 -1
  128. package/docs/migration/v0.8-to-v0.9.md +3 -1
  129. package/docs/reference/README.md +11 -4
  130. package/docs/reference/bundle-types.md +19 -0
  131. package/docs/reference/cli.md +105 -16
  132. package/docs/reference/configuration.md +15 -2
  133. package/docs/reference/data-and-telemetry.md +30 -10
  134. package/docs/reference/supported-formats.md +50 -0
  135. package/docs/reference/workflow-schema.md +1014 -0
  136. package/docs/reference/workflows.md +37 -633
  137. package/package.json +13 -6
  138. package/schemas/akm-config.json +18 -5
  139. package/schemas/akm-task.json +27 -5
  140. package/schemas/akm-workflow.json +92 -13
@@ -9,639 +9,43 @@ an interruption without replaying completed units.
9
9
  > It is the canonical start/resume/execute command; there is no separate
10
10
  > external-driver protocol.
11
11
 
12
- > **Every workflow run needs a selected engine.** Freezing resolves an engine
13
- > for each unit. With no `defaults.engine`, akm falls back to a config-free
14
- > `opencode-sdk` engineprovider, model, and auth come from opencode's own
15
- > configuration and announces it once in the run's `warnings`. The fallback
16
- > needs the **`opencode` binary on PATH**: the bundled `@opencode-ai/sdk`
17
- > package is an HTTP client only and spawns `opencode serve` to have something
18
- > to talk to, so installing the npm package alone is not enough. With no
19
- > binary, freezing fails with `INVALID_CONFIG_FILE` and exit 78.
20
- >
21
- > A workflow with a non-empty `### gate` additionally requires
22
- > `workflow.judgeEngine` to name a configured LLM or agent engine the gate
23
- > judge is not covered by the fallback.
24
- >
25
- > `akm setup` normally selects a default execution engine. On a bare container
26
- > or CI image, either install opencode and let the fallback apply, or choose an
27
- > engine explicitly:
28
- >
29
- > ```sh
30
- > npm i -g opencode-ai # fallback route: puts `opencode` on PATH
31
- > # ...or pick an engine yourself:
32
- > akm config set engines.claude '{"kind":"agent","platform":"claude"}'
33
- > akm config set defaults.engine claude
34
- > ```
35
-
36
- ## akm workflow run
37
-
38
- `akm workflow run <run-id|workflows/ref>` starts or continues a persisted run
39
- and executes it until completion, failure, verification rejection,
40
- interruption, or an explicit invocation limit. Run state is scoped to the
41
- current project directory (nearest `.akm/config.json`, git root, bundle root,
42
- or current directory), so the same workflow can run independently in separate
43
- projects.
44
-
45
- ```sh
46
- akm workflow run workflows/ship-release --version 1.2.3
47
- akm workflow run workflows/review --changed_files a.ts --changed_files b.ts
48
- akm workflow run <run-id> --max-retries 2 --timeout 10m
49
- ```
50
-
51
- Parameter flags must come after the target and exactly match declared `params`
52
- keys. Values are coerced through each parameter's JSON Schema: repeat an array
53
- flag, pass an object or whole array as JSON, and use a bare boolean flag for
54
- `true`. There are no hyphen/underscore aliases. The old `--params` JSON bag is
55
- removed, and parameters are accepted only while creating a new run.
56
-
57
- `--max-steps <n>` leaves a partial run active after at most `n` steps.
58
- `--max-retries <n>` retries a failed step on the same run up to `n` additional
59
- times (0 through 100). `--timeout <duration>` bounds the whole invocation and
60
- accepts `N`, `Nms`, `Ns`, or `Nm`; bare `N` is milliseconds. A timeout or
61
- signal abort releases the run lease without advancing the active step, so the
62
- run remains resumable. Failed, gate-rejected, timed-out, and interrupted runs
63
- exit nonzero.
64
-
65
- The run freezes its plan, exact models, execution limits, parameter snapshot,
66
- and verifier selection at creation. Edits to source or config do not alter an
67
- in-flight run.
68
-
69
- ## akm workflow status
70
-
71
- `akm workflow status` shows the full run state — all step statuses, notes, and
72
- evidence — for a given run ID or workflow ref.
73
-
74
- ```sh
75
- akm workflow status <run-id>
76
- akm workflow status workflows/ship-release
77
- # When given a ref, resolves to the most-recently-updated run in the current scope
78
- ```
79
-
80
- Use this to inspect where a run is after a context window break, or to verify
81
- all steps completed cleanly before closing a PR.
82
-
83
- **`--units` — per-unit diagnostics.** For an orchestrated run, add `--units`
84
- to also list the run's journaled unit rows — each unit's id, status,
85
- `failure_reason`, and any result/error diagnostic text the row carries:
86
-
87
- ```sh
88
- akm workflow status <run-id> --units
89
- ```
90
-
91
- This is a **diagnostic** surface, deliberately kept out of the deterministic
92
- artifact graph. A step's promoted artifact (what `steps.x.output` resolves to,
93
- and what a gate judges) keeps only a failed unit's structured `failure_reason`
94
- — never the raw error text — so step evidence stays reproducible across
95
- resumes. When you need the human-facing *why*
96
- behind a failure, `--units` reads the unit journal directly and shows it
97
- without ever feeding that text back into an artifact or input hash.
98
-
99
- ## akm workflow list
100
-
101
- `akm workflow list` shows workflow runs in the current scope.
102
-
103
- ```sh
104
- akm workflow list # All runs in this scope (any status)
105
- akm workflow list --active # Only status=active (executable) runs
106
- akm workflow list --ref workflows/ship-release # Runs for a specific workflow
107
- ```
108
-
109
- `--active` filters to runs whose status is exactly `active` — currently
110
- executable work. A `blocked` run (parked awaiting a human `akm workflow resume`)
111
- or a `failed`/`completed` run is **not** active and is excluded, so a script
112
- that treats `--active` output as runnable never picks one up. Blocked runs
113
- remain listed by the unfiltered `akm workflow list` with their `blocked` status.
114
-
115
- **Example: see what is in flight**
116
-
117
- ```sh
118
- akm workflow list --active
119
- # → lists runs by workflow ref, status, currentStepId, and updatedAt
120
- ```
121
-
122
- ## Writing a workflow
123
-
124
- A workflow is an ordinary AKM markdown asset — the same envelope as every
125
- other type, OKF-conformant frontmatter plus a markdown body — whose
126
- frontmatter carries the entire orchestration graph (params, and how each step
127
- dispatches, fans out, routes, and gates) and whose body carries each step's
128
- instructions and gate rubric under plain headings, joined to the frontmatter
129
- by step id. There is **one** format: no separate YAML "program" surface, no
130
- `.yaml`/`.yml` workflow files.
131
-
132
- Use `akm workflow create --print` to print a valid starter, then edit it and
133
- register it with `akm workflow create`.
134
-
135
- ```sh
136
- akm workflow create my-release --print # Print the template, without writing
137
- akm workflow create my-release --from ./my-release.md
138
- akm lint --type workflows # Check for structural errors before using it
139
- ```
140
-
141
- **A minimal workflow:**
142
-
143
- ```markdown
144
- ---
145
- type: workflow
146
- description: Ship a tagged release to production
147
- params:
148
- version: { type: string, description: The semver version string to release }
149
- steps:
150
- - id: validate
151
- - id: build
152
- inputs: [steps.validate.output]
153
- ---
154
-
155
- # Ship Release
156
-
157
- ## validate
158
-
159
- Check that the `version` parameter follows semver and the tag does not
160
- already exist.
161
-
162
- ### gate
163
-
164
- - `git tag v<version>` does not already exist.
165
- - The version string matches `^\d+\.\d+\.\d+$`.
166
-
167
- ## build
168
-
169
- Run `npm run build && npm test`, using the validation from `validate`,
170
- attached to this unit as input. Fix any failures before proceeding.
171
- ```
172
-
173
- **Body rules** (checked by `akm lint --type workflows`):
174
-
175
- 1. Every level-2 heading must be `## <step-id>` for a step declared in
176
- frontmatter, exactly — no titles, no `Step:`/`Step ID:` lines, no
177
- `# Workflow:` prefix on the H1. (Fenced code blocks are skipped when
178
- scanning for headings.)
179
- 2. A `unit` or `map` step **must** have a body section — its instructions,
180
- or its per-item template for a map step, byte-exact to the next H2 or
181
- EOF. A `route` step **may** have one (documentation, plus a gate rubric
182
- if it is gated). Everything before the first H2 is free preamble —
183
- indexed for search, shown in `akm show`, never dispatched.
184
- 3. Inside a step's section, an optional `### gate` sub-heading starts that
185
- step's gate rubric, running to the section end — the format's **single
186
- reserved marker**. The judge that evaluates the step receives this whole
187
- section byte-exact. An omitted or empty `### gate` section needs no
188
- verification. A non-empty rubric enables mandatory fail-closed verification;
189
- frontmatter `gate:` only tunes its retry bound.
190
-
191
- Prose is never templated — see [The reference grammar](#the-reference-grammar)
192
- for how a step's instructions refer to run params, upstream artifacts, and a
193
- map unit's item.
194
-
195
- **Frontmatter**, validated by one published JSON Schema
196
- (`schemas/akm-workflow.json`): the standard AKM asset envelope (`type`,
197
- `description`, `tags`, `when_to_use`, `xrefs`, `updated`/`timestamp`, and the
198
- OKF v0.2 trust/lifecycle families) plus the orchestration keys:
199
-
200
- - `params` — name → `{ type, description }` (JSON-Schema-typed, unlike a bare
201
- description string).
202
- - `defaults` — run-level dispatch defaults (`engine`, `model`, `llm`,
203
- `timeout`, `on_error`), overridable per unit.
204
- - `budget` — run-lifetime ceilings (`max_units`, `max_tokens`; see *Budget
205
- ceilings* below).
206
- - `steps` — an ordered list. Each step has an `id`
207
- (`[A-Za-z_][A-Za-z0-9_-]*` — no dots) and **at most one** of `unit`, `map`,
208
- or `route`. A step with neither is **still a unit step** — bare
209
- `- id: validate` is the complete minimal declaration. `unit:` is the
210
- optional dispatch-override bag (`engine`, `model`, `llm`, `timeout`,
211
- `retry`, `on_error`, `env`, `isolation`; see below).
212
- - `inputs` — on a `unit`/`map` step, the prior-step artifacts this step
213
- consumes, as bare reference strings (sub-paths legal:
214
- `steps.x.output.issues`, not just `steps.x.output`). This is how a step's
215
- attached context sees upstream data, and how replay hashing gets its exact
216
- input set — a step re-dispatches only when the slice it actually consumes
217
- changes.
218
- - `output` — a JSON Schema for the step's promoted artifact.
219
- - `gate` — optional validation-loop configuration: `max_loops` bounds
220
- evaluator-optimizer retries (see *Gates judge the artifact*). The rubric
221
- itself lives in the body's `### gate` section. Without non-empty rubric
222
- text, the configuration is inert.
223
-
224
- No `version:`/`name:` keys — identity is the ref, and the frozen plan already
225
- versions execution semantics — and no step titles anywhere: a step is its id,
226
- and the asset's human name is its `description` and H1 like any other asset
227
- type.
228
-
229
- **A richer example** — fan-out, routing, retries, gates, and a run budget:
230
-
231
- ```markdown
232
- ---
233
- type: workflow
234
- description: Review changed files and route the outcome
235
- params:
236
- changed_files: { type: array, description: Files to review }
237
- defaults: { engine: reviewer, model: balanced, timeout: 10m, on_error: fail }
238
- budget: { max_units: 40, max_tokens: 200000 }
239
- steps:
240
- - id: discover
241
- output: { type: object, properties: { files: { type: array } }, required: [files] }
242
- - id: review
243
- map:
244
- over: steps.discover.output.files
245
- concurrency: 8
246
- unit:
247
- engine: reviewer
248
- model: deep
249
- timeout: 5m
250
- retry: { max: 1, on: [timeout, llm_rate_limit] }
251
- on_error: continue
252
- isolation: worktree
253
- output: { type: object, properties: { file: { type: string }, verdict: { type: string } }, required: [file, verdict] }
254
- # `output` here describes the REDUCER RESULT, not one unit's result: the
255
- # default `collect` reducer folds per-item unit results into an array.
256
- output: { type: array }
257
- gate: { max_loops: 2 }
258
- - id: aggregate
259
- inputs: [steps.review.output]
260
- output: { type: object, properties: { verdict: { type: string } }, required: [verdict] }
261
- - id: triage
262
- route:
263
- input: steps.aggregate.output.verdict
264
- when: [{ match: pass, step: ship }, { match: fail, step: rework }]
265
- default: manual-triage
266
- - id: ship
267
- - id: rework
268
- - id: manual-triage
269
- ---
270
-
271
- # Review Changes
272
-
273
- ## discover
274
-
275
- List the files that need review, drawn from the `changed_files` parameter.
276
-
277
- ### gate
278
-
279
- Every file named by `changed_files` is listed in the reported result.
280
-
281
- ## review
282
-
283
- This section is the **map unit template** — the engine attaches each unit's
284
- item (the file to review) and its index as context; instructions refer to
285
- "the file you were given," never a template expression.
286
-
287
- Review the file you were given for correctness bugs.
288
-
289
- ### gate
290
-
291
- Every changed file has a verdict of `pass` or `fail`.
292
-
293
- ## aggregate
294
-
295
- Combine the per-file review verdicts — attached to this unit as input via
296
- `inputs: [steps.review.output]` above — into one overall verdict, `pass` or
297
- `fail`.
298
-
299
- ## triage
300
-
301
- Routes on the verdict `aggregate` reported: `pass` proceeds to `ship`, `fail`
302
- proceeds to `rework`, anything else goes to `manual-triage`.
303
-
304
- ## ship
305
-
306
- Ship the change.
307
-
308
- ## rework
309
-
310
- Address the review findings. Confirming the fix is a fresh `akm workflow run`
311
- of this workflow, not a step this run routes back to.
312
-
313
- ## manual-triage
314
-
315
- Summarize the ambiguous verdict for a human to triage.
316
- ```
317
-
318
- ## The reference grammar
319
-
320
- Workflow prose is **never templated** — there is no `${{ … }}`/`{{ … }}`
321
- interpolation anywhere in a workflow's body, and no escape syntax to learn,
322
- because there are no delimiters in prose to escape.
323
-
324
- Bare reference strings appear in exactly three frontmatter positions, each an
325
- unquoted-style YAML string:
326
-
327
- | Position | What it names |
328
- | --- | --- |
329
- | `map.over` | The list a map step fans out over. |
330
- | `route.input` | The value a route step matches on. |
331
- | `inputs` (each entry) | A prior step's artifact this step consumes. |
332
-
333
- Every reference resolves against exactly two roots:
334
-
335
- | Reference | Meaning |
336
- | --- | --- |
337
- | `params.<name>` | A run parameter, by name. |
338
- | `steps.<id>.output( .<ident> \| [<int>] )*` | A prior step's artifact, addressed by producer step id; the path walks properties (`.name`) and array indexes (`[0]`). |
339
-
340
- Nothing else parses: no functions, no clock, no randomness, no ambient
341
- lookup. `item` and `item_index` are **not** part of the language — a map
342
- unit's item and its index are never referenced from anywhere in frontmatter
343
- or body. They arrive as **attached context** instead, the same way as
344
- everything else a unit needs.
345
-
346
- **Context attachment, not string splicing.** Each dispatched unit receives,
347
- alongside its byte-exact instructions, structured context:
348
-
349
- - every run **param** (params are run-scoped and documented non-secret — see
350
- *Params are not secret* below);
351
- - for a **map** unit, its **item** and **item index**;
352
- - the artifacts named by its step's **`inputs:`**.
353
-
354
- Instructions refer to this context in plain language — "clone the repository
355
- named by the `repo` parameter," "review the file you were given," "using the
356
- intake step's artifact attached to this unit" — never by splicing a value
357
- into the instruction string. This closes the injection class at the root:
358
- data never enters the instruction string, spliced or otherwise.
359
-
360
- `akm lint --type workflows` still checks every bare reference statically —
361
- unknown step, unknown param, bad path — at lint time.
362
-
363
- **What a step's output is.** `steps.<id>.output` resolves to the value the
364
- step's execution produced:
365
-
366
- - a `unit` step → the unit's structured result (when the unit declares
367
- `output`) or its text;
368
- - a `map` step → the collected array of per-item results, in item order
369
- (under `on_error: continue`, a failed item's slot is `null`), unless the
370
- step's own `output` schema describes a reduced, single-value shape instead.
371
-
372
- **An empty successful free-text output is treated as no output.** When a
373
- schemaless unit (one that declares no `output` schema) succeeds but returns
374
- the empty string, akm normalizes it to *absent*: nothing is journaled for its
375
- result, and its contribution to the step artifact is `null` — a `null` slot
376
- in a collected array, or `output = null` for a solo step. This absence is
377
- deliberate, so a live run and a resumed run promote the
378
- identical artifact. The practical consequence: a downstream step that
379
- declares an empty upstream result in its `inputs:` gets nothing meaningful
380
- attached for it — akm surfaces this loudly rather than silently attaching an
381
- empty string. A unit that declares an `output` schema is unaffected — an
382
- empty response is not valid JSON, so it fails as a parse error and can never
383
- satisfy a schema as a silent `null`.
384
-
385
- ## Frozen plans
386
-
387
- The first `akm workflow run <ref>` compiles the workflow and freezes the
388
- resulting plan on the run row (`plan_json` + `plan_hash`). **A run executes the
389
- plan compiled at creation; edits to the source file need a new run** — the file
390
- is never re-read for an in-flight run, so `run` and `resume` retain the same
391
- workflow no matter what changed on disk. Orchestration decisions are pure
392
- functions of the frozen plan, run params, and journaled unit results.
393
-
394
- **Resume is journaled replay.** Every dispatched unit is journaled with a
395
- content-derived identity — the step id plus a hash of the unit's frozen
396
- instructions, its item (for a map unit), its declared `inputs:` artifacts,
397
- and the params snapshot — and its input hash. On re-run, a journaled
398
- completed unit with the same identity and the same inputs is **reused**,
399
- never re-dispatched; a failed or missing unit is dispatched live. If a
400
- journaled completed unit matches by identity but its recorded inputs differ,
401
- the engine fails the step with a **replay divergence** error naming the unit
402
- — it never silently re-runs work whose inputs changed under it. (Divergence
403
- means the program produced different data for the "same" unit across
404
- invocations — a nondeterminism bug worth surfacing, not papering over.)
405
-
406
- ## Failure policy
407
-
408
- Fail-fast is the default. Per unit (or via `defaults.on_error`):
409
-
410
- - `on_error: fail` — the first failed unit fails the step, which fails the
411
- run (`akm workflow resume` re-opens it; `run` re-dispatches only
412
- incomplete units).
413
- - `on_error: continue` — failures are recorded in the step's results and the
414
- completion gate decides whether the step passes.
415
- - `retry: { max: <n>, on: [<failure_reason>…] }` — re-dispatches a failed
416
- unit up to `max` extra times when its recorded `failure_reason` is listed
417
- (e.g. `timeout`, `llm_rate_limit`, `spawn_failed`, `non_zero_exit`); every
418
- attempt is journaled separately.
419
-
420
- A unit's `output` schema is validated on every runner; a validation miss
421
- re-dispatches once with corrective feedback before the unit is recorded as
422
- failed.
423
-
424
- ## Routing
425
-
426
- A `route` step makes classify-and-dispatch first-class: the engine resolves
427
- the explicit `input:` expression, selects the matching `when:` branch (or
428
- `default:`), and auto-skips the unselected branch targets as the spine
429
- reaches them. **Routes are forward-only**: every target (each `when.step`
430
- and `default`) must be a step declared *later* in the workflow than the
431
- routing step, and a step never routes to itself — this keeps the plan a DAG,
432
- so termination is structural rather than a runtime budget's job. A
433
- `default:` that names an earlier step is a lint error, not a loop. An
434
- unroutable value with no `default` fails the step rather than letting every
435
- branch run.
436
-
437
- **"Go back and fix it" is a gate, not a backward route.** A failed gate
438
- re-runs its *own* step with the judge's feedback, bounded by `gate.max_loops`
439
- — and a declared `output:` schema the promoted artifact fails is specifically
440
- the error a gate loop retries through. A workflow that used to describe "loop
441
- back to an earlier step until this passes" expresses that as a bounded gate
442
- on the step doing the work, not as routing.
443
-
444
- Route decisions are journaled, so a resumed run replays the same choice.
445
- Skips cascade: when a route step is itself skipped (it was the unselected
446
- target of an earlier route), its own branch targets are skipped too — a
447
- router that never decided selects nothing.
448
-
449
- ## Typed step artifacts
450
-
451
- When a step declares `output`, the promoted step artifact (the unit's
452
- structured result, the collected array, or a reduced single value — see
453
- *What a step's output is* above) is validated against that schema **before**
454
- the step can complete. A mismatch fails the step with the validation errors
455
- in its summary. This is fail-fast on purpose: a bounded gate loop (next
456
- section) can re-run the step with those errors as corrective feedback.
457
-
458
- ## Gates judge the artifact; `max_loops` bounds the retry
459
-
460
- Under `akm workflow run`, a step with a body `### gate` rubric is gated on
461
- its **artifact**, not on engine prose: the judge receives the step's
462
- artifact as canonical JSON (clipped at 4000 characters) alongside the
463
- `### gate` section byte-exact, so the gate evaluates real results rather
464
- than a machine summary like "Executed 3 units". Each engine-driven gate
465
- evaluation is itself an LLM call and is journaled as a unit row
466
- (`<step-id>.gate:l<loop>`).
467
-
468
- `gate.max_loops: <n>` (frontmatter) turns the gate into a bounded
469
- evaluator-optimizer loop: on a rejection (or a typed-artifact schema
470
- mismatch) with loop budget left, the engine re-executes the step's units
471
- with the gate feedback and the missing-criteria list appended as attached
472
- context. The feedback changes each unit's inputs, so the re-run naturally
473
- dispatches fresh units instead of replaying journaled results. When the loop
474
- budget is spent, the rejection stands exactly as in the one-shot case.
475
-
476
- ## Fail-closed verification
477
-
478
- With no non-empty `### gate` rubric, no verification runs. When a rubric is
479
- present, the workflow requires `workflow.judgeEngine` to name a configured LLM
480
- or agent engine before the plan can be frozen. That verifier invocation is
481
- frozen into the run.
482
-
483
- Only a well-formed `complete: true` verdict advances a criteria-bearing step.
484
- A missing verifier, dispatch failure, or malformed result rejects the gate
485
- instead of silently bypassing it. A well-formed `complete: false` verdict
486
- returns its missing criteria and feedback and can trigger another bounded
487
- `max_loops` attempt.
488
-
489
- ## Budget ceilings
490
-
491
- The top-level `budget:` key declares run-lifetime ceilings: `max_units`
492
- (total dispatched units) and `max_tokens` (total reported token usage). Both
493
- counters are seeded from the unit journal, so they measure the **whole run
494
- across resumes**, not just the current invocation. Hitting a ceiling aborts
495
- the step's still-pending dispatches and fails the step with a
496
- `budget exceeded (<which> ceiling)` summary — budget exhaustion is a hard
497
- stop that ignores `on_error: continue`. Because the plan is frozen, raising
498
- a budget means starting a new run.
499
-
500
- ## One engine drives a run (the run lease)
501
-
502
- `akm workflow run` takes a **run lease** before dispatching anything: a
503
- random holder id with a 90-second expiry recorded on the run row, renewed
504
- between steps, and released when the invocation exits. A second
505
- `workflow run` against a live-leased run refuses up front, naming the holder
506
- and the expiry. An *expired* lease is claimable, so a crashed engine never
507
- wedges a run — wait out the expiry and re-run. While the lease is live the
508
- engine owns the step spine. `workflow status` remains read-only; run detail
509
- surfaces a live lease as `engineLease` (holder + expiry).
510
-
511
- ## Following a run's events
512
-
513
- There is no `akm workflow watch` (0.9.0: dropped — a foreground polling
514
- daemon in a one-shot CLI). `akm log --run <run-id>` reads the same
515
- `workflow_*` / `workflow_unit_*` events from the general append-only events
516
- stream: `--since '@offset:<id>'` gives a durable row-id cursor a cooperating
517
- process can poll from, in place of `watch --stream`'s in-process loop.
518
-
519
- ```sh
520
- akm workflow run <run-id> & # engine in one shell
521
- akm log --run <run-id> --since '@offset:0' # backlog so far
522
- akm log --run <run-id> --since '@offset:<nextOffset>' # poll for more, from the prior call's nextOffset
523
- ```
524
-
525
- Event metadata is ids/status/enums only — never workflow-authored content —
526
- so following a run's events is safe to pipe into logs or dashboards.
527
-
528
- ## Worktree isolation
529
-
530
- A file-mutating unit can declare `isolation: worktree` (agent and sdk
531
- runners). Each unit attempt gets a fresh **detached git worktree** of the
532
- run's base repository under a run-scoped temp directory; the worktree path
533
- is journaled on the unit row and passed to the harness as its working
534
- directory, so parallel fan-out units can never trample each other's working
535
- tree. After the unit finishes, a clean worktree (`git status --porcelain`
536
- empty) is removed automatically; a dirty one is retained and its path
537
- logged, so uncollected work is never destroyed. Declaring worktree isolation
538
- in a non-git directory fails the step cleanly before anything dispatches.
539
-
540
- > **⚠️ Warning — outputs matched by `.gitignore` are treated as disposable.**
541
- > A worktree-isolated unit's output survives only if it lands on a
542
- > **collectible path**: a tracked file, or an untracked file your repository
543
- > does **not** `.gitignore`. Anything a unit writes to a `.gitignore`d path —
544
- > build outputs, caches, logs, dependency directories like
545
- > `node_modules`/`dist`, or a scratch file under an ignored directory — is
546
- > **discarded** when its clean worktree is auto-removed. If a unit produces an
547
- > artifact that must survive, write it to a non-ignored path, or report it as a
548
- > result (a structured `output` / free-text result), before the unit returns.
549
-
550
- The clean probe deliberately does **not** pass `--ignored`, so "uncollected
551
- work" means tracked or untracked-*unignored* changes only. A worktree whose
552
- only residue is files your repository's own `.gitignore` matches is treated as
553
- clean and removed: those files are disposable by the repo's own declaration,
554
- and retaining a worktree after every package install or build would blow up
555
- disk under the temp root.
556
-
557
- ## Model tiers
558
-
559
- Reference semantic aliases in `model:` fields instead of exact model ids so a
560
- workflow stays harness-agnostic. Recommended vocabulary (convention, not
561
- hardcoded) via the config-root `modelAliases` key:
562
-
563
- ```jsonc
564
- {
565
- "modelAliases": {
566
- "fast": { "llm": "claude-haiku-4-5", "*": "claude-haiku-4-5" },
567
- "balanced": { "llm": "claude-sonnet-4-6", "*": "claude-sonnet-4-6" },
568
- "deep": { "claude": "claude-fable-5", "opencode": "opencode/claude-fable-5", "*": "claude-fable-5" }
569
- }
570
- }
571
- ```
572
-
573
- For an LLM engine, resolution checks its engine-name column, then `llm`, then
574
- `*`. Agent engines check their harness platform and then `*`.
575
-
576
- The built-in aliases `fable`, `opus`, `sonnet`, and `haiku` resolve per
577
- platform with no config. Point `deep` work (review, verification, judging) at
578
- `fable` — Anthropic's tier above Opus — and keep high-volume fan-out units on
579
- `fast`/`balanced`.
580
-
581
- Trust note: a workflow that fans out is authorizing **N parallel agents**, not
582
- one — the security section below applies with multiplied blast radius. The
583
- engine enforces a concurrency cap, a lifetime unit cap per run, per-unit
584
- timeouts, and (when the workflow declares them) run budget ceilings.
585
-
586
- Native fan-out (`akm workflow run`) uses the minimum of four limits: the map's
587
- declared `concurrency`, the run's frozen `workflow.maxConcurrency`, the
588
- selected frozen LLM engine's `concurrency` (including an SDK engine's fallback
589
- LLM), and the current host's CPU-derived safety limit. Reapplying host safety
590
- keeps a run safe when it resumes on a smaller machine.
591
-
592
- - **Unset (default):** the CPU-derived value `min(16, max(1, cores − 2))` — a
593
- conservative default that leaves headroom on the host and matches the
594
- original Claude-Code cap.
595
- - **Set:** an explicit positive integer, clamped when frozen to `[1, 64]`
596
- (values above 64 are clamped down, never rejected, so one config shared
597
- across machines with different core counts never hard-fails).
598
-
599
- ```console
600
- $ akm config set workflow.maxConcurrency 8 # raise the frozen workflow limit
601
- $ akm config get workflow.maxConcurrency
602
- 8
603
- ```
604
-
605
- ## Security: workflow sources are executed code
606
-
607
- Workflow steps that include shell commands run with **the full environment
608
- and PATH of the user invoking `akm workflow run`** — same as if the user had
609
- typed those commands in their shell. There is no sandbox, no env-var
610
- allowlist, and no separation between trusted and untrusted workflows.
611
-
612
- This is by design: a workflow is a runbook authored by you or by a stash
613
- maintainer you trust. The flexibility of "run any shell command, read any
614
- file, hit any network" is what makes workflows useful as automation.
615
-
616
- The consequence is that **you should treat workflow sources the same way you
617
- treat package dependencies**:
618
-
619
- - **Only add workflow sources you trust.** `akm bundle add github:<some-user>/stash`
620
- followed by `akm workflow run workflows/<their-thing>` is functionally
621
- equivalent to piping a stranger's bash script into your shell. Read the
622
- workflow file first (`akm show workflows/<name>`) before running it.
623
- - **Audit before run** for any workflow that touches secrets, deploys to
624
- production, or writes outside the project tree. Workflow steps can read
625
- any environment variable visible to the akm process — including secrets
626
- exported by your shell or injected via `akm env run` / `akm secret run`.
627
- - **Pin known-good versions** when adding workflow sources from a registry
628
- or git remote (`akm bundle add github:owner/stash#v1.2.3`), and update
629
- deliberately rather than via `akm bundle update --all`. A trusted workflow source
630
- can become hostile if its upstream is compromised.
631
- - **Workflow steps cannot escape this trust model** by being labeled
632
- `dryRun` or `interactive` — those flags affect bookkeeping, not execution.
633
- `akm workflow status` is read-only; `akm workflow run` executes configured
634
- units with your process's access.
635
-
636
- If you operate a CI runner or shared host where untrusted workflows might be
637
- executed, scope the process: a dedicated user account with no secrets in its
638
- environment, ephemeral working directory, and a network/filesystem allowlist
639
- enforced outside akm.
12
+ Workflows are one of the execution surfaces AKM directly orchestrates: AKM
13
+ retrieves every supported capability type, but a workflow's declared steps
14
+ not arbitrary indexed content are what actually gets dispatched. See
15
+ [Architecture: Core Principles](https://github.com/itlackey/akm/blob/main/docs/architecture/akm-core-principles.md) for
16
+ that boundary.
17
+
18
+ This page is a short map. The full contract now lives across four pages,
19
+ split by what you're doing:
20
+
21
+ - **[Running Workflows](https://github.com/itlackey/akm/blob/main/docs/guides/run-workflows.md)** operating a run:
22
+ start, check status, resume a blocked run, abandon one, and follow its
23
+ events. Includes the trust model for running a workflow sourced from
24
+ someone else's bundle.
25
+ - **[Author's Guide: Writing Workflows](https://github.com/itlackey/akm/blob/main/docs/guides/author-workflows.md)**
26
+ writing and testing a workflow definition: the markdown structure, a
27
+ minimal complete example, common authoring mistakes, choosing engines and
28
+ models, and engine-selection troubleshooting.
29
+ - **[Workflow Schema](../reference/workflow-schema.md)** — the exhaustive,
30
+ authoritative reference: every frontmatter key, the bare-reference grammar,
31
+ routing, failure policy, gates, and budget ceilings, with exact syntax.
32
+ - **[Architecture: The Workflow Engine](https://github.com/itlackey/akm/blob/main/docs/architecture/workflow-engine.md)**
33
+ how a frozen plan actually executes: persistence, the run lease, dispatch,
34
+ worktree isolation, concurrency limits, and resume-without-replay.
35
+
36
+ For task- or schedule-driven workflow runs — an `akm task` bound to
37
+ `--workflow <ref>` and reconciled with the OS scheduler — see
38
+ [Scheduling](https://github.com/itlackey/akm/blob/main/docs/guides/scheduling.md).
640
39
 
641
40
  ## See also
642
41
 
643
- - [Search & Discovery](https://github.com/itlackey/akm/blob/main/docs/guides/search-discovery.md) — find available workflows with `akm curate`
644
- - [Knowledge Management](https://github.com/itlackey/akm/blob/main/docs/guides/knowledge-management.md) capture workflow outputs as memories
645
- - [Improvement Loop](https://github.com/itlackey/akm/blob/main/docs/guides/improvement-loop.md) — improve workflow assets over time
646
- - [CLI Reference](../reference/cli.md) — full flag documentation for all `workflow` subcommands
647
- - [Concepts](https://github.com/itlackey/akm/blob/main/docs/guides/concepts.md) — workflow asset type and run-state storage
42
+ - [Discover and Load](https://github.com/itlackey/akm/blob/main/docs/guides/discover-and-load.md) — find available
43
+ workflows with `akm curate` before running one
44
+ - [Capture Knowledge](https://github.com/itlackey/akm/blob/main/docs/guides/capture-knowledge.md) — turn a workflow run's
45
+ outputs into searchable memories
46
+ - [Improve the Library](https://github.com/itlackey/akm/blob/main/docs/guides/improve-the-library.md) — feed run outcomes
47
+ back into a workflow asset's ranking and proposed edits
48
+ - [Concepts](https://github.com/itlackey/akm/blob/main/docs/guides/concepts.md) — the workflow asset type and run-state
49
+ storage in the broader AKM model
50
+ - [CLI Reference](cli.md) — full flag documentation for all `workflow`
51
+ subcommands