akm-cli 0.9.29 → 0.9.31

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 (162) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +10 -10
  3. package/ROADMAP.md +112 -0
  4. package/dist/commands/improve/improve-cli.js +33 -12
  5. package/dist/commands/sources/plugin-upgrade.js +46 -23
  6. package/dist/core/file-lock.js +10 -1
  7. package/dist/llm/client.js +33 -3
  8. package/dist/scripts/akm-migrate-node.js +5 -1
  9. package/dist/scripts/akm-migrate.js +5 -1
  10. package/docs/README.md +42 -41
  11. package/docs/agents/README.md +15 -0
  12. package/docs/agents/agent-install.md +14 -0
  13. package/docs/architecture/README.md +67 -0
  14. package/docs/architecture/adapters.md +272 -0
  15. package/docs/architecture/akm-architecture-decision-history.md +2055 -0
  16. package/docs/architecture/akm-core-principles.md +126 -0
  17. package/docs/architecture/architecture.md +579 -0
  18. package/docs/architecture/comparisons/claude-code-vs-akm-workflows-full.md +550 -0
  19. package/docs/architecture/decisions/0001-no-interpolation-attached-structured-context.md +72 -0
  20. package/docs/architecture/decisions/0002-unit-reuse-and-input-hash-scope.md +142 -0
  21. package/docs/architecture/decisions/0003-child-env-allowlist-and-provenance.md +189 -0
  22. package/docs/architecture/decisions/0004-task-input-contract-and-flag-coercion.md +97 -0
  23. package/docs/architecture/decisions/0005-task-result-vocabulary-and-legacy-read-mapping.md +108 -0
  24. package/docs/architecture/decisions/0006-task-source-version-routing.md +124 -0
  25. package/docs/architecture/decisions/0007-workflow-composition-bounds.md +83 -0
  26. package/docs/architecture/decisions/0008-task-binding-normalization.md +88 -0
  27. package/docs/architecture/decisions/0009-child-run-publication-column-parity.md +80 -0
  28. package/docs/architecture/decisions/0010-driver-protocol-cut.md +36 -0
  29. package/docs/architecture/decisions/0011-engine-run-loop-invariants.md +125 -0
  30. package/docs/architecture/decisions/README.md +62 -0
  31. package/docs/architecture/improvement.md +440 -0
  32. package/docs/architecture/internals/classification.md +154 -0
  33. package/docs/architecture/internals/fresh-host-rebuild-runbook.md +40 -0
  34. package/docs/architecture/internals/functional-contract-patterns.md +340 -0
  35. package/docs/architecture/internals/health-advisories.md +110 -0
  36. package/docs/architecture/internals/improve-workflow.md +713 -0
  37. package/docs/architecture/internals/indexing.md +498 -0
  38. package/docs/architecture/internals/registry-network-boundary.md +57 -0
  39. package/docs/architecture/internals/search.md +249 -0
  40. package/docs/architecture/internals/storage-locations.md +943 -0
  41. package/docs/architecture/persisted-data-compat.md +59 -0
  42. package/docs/architecture/reviews/env-secret-access.md +319 -0
  43. package/docs/architecture/runtime-boundary-design.md +153 -0
  44. package/docs/architecture/specs/0.9.0-close-out-plan.md +361 -0
  45. package/docs/architecture/specs/0.9.0-decisions.md +528 -0
  46. package/docs/architecture/specs/0.9.0-docs-code-drift-register.md +745 -0
  47. package/docs/architecture/specs/0.9.0-open-items-register.md +135 -0
  48. package/docs/architecture/specs/0.9.0-public-api-issue-backlog.md +740 -0
  49. package/docs/architecture/specs/0.9.0-release-surface-review.md +636 -0
  50. package/docs/architecture/specs/agent-command-engine-model-design.md +513 -0
  51. package/docs/architecture/specs/akm-0.9.0-bundle-adapter-spec.md +501 -0
  52. package/docs/architecture/specs/akm-0.9.0-ref-grammar-decision.md +291 -0
  53. package/docs/architecture/specs/akm-format-neutral-bundle-workspace-spec.md +2059 -0
  54. package/docs/architecture/specs/di-seams-plan.md +539 -0
  55. package/docs/architecture/specs/driver-protocol-keep-or-cut.md +275 -0
  56. package/docs/architecture/specs/fact-asset-type.md +142 -0
  57. package/docs/architecture/specs/improve-collapse-churn-detector-design.md +634 -0
  58. package/docs/architecture/specs/okf-support.md +148 -0
  59. package/docs/architecture/specs/ref.md +329 -0
  60. package/docs/architecture/specs/reviews/task-workflow-unification/README.md +72 -0
  61. package/docs/architecture/specs/reviews/task-workflow-unification/findings-consolidated.md +152 -0
  62. package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-alternatives.md +352 -0
  63. package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-bunqueue.md +300 -0
  64. package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-openworkflow.md +402 -0
  65. package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-1.md +758 -0
  66. package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-2.md +645 -0
  67. package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-3.md +602 -0
  68. package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-adversarial.md +27 -0
  69. package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-architecture.md +27 -0
  70. package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-coverage.md +74 -0
  71. package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-security.md +15 -0
  72. package/docs/architecture/specs/reviews/task-workflow-unification/panel-r2-adversarial.md +132 -0
  73. package/docs/architecture/specs/reviews/task-workflow-unification/panel-r2-verification.md +147 -0
  74. package/docs/architecture/specs/stash-conventions-code-spec.md +275 -0
  75. package/docs/architecture/specs/stash-organization-conventions.md +377 -0
  76. package/docs/architecture/specs/task-workflow-format-unification.md +721 -0
  77. package/docs/architecture/specs/workflow-engine-buy-vs-build.md +231 -0
  78. package/docs/architecture/specs/workflow-format-unification.md +437 -0
  79. package/docs/architecture/testing/manual-testing-checklist.md +3420 -0
  80. package/docs/architecture/testing/okf-v0.2-conformance-runbook.md +924 -0
  81. package/docs/architecture/testing/task-workflow-manual-tests.md +1817 -0
  82. package/docs/architecture/testing/testing-workflow.md +446 -0
  83. package/docs/architecture/workflow-engine.md +274 -0
  84. package/docs/guides/README.md +33 -0
  85. package/docs/guides/author-bundles.md +310 -0
  86. package/docs/guides/author-workflows.md +520 -0
  87. package/docs/guides/bundles.md +321 -0
  88. package/docs/guides/capture-knowledge.md +136 -0
  89. package/docs/guides/claude-code-vs-akm-workflows.md +30 -0
  90. package/docs/guides/concepts.md +93 -0
  91. package/docs/guides/discover-and-load.md +158 -0
  92. package/docs/guides/getting-started.md +240 -0
  93. package/docs/guides/improve-the-library.md +246 -0
  94. package/docs/guides/knowledge-management.md +18 -0
  95. package/docs/guides/recipes/headless-install.md +97 -0
  96. package/docs/guides/recipes/website-source.md +28 -0
  97. package/docs/guides/run-workflows.md +279 -0
  98. package/docs/guides/scheduling.md +123 -0
  99. package/docs/guides/use-with-any-agent.md +129 -0
  100. package/docs/guides/wikis.md +81 -0
  101. package/docs/maintainers/README.md +19 -0
  102. package/docs/maintainers/curate-workmap.md +336 -0
  103. package/docs/maintainers/eval.md +3 -0
  104. package/docs/maintainers/local-development.md +108 -0
  105. package/docs/maintainers/release-checklist.md +103 -0
  106. package/docs/migration/README.md +2 -2
  107. package/docs/migration/release-notes/0.0.13.md +1 -1
  108. package/docs/migration/release-notes/0.1.0.md +1 -1
  109. package/docs/migration/release-notes/0.2.0.md +1 -1
  110. package/docs/migration/release-notes/0.3.0.md +1 -1
  111. package/docs/migration/release-notes/0.5.0.md +1 -1
  112. package/docs/migration/release-notes/0.6.0.md +1 -1
  113. package/docs/migration/release-notes/0.7.0.md +1 -1
  114. package/docs/migration/release-notes/0.7.3.md +2 -2
  115. package/docs/migration/release-notes/0.7.4.md +1 -1
  116. package/docs/migration/release-notes/0.7.5.md +1 -1
  117. package/docs/migration/release-notes/0.8.0.md +1 -1
  118. package/docs/migration/release-notes/0.9.0.md +1 -1
  119. package/docs/migration/release-notes/0.9.14.md +1 -1
  120. package/docs/migration/release-notes/0.9.15.md +1 -1
  121. package/docs/migration/release-notes/0.9.16.md +1 -1
  122. package/docs/migration/release-notes/0.9.17.md +1 -1
  123. package/docs/migration/release-notes/0.9.19.md +1 -1
  124. package/docs/migration/release-notes/0.9.2.md +1 -1
  125. package/docs/migration/release-notes/README.md +27 -31
  126. package/docs/migration/v0.5-to-v0.6.md +759 -0
  127. package/docs/migration/v0.8-to-v0.9.md +1 -1
  128. package/docs/plans/0.9.2-agent-command-workflow-plan.md +423 -0
  129. package/docs/posts/README.md +30 -0
  130. package/docs/posts/akm-command-tour.md +495 -0
  131. package/docs/posts/akm-logo-sized.webp +0 -0
  132. package/docs/posts/belief-aware-memory-12.md +139 -0
  133. package/docs/posts/config-v2-lessons-16.md +191 -0
  134. package/docs/posts/improve-loop-autonomous-10.md +225 -0
  135. package/docs/posts/improve-pipeline-debugging-0803.md +179 -0
  136. package/docs/posts/intro-part-01.md +76 -0
  137. package/docs/posts/intro-part-02.md +221 -0
  138. package/docs/posts/intro-part-03.md +190 -0
  139. package/docs/posts/intro-part-04.md +221 -0
  140. package/docs/posts/llm-mode-reflect-13.md +166 -0
  141. package/docs/posts/openpalm-built-on-akm.md +86 -0
  142. package/docs/posts/progressive-disclosure-06.md +129 -0
  143. package/docs/posts/proposal-safety-net-15.md +112 -0
  144. package/docs/posts/release-0.5.0.md +247 -0
  145. package/docs/posts/release-0.6.0.md +117 -0
  146. package/docs/posts/release-0.7.0.md +159 -0
  147. package/docs/posts/release-0.8.0.md +332 -0
  148. package/docs/posts/scattered-skills-05.md +100 -0
  149. package/docs/posts/task-assets-persistent-workflows-11.md +222 -0
  150. package/docs/posts/team-skills-07.md +198 -0
  151. package/docs/posts/wiki-agent-knowledge-08.md +223 -0
  152. package/docs/posts/workflows-vaults-09.md +321 -0
  153. package/docs/product-surface.md +54 -0
  154. package/docs/reference/README.md +1 -2
  155. package/docs/reference/asset-types.md +168 -0
  156. package/docs/reference/cli.md +5 -6
  157. package/docs/reference/env-and-secrets.md +126 -0
  158. package/docs/reference/memory.md +124 -0
  159. package/docs/reference/refs.md +156 -0
  160. package/docs/reference/registry.md +489 -0
  161. package/docs/reference/website-sources.md +141 -0
  162. package/package.json +12 -15
@@ -0,0 +1,602 @@
1
+ # Review #3 — `task-workflow-format-unification.md` (v7) + the implementation it builds on
2
+
3
+ Reviewer: independent reviewer #3 of 3.
4
+ Repo: `/home/user/akm` @ `claude/akm-markdown-tasks-history-75l6du` (baselined on `claude/release-0-9-0-polish-d6sycl`).
5
+ Scope: the spec end-to-end, every factual claim it makes about the code, and the readiness of the
6
+ code it plans to build on.
7
+
8
+ ---
9
+
10
+ ## 0. Summary judgement
11
+
12
+ The **direction** is right and the grounding (§1) is sound: `uses:`/`run:`, the prose rule, and
13
+ "steps are tasks" genuinely reduce concept count, and the target-key XOR really does make a whole
14
+ error class structurally impossible. The `.yml` task format is, on inspection, worse than the spec
15
+ says it is (see F-04) and deserves to die.
16
+
17
+ The **v7 configuration section (§3.4) and the non-goals (§10) do not survive contact with the
18
+ code.** Three load-bearing claims are false:
19
+
20
+ 1. Freeze's "layer cascade" the spec says it is merely *exposing* does **not** include a config
21
+ defaults layer for anything but `engine` — `defaults.model` does not exist in the config schema
22
+ and is read nowhere, so the spec's own worked example silently does nothing today.
23
+ 2. The "kind-gated hard error" the spec says `llm:` exists to scope is **dead code** in
24
+ `freeze.ts`. The workflow surface already silently drops `llm` overrides on agent engines.
25
+ 3. §10's "no changes to IR, freeze, journal, leases, or replay" is contradicted by §3.4/§3.6/§3.7 on
26
+ at least six counts (agent persona → system prompt/tool policy; shell units without an engine;
27
+ `env` list-of-mappings; `cwd`/`shell`/`params` at dispatch; non-empty-`instructions` invariant;
28
+ the unit input-hash preimage). This is not authoring-surface-only work; it is an IR v3 schema +
29
+ hash-version change.
30
+
31
+ Plus two silent-loss bugs the plan would bake in: deleting `unit.output` conflates two
32
+ *semantically different* schemas (F-05), and moving `command:` argv → `run:` shell text destroys
33
+ the `akm`-binary resolution seam every shipped template depends on (F-06).
34
+
35
+ Recommendation: **do not sign off decision 14 as written.** §3.4 needs a config-layer reality pass
36
+ and §10 needs to be rewritten to admit an IR change. §3.3/§3.6 need the input-hash and
37
+ `instructions` invariants addressed before anyone starts.
38
+
39
+ ---
40
+
41
+ ## 1. Findings table
42
+
43
+ | # | Sev | Title | Evidence |
44
+ |---|---|---|---|
45
+ | F-01 | critical | Cascade's "config defaults" layer does not exist; the worked example is a silent no-op | `config-schema.ts:102-108`, `freeze.ts:169-171,193-209` |
46
+ | F-02 | critical | §10 "no IR/freeze changes" is false — §3.4/§3.6/§3.7 all require IR v3 schema changes | `ir/schema.ts:417-451`, `unit-dispatch.ts:9-31`, `native-executor.ts:1026-1027` |
47
+ | F-03 | critical | Shell (`run:`) units cannot exist in the current IR: no engine, empty instructions, no runner kind | `freeze.ts:52-62`, `ir/schema.ts:434-441`, `native-executor.ts:1104+` |
48
+ | F-04 | critical | Unit input-hash preimage is unaddressed — `run:`/`uses:`/`cwd`/`shell`/`params` would not re-dispatch | `step-work.ts:376-392` |
49
+ | F-05 | major | Deleting the `unit:` bag conflates two distinct `output` schemas and loses per-item validation on `map` | `program/schema.ts:125,186`, `compile.ts:204,229`, `native-executor.ts:54-56,663` |
50
+ | F-06 | major | `command:` argv → `run:` shell text destroys `resolveNestedAkmCommand` / akm-bin resolution | `runner.ts:263,343-348`, `command-executable.ts:18-22`, `src/assets/tasks/**` |
51
+ | F-07 | major | The "kind-gated hard error" §3.4 argues against is dead code; workflows already drop `llm` silently | `freeze.ts:67-73` |
52
+ | F-08 | major | `agent:` as a cascade selector requires system-prompt + tool-policy plumbing that does not exist | `native-executor.ts:1026-1027`, `renderers.ts:252-262`, `IrInvocation` `ir/schema.ts:78-84` |
53
+ | F-09 | major | `params:` now means three different things; §5 claims this as a simplification | §3.2 vs §5 vs `renderers.ts:164-176`, `ir/params.ts:34-56` |
54
+ | F-10 | major | Command-template filling is undefined for the actual placeholder grammar (`$ARGUMENTS`, `$1`..`$9`) | `renderers.ts:164-176`, `indexer/walk/matchers.ts:126` |
55
+ | F-11 | major | `env:` list-of-mappings breaks the IR `string[]` contract and the "names only" hash invariant | `ir/schema.ts:448`, `step-work.ts:353-357,387` |
56
+ | F-12 | major | Prose-optional on `uses:`/`run:` steps collides with the non-empty-`instructions` invariant | `workflows/parser.ts:281-296`, `ir/schema.ts:434-441` |
57
+ | F-13 | major | Migrator depends on the live task parser; yml→md is a path-changing content step in a journaled plan | `task-target-ref-migration.ts:27,44-58` |
58
+ | F-14 | major | Markdown tasks silently inherit the whole markdown lint/OKF/`improve` surface — including body rewrites of live prompts | `akm-adapter.ts:388-434`, `asset-placement.ts:157-169` |
59
+ | F-15 | minor | Model aliases are *not* portable to LLM endpoints or non-claude/opencode platforms | `model-aliases.ts:45-104` |
60
+ | F-16 | minor | Gate judges are outside the cascade; §3.4 never says whether that stays | `freeze.ts:109,263-278` |
61
+ | F-17 | minor | `on_error` is called steps-only in §3.4 but already exists at document defaults | §3.4 vs `program/schema.ts:210`, `compile.ts:231` |
62
+ | F-18 | minor | Adapter story for `.md` tasks is underspecified and partly contradicts `okf-support.md` | `adapters/index.ts:73-88`, `akm-workflow-adapter.ts:141-155`, `okf-support.md:47-63` |
63
+ | F-19 | minor | `status: draft` adds a second, orthogonal installation gate next to `enabled:` | §4 vs `tasks.ts:400-445` |
64
+ | F-20 | minor | `task_history.target_kind` is a persisted enum the new target vocabulary outgrows | `runner.ts:823-830,870-899` |
65
+ | F-21 | minor | Embedded-template enumeration fails *silently* on format change | `embedded.ts:91-97` |
66
+ | F-22 | minor | Composition loses single-file provenance (`IrUnitNode.source`) and can blow the 256 KiB instruction cap | `ir/schema.ts:444-445`, `compile.ts:186` |
67
+ | F-23 | minor | 0.7 md → 0.8 yml → 0.9 md whiplash; the existing collision guard must be inverted | `tasks.ts:154-163` |
68
+ | F-24 | minor | `prompt: agents/x` sends raw file bytes (frontmatter included) — worse than §3.5 describes | `runner.ts:592-593` |
69
+
70
+ Claims I checked and found **accurate**: `parser.ts` is 368 lines; `akm-task.json` is 87 lines and
71
+ loaded by nothing at runtime (`tests/integration/tasks-schema.test.ts:8` only); `start`/`next`/
72
+ `complete` are gone (`workflow-cli.ts:522-531`); `runWorkflowTask()` → `runWorkflowSteps()` works
73
+ (`runner.ts:352-416`); `UnitDispatchRequest` carries prompt/engine/timeoutMs/env/cwd/
74
+ sensitiveValues and results carry `failureReason` (`unit-dispatch.ts:9-43`); `output` really is
75
+ declared twice in `akm-workflow.json` (`:232,344`); command assets really do read an `agent:`
76
+ frontmatter key (`renderers.ts:235`); agent assets really are model-hint-only
77
+ (`renderers.ts:260`); `temperature` on an agent-kind engine really is a config error
78
+ (`engines.ts:105-120`); `resolveModel()` really is a one-level four-tier chain
79
+ (`model-aliases.ts:89-104`).
80
+
81
+ ---
82
+
83
+ ## 2. Critical
84
+
85
+ ### F-01 — The cascade's far layer does not exist. The worked example is a silent no-op.
86
+
87
+ §2 and §3.4 rest on: *"Freeze already implements a configuration cascade … §3.4 exposes this
88
+ existing mechanism."* The mechanism exists **for the two document layers only**.
89
+
90
+ `freeze.ts:50` builds `layers = [documentDefaults, unit]`. Resolution:
91
+
92
+ - engine — `selectedEngine()` walks layers nearest-first, then falls back to
93
+ `config.defaults?.engine` (`freeze.ts:151-155`). ✅ config layer exists.
94
+ - model — `exactModel()` walks layers, then `engine.model` (`freeze.ts:169-171`). **`config.defaults.model` is never consulted.**
95
+ - timeout — `effectiveTimeout()` walks layers, then `engine.timeoutMs`, then a builtin constant (`freeze.ts:193-209`). **No config-defaults timeout.**
96
+ - llm overrides — `mergedLlmOverrides()` deep-merges layers only (`freeze.ts:211-216`). No engine-node or config-defaults participation (the engine's own `temperature`/`maxTokens` go into the *snapshot*, `freeze.ts:231-236`, a different path).
97
+
98
+ And the config schema has no such key at all:
99
+
100
+ ```ts
101
+ // src/core/config/config-schema.ts:102-108
102
+ export const DefaultsSchema = z.object({
103
+ engine: engineName.optional(),
104
+ llmEngine: engineName.optional(),
105
+ improveStrategy: engineName.optional(),
106
+ }).passthrough();
107
+ ```
108
+
109
+ `.passthrough()` means the spec's literal worked example —
110
+ `{ "defaults": { "engine": "claude", "model": "sonnet" } }` (§3.4) — **validates today and does
111
+ absolutely nothing.** A user following the spec's teaching example gets silent configuration loss,
112
+ the exact failure mode `config-schema.ts:117-120` says the strict schema exists to prevent.
113
+
114
+ So the honest framing is the opposite of the spec's: freeze implements a *two-layer* nearest-wins
115
+ resolution with per-field engine fallbacks. §3.4 proposes a **five-layer** cascade. Three of those
116
+ five layers (config defaults, engine node as a uniform layer, persona node) do not exist as layers
117
+ and must be built, each with its own schema surface.
118
+
119
+ **Recommendation.** Rewrite §2's third bullet and §3.4's opening to say what is true: "freeze
120
+ resolves two document layers nearest-wins; §3.4 generalizes that to five." Then add an explicit
121
+ sub-section enumerating the *new config schema surface* (`defaults.model`, `defaults.timeout`,
122
+ `defaults.env`, …) with `.strict()` on it, plus a migration note that a pre-existing
123
+ passthrough `defaults.model` would change meaning from inert to load-bearing — which violates §8's
124
+ "No key survives with a changed meaning."
125
+
126
+ ### F-02 — §10 "no changes to IR, freeze, journal, leases, or replay" is false.
127
+
128
+ §10 asserts the §3.4 cascade and §3.6 flatten are "authoring-surface changes over the layer
129
+ resolution freeze already performs." Every one of the following requires an IR v3 shape change,
130
+ each of which trips `assertKeys` (`ir/schema.ts:417-433`) and therefore changes `plan_hash`
131
+ (`plan-hash.ts:22-34`) for every plan:
132
+
133
+ | §3.x feature | IR field needed | Blocking code |
134
+ |---|---|---|
135
+ | `agent:` persona (system prompt) | `IrUnitNode.systemPrompt` | `native-executor.ts:1026-1027` — "`systemPrompt`/`tools`/`cwd` come from the profile/asset, not the workflow unit" |
136
+ | `agent:` persona (tool policy) | new IR field + dispatch plumbing | no field on `UnitDispatchRequest` at all (`unit-dispatch.ts:9-31`) |
137
+ | `cwd:` as a value field | `IrUnitNode.cwd` | today `request.cwd` is populated only by worktree isolation |
138
+ | `shell:` | `IrUnitNode.shell` | no such concept |
139
+ | `params:` at step level | `IrUnitNode.params` | params are run-level only (`step-work.ts:326`) |
140
+ | `run:` target | a target discriminator on the node | `node.kind` is `"unit"\|"map"` only (`ir/schema.ts:392`) |
141
+ | `env:` literal mappings | `env` type widened | `validateStringArray(node.env, …)` (`ir/schema.ts:448`) |
142
+
143
+ `IrInvocation` today is exactly `{engine, model, timeoutMs, llm?}` (`ir/schema.ts:78-84`).
144
+
145
+ **Recommendation.** Replace §10's first bullet with an explicit IR-change budget: list the new
146
+ `IrUnitNode`/`IrInvocation` fields, state whether `WORKFLOW_IR_VERSION` bumps to 4 (it should — the
147
+ plan classifier already has an `unsupported-version` arm, `plan-classifier.ts:14`), and note that
148
+ every frozen-plan golden and the replay-fuzz suite re-baselines. Pre-release makes this cheap; the
149
+ spec pretending it is free makes it *unschedulable*.
150
+
151
+ ### F-03 — A `run:` step cannot be frozen at all today.
152
+
153
+ §3.6 says a shell unit "journals like any unit (`workflow_run_units`): no tokens, but status,
154
+ timing, attempts, and `failure_reason` behave normally." Three hard blocks:
155
+
156
+ 1. **Engine required.** `freezeInvocation()` (`freeze.ts:49-75`) throws `ConfigError("No workflow
157
+ engine is selected…")` when no layer and no `config.defaults.engine` names one. A workflow of
158
+ nothing but `run:` steps therefore **fails to freeze on a machine with no agent CLI installed** —
159
+ the single most obvious reason to want shell steps.
160
+ 2. **Non-empty instructions required.** `ir/schema.ts:434-441` fails a unit whose `instructions` is
161
+ not a non-empty string. A `run:` step whose prose section is optional (§3.3/§4) produces `""`
162
+ (`compile.ts:174`).
163
+ 3. **No shell runner.** `frozenUnitRunner`/`defaultUnitDispatcher` branch on `llm` / `sdk` / `agent`
164
+ (`native-executor.ts:1076,1104`). `FrozenEngineSnapshot` is `FrozenLlmEngine | FrozenAgentEngine`.
165
+ There is no fourth kind, and the two guards at `native-executor.ts:1050-1074` (env-unsupported,
166
+ isolation-unsupported) are written assuming a child process implies an agent.
167
+
168
+ **Recommendation.** §3.6 must specify a `kind: "shell"` frozen node with **no** `invocation`, make
169
+ `instructions` optional-when-shell (or synthesise the `run:` text as the instruction bytes), and
170
+ say explicitly that a shell-only workflow must freeze without any engine configured. Otherwise the
171
+ headline "`run:` is inline shell, no AI" is undeliverable.
172
+
173
+ ### F-04 — The unit input-hash preimage is never mentioned; replay would be wrong.
174
+
175
+ `step-work.ts:376-392` computes the per-unit `inputHash` over a fixed preimage
176
+ (`hashVersion: 4`): `template.instructions`, `item`, `inputs`, `params`, `dispatch`, `invocation`,
177
+ `schema`, `env`, `isolation`, `gateFeedback`. Its own comment states the contract: *"Every field
178
+ here is a PLAN-FROZEN input that changes what the backend is actually asked to do, so a completed
179
+ unit is reused ONLY when all of them match."*
180
+
181
+ None of the new dispatch-significant surface is in that preimage:
182
+
183
+ - the `run:` shell text (for a shell unit `instructions` may be empty or unrelated prose — editing
184
+ `run: bun test` → `run: bun test --coverage` would **reuse the completed row**);
185
+ - `uses:` ref and the resolved target;
186
+ - `cwd`, `shell`, step-level `params`;
187
+ - the appended `uses:`-step prose (§3.3) if the concat happens at prompt assembly rather than at
188
+ freeze — `instructions` is what is hashed, not the assembled `prompt`.
189
+
190
+ That last one is subtle and important. §3.3 says the append is "a deterministic concat" at "one
191
+ seam". If that seam is `buildUnitPrompt` (`step-work.ts:331`), the hash preimage does not see it and
192
+ two materially different asks hash identically — the same class of bug the `gateFeedback` inclusion
193
+ (`step-work.ts:363-370`) was added to fix.
194
+
195
+ **Recommendation.** Add a §3.8 "Replay identity": every new field enters the preimage,
196
+ `hashVersion` becomes 5, and the `uses:` prose append happens **at freeze**, folded into
197
+ `instructions`, so the existing preimage covers it for free. Also note the one-time consequence:
198
+ bumping `hashVersion` invalidates reuse for every in-flight run at cutover.
199
+
200
+ ---
201
+
202
+ ## 3. Major
203
+
204
+ ### F-05 — Deleting the `unit:` bag silently deletes per-item output validation.
205
+
206
+ §3.6: *"`output:` is declared **both** on the step and inside the bag — a live wart … The duplicate
207
+ `output` declaration disappears with the bag."* They are not duplicates.
208
+
209
+ - `unit.output` → `WorkflowUnitDraft.schema` (`compile.ts:229`) → `IrUnitNode.schema` →
210
+ `UnitDispatchRequest.schema` (`native-executor.ts:663`) → constrains the harness's structured
211
+ output and drives `runStructured` / `parse_error` retries (`native-executor.ts:928-929,1038,1092`).
212
+ - `step.output` → `IrStepPlan.outputSchema` → validates the **promoted step artifact** after
213
+ reduction, with a distinct failure mode that feeds the gate loop
214
+ (`native-executor.ts:54-62`: "the promoted artifact is validated … flagged (`artifactSchemaFailure`)
215
+ so the engine's bounded gate loop can re-run the step").
216
+
217
+ For a `map` step these are necessarily different schemas: `map.unit.output` types *one item's*
218
+ result, `step.output` types the *collected* array. Collapsing them onto one step-level `output:`
219
+ key makes per-item structured output unexpressible on fan-out, and quietly disables constrained
220
+ generation for every mapped unit.
221
+
222
+ **Recommendation.** Keep two keys under distinct names after the flatten — e.g. `output:` (step
223
+ artifact, unchanged) and `result:`/`unit_output:` (per-unit/per-item schema). Correct the §3.6
224
+ "wart" claim and the §6 table row that repeats it.
225
+
226
+ ### F-06 — `run:` shell text destroys akm-binary resolution for every shipped template.
227
+
228
+ Today `command:` is **pre-split argv** (`parser.ts:300-306`) executed without a shell
229
+ (`runner.ts:277`), and before spawning, `resolveNestedAkmCommand()` (`runner.ts:343-348`) rewrites a
230
+ bare `akm` in the executable position to the *current installation's* invocation via
231
+ `resolveAkmInvocation()` — including the `env VAR=… akm …` form (`command-executable.ts:10-22`).
232
+
233
+ All ten shipped templates are exactly this shape:
234
+
235
+ ```yaml
236
+ # src/assets/tasks/core/improve.yml
237
+ command: akm improve
238
+ ```
239
+
240
+ Under `run: akm improve` executed by `sh -c`, that rewrite is impossible without shell-text
241
+ parsing — which the spec explicitly wants to avoid ("no ref-vs-inline sniffing exists anywhere in
242
+ the format"). The scheduled invocation captures `PATH` in its descriptor
243
+ (`scheduler-invocation.ts:86`), so a globally-installed binary survives; a `bun run src/index.ts`
244
+ dev install, a version-managed install, or a compiled binary not on the captured `PATH` does not.
245
+ `akm task doctor` already flags `path-selected` bindings as a distinct status (`tasks.ts:739`) —
246
+ this change makes *every* shell task path-selected.
247
+
248
+ Related: `runCommandTask` runs with `cwd: process.env.HOME ?? os.tmpdir()` (`runner.ts:279`) while
249
+ `runPromptTask` uses `cwd: stashDir` (`runner.ts:522`). §3.4 introduces `cwd:` as a value field but
250
+ never states the default, and the two existing paths disagree.
251
+
252
+ **Recommendation.** §3.6 must state: (a) how `akm` self-reference is resolved under shell
253
+ execution — the honest answer is probably to inject `AKM_BIN` into the env and document
254
+ `$AKM_BIN improve` in templates, or to prepend the resolved bin dir to `PATH`; (b) the default
255
+ `cwd` for `run:` in tasks vs steps, explicitly reconciling `runner.ts:279` and `:522`.
256
+
257
+ ### F-07 — The hard error §3.4 argues against does not fire.
258
+
259
+ §3.4: *"`llm:` … was a grouping whose only job was to scope a kind-gated hard error, and that error
260
+ is incompatible with a cascade."* In `freeze.ts`:
261
+
262
+ ```ts
263
+ 67 const llm = engine.kind === "llm" ? mergedLlmOverrides(layers) : undefined;
264
+ 68 if (engine.kind !== "llm" && llm !== undefined) {
265
+ 69-73 throw new ConfigError(`Workflow engine "${name}" is an agent engine and cannot receive llm overrides.`, …);
266
+ ```
267
+
268
+ Line 68 is **unreachable**: `llm` is `undefined` in exactly the branch the guard tests. So today a
269
+ workflow that writes `defaults: { llm: { temperature: 0.2 } }` under an agent engine gets **silent
270
+ drop**, not an error. The real hard error lives on the *task* path
271
+ (`runner.ts:497-501`) and in `akm-task.json`'s `oneOf` (`:36-40`).
272
+
273
+ This matters two ways. First, the spec's premise is wrong about where the behavior is, which
274
+ weakens the case for dissolving `llm:` (the argument reduces to "the *task* error must relax",
275
+ which is true but much narrower). Second, the proposed capability notice is a strict
276
+ *improvement* over the current silent drop — the spec should claim that credit rather than
277
+ argue against a guard that never runs.
278
+
279
+ **Recommendation.** Fix `freeze.ts:67-73` independently of this proposal (compute
280
+ `mergedLlmOverrides(layers)` unconditionally, then branch). Amend §3.4 to describe the actual
281
+ state: workflow = silent drop (bug), task = hard error, target = uniform notice.
282
+
283
+ ### F-08 — `agent:` as a selector needs persona plumbing that does not exist.
284
+
285
+ §3.4 defines `agent:` as picking "the persona node — *who* runs it (system prompt, tool policy,
286
+ model preference)". Today an agent asset yields three things (`renderers.ts:252-262`): `prompt`
287
+ (system prompt), `toolPolicy` (`tools` frontmatter), `modelHint` (`model`).
288
+
289
+ Only the third is in the spec's flat value vocabulary. The other two are the ones that make a
290
+ persona a persona, and neither can reach a workflow unit: `native-executor.ts:1026-1027` states
291
+ outright that `systemPrompt`/`tools`/`cwd` "come from the profile/asset, not the workflow unit".
292
+ `UnitDispatchRequest.systemPrompt` exists but is used only by the frozen gate judge
293
+ (`frozen-judge.ts:38`), and for agent-CLI dispatch it is passed through
294
+ `buildAgentDispatchRequest` (`native-executor.ts:1035`) with no tool-policy channel at all.
295
+
296
+ There is also a **security seam** the spec walks past: `renderers.ts:246-250` documents that the
297
+ self-declared `tools` policy is subject to a *provenance ceiling* applied at the show layer, which
298
+ knows whether the asset came from the operator's own writable stash or a read-only third-party
299
+ source. Making `agent:` a first-class cascade selector resolved at freeze moves tool-policy
300
+ selection to a layer that does **not** have that provenance context. A third-party bundle shipping
301
+ `agents/helper` with a permissive `tools:` block, referenced by `agent: agents/helper` in a
302
+ workflow step, would bypass the ceiling.
303
+
304
+ **Recommendation.** Either (a) scope `agent:` to model-preference-only in 0.9.0 and say so
305
+ explicitly, or (b) add a §3.4 sub-section covering system-prompt and tool-policy plumbing plus the
306
+ provenance ceiling, and move the IR cost into the §10 budget (F-02).
307
+
308
+ ### F-09 — `params:` acquires three meanings; §5 sells that as simplification.
309
+
310
+ §5: *"`inputs:` declared / `with:` passed → `params:` both places — one word beats two."* After
311
+ this change `params:` means:
312
+
313
+ 1. **Declarations** at workflow document level: name → JSON Schema (`ir/params.ts:34-56`,
314
+ `akm-workflow.json:26-36`). Not values.
315
+ 2. **Workflow run arguments** on `uses: workflows/*` (§3.2) — coerced through those schemas.
316
+ 3. **Template placeholder fills** on `uses: commands/*` (§3.2) — an entirely different mechanism
317
+ (see F-10).
318
+ 4. And, per §3.4, a **cascading value field legal at every layer** — so `defaults: { params: … }`
319
+ would be values sitting one nesting level away from root `params:` which are schemas.
320
+
321
+ This is precisely the "three overloaded meanings" indictment the spec levels at `prompt:` in §3.1.
322
+ Worse, GHA's `inputs:`/`with:` split exists *because* the two are different things.
323
+
324
+ **Recommendation.** Keep two words. `params:` for declarations (existing), `with:` for passed
325
+ values at any call site. It is one extra word against four collapsed meanings, and it matches the
326
+ GHA muscle memory §5 is otherwise chasing.
327
+
328
+ ### F-10 — Command-template filling is undefined against the real placeholder grammar.
329
+
330
+ §3.2: *"`uses: commands/<name>` … `params:` fill the placeholders … a placeholder with no matching
331
+ param is an error at fill time."*
332
+
333
+ The actual command placeholder grammar is **positional plus named**:
334
+ `$ARGUMENTS`, `$1`–`$9`, and `{{named}}` (`renderers.ts:164-176`, `indexer/passes/metadata.ts:438-444`,
335
+ `indexer/walk/matchers.ts:126` — `$ARGUMENTS|$[123]\b` is part of *recognizing* a file as a command
336
+ asset). A `params:` **mapping** has no defined projection onto `$ARGUMENTS` or `$1`.
337
+
338
+ Combined with the hard-error rule, this is actively breaking: `$ARGUMENTS` is the single most
339
+ common placeholder, and a scheduled task has no arguments, so
340
+ `uses: commands/<anything-with-$ARGUMENTS>` becomes an unconditional freeze/fill error.
341
+
342
+ **Recommendation.** §3.2 must state the mapping explicitly: `{{named}}` ← `params.<name>`;
343
+ `$1`–`$9` ← a positional `args:` list or `params: { "1": … }`; `$ARGUMENTS` ← the joined positional
344
+ list, defaulting to empty string rather than erroring. Also decide the converse case the spec omits:
345
+ a `params:` key with no matching placeholder (recommend: notice, consistent with §3.4's
346
+ capability-notice stance).
347
+
348
+ ### F-11 — `env:` as a list of mappings breaks two existing contracts.
349
+
350
+ §3.7 claims the change "extends the existing schema additively — the workflow `unit.env` is already
351
+ a list of ref strings, so every currently-valid value stays valid." True of the *authoring* schema
352
+ (`akm-workflow.json:236-243`). False downstream:
353
+
354
+ - `ir/schema.ts:448`: `validateStringArray(node.env, …)` — a list containing a mapping fails plan
355
+ decode.
356
+ - `step-work.ts:353-357`: the input hash includes `env: template.env ?? null` with the documented
357
+ invariant *"`env` carries NAMES ONLY, never resolved values: hashing a resolved secret would leak
358
+ it into a durable hash oracle."* Literal mappings are values. Inline literals would land in both
359
+ `plan_json` and the durable hash.
360
+ - The spec also never says how `env` list concatenation interacts with **key collisions across
361
+ layers**: "later entries win" is stated for entries, but an `env/prod` group entry and a literal
362
+ entry can define the same key — is the winner determined by list position after concatenation
363
+ (yes, presumably) or by layer? Say it.
364
+ - `ProgramDefaults` (`program/schema.ts:204-211`) has **no** `env` field, so "legal at every layer"
365
+ is new surface at the document-defaults layer too.
366
+
367
+ **Recommendation.** State the IR representation for the new `env` shape (recommend: freeze
368
+ normalizes to an ordered list of `{ref}` / `{literals}` records, hash only refs + literal *keys*,
369
+ never literal values that resolve from `secrets/`), and add `env` to `ProgramDefaults`.
370
+
371
+ ### F-12 — Optional prose on `uses:`/`run:` steps contradicts a hard parser + IR rule.
372
+
373
+ `workflows/parser.ts:281-296` errors when a unit/map step has no `## <id>` section, and errors again
374
+ when the section is empty. `ir/schema.ts:434-441` independently rejects empty `instructions`. §3.3
375
+ ("Sections on `uses:` steps remain optional either way") and §3.6 (`run: bun test` with no section)
376
+ both require relaxing this.
377
+
378
+ That relaxation is not free: the "every unit step has prose" rule is what makes the prose rule
379
+ teachable and what the `instructions`-is-the-hash-preimage design leans on (`step-work.ts:378`).
380
+
381
+ **Recommendation.** Spell out the new body rule as a table: (no target → section **required**),
382
+ (`uses: commands/*` / `uses: tasks/<agent-task>` → optional, appended), (`run:` / `uses: scripts/*`
383
+ → optional, documentation only). And say what fills `instructions` in the IR when the section is
384
+ absent.
385
+
386
+ ### F-13 — The migrator depends on the live parser, and yml→md is a path change inside a journaled plan.
387
+
388
+ `scripts/akm-migrate/migrate/legacy/task-target-ref-migration.ts:27` imports `parseTaskDocument`
389
+ from `src/tasks/parser`, and its module doc says *"live task parsing remains strict 0.9 grammar."*
390
+ Replacing that parser with a markdown one breaks the migrator, which must read the **old** format.
391
+
392
+ Second problem: this migrator's plan is a list of `{filePath, before: Buffer, after: Buffer, mode}`
393
+ rewrites with `durabilityPaths` and a journaled, crash-resumable apply phase (`:44-58` and the
394
+ module doc). A format conversion **renames** `tasks/<id>.yml` → `tasks/<id>.md`. In-place byte
395
+ rewrite and rename-plus-write are different durability shapes; a crash between them leaves both
396
+ files present, which is exactly the "leftover files must never be silently invisible" lesson §8
397
+ invokes. Note also that `akmTasksSync` reads only `*.yml` today (`tasks.ts:414`) and
398
+ `akmTasksAdd` already has an *inverse* collision guard (`tasks.ts:154-163`, "a leftover `<id>.md`
399
+ still names the same task") that must be flipped.
400
+
401
+ **Recommendation.** §8 should require (a) a frozen copy of the v2 parser vendored into
402
+ `scripts/akm-migrate/`, with a note that `src/tasks/parser.ts` is free to change; (b) an explicit
403
+ ordering constraint (ref-rewrite phase completes before format-conversion phase, or they merge into
404
+ one phase); (c) the rename be journaled as delete+create with both paths in `durabilityPaths`.
405
+
406
+ ### F-14 — Markdown tasks inherit the whole markdown lint / OKF / `improve` surface, including body rewrites.
407
+
408
+ `akm-adapter.ts:401-414` special-cases `type === "task"` to parse as **pure YAML with
409
+ `frontmatter: null`**, and the comment explains why: *"so the TaskLinter's field checks see real
410
+ data and `missing-updated` never fires."* Once tasks are markdown with real frontmatter, that
411
+ branch goes away and tasks join `runBaseValidateChecks` — `missing-updated`, `missing-description`,
412
+ staleness, OKF stamping, the lot. §6's "Envelope … future stamped keys inherited free" is true, and
413
+ also means the lint output for every existing task changes on day one.
414
+
415
+ The bigger issue is `improve`. A task body is now indexed prose *and*, for an agent task, the
416
+ literal executable prompt (§4, §3.1: "the prose *is* the work"). The improve pipeline semantically
417
+ rewrites markdown asset bodies. That means an automated pass can change what a scheduled agent job
418
+ does, with no scheduler-side signal — the file mtime changes but `akm task sync` only reconciles
419
+ `schedule`/`enabled` drift (`tasks.ts:388`), not body drift.
420
+
421
+ **Recommendation.** §4 must state whether task bodies are `improve`-eligible. Recommend: exclude
422
+ `tasks/` from semantic rewrite passes in 0.9.0, and add a §6 row acknowledging the lint-surface
423
+ change with an explicit list of newly-applicable base checks.
424
+
425
+ ---
426
+
427
+ ## 4. Minor
428
+
429
+ ### F-15 — Model aliases are not as portable as §3.5 claims.
430
+
431
+ §3.5: *"`model: sonnet` resolves to the right string under a claude engine, an opencode engine, or
432
+ an LLM endpoint."* `BUILTIN_ALIASES` has exactly two platform columns, `claude` and `opencode`
433
+ (`model-aliases.ts:45-76`). `resolveModel` returns `entry?.platforms[platform] ?? model`
434
+ (`:103`) — so under any other harness id in `VALID_HARNESS_IDS` (codex, copilot, gemini, pi, …
435
+ referenced at `native-executor.ts:1016-1022`) `sonnet` passes through **verbatim** and the CLI gets
436
+ a bogus model string. For an LLM endpoint, `resolveLlmModel` looks up
437
+ `tier[engineName] ?? tier["llm"] ?? tier["*"]` (`:100,108`) — none of which the builtin table has —
438
+ so it also passes through verbatim unless the user has hand-written a global `modelAliases` entry.
439
+
440
+ Since §3.4's whole portability argument is "a persona node saying `model: opus` works under any
441
+ engine", this is load-bearing.
442
+
443
+ **Recommendation.** Soften §3.5 to "resolves under claude and opencode; other platforms and LLM
444
+ endpoints require a `modelAliases` entry", and add a §6 row for extending the builtin table (or
445
+ emitting an alias-unresolved notice, consistent with the capability-notice stance).
446
+
447
+ ### F-16 — Gate judges sit outside the cascade.
448
+
449
+ `freezeGateJudge` (`freeze.ts:263-278`) resolves entirely from `config.workflow.judgeEngine` with
450
+ `layers = []`. §3.4 never mentions gate judges. Does a step's `model:`/`agent:` apply to its gate?
451
+ (Almost certainly it should not — but the "legal at every layer, nearest wins" sentence implies it
452
+ does.)
453
+
454
+ **Recommendation.** One sentence in §3.4: gate judges resolve from `workflow.judgeEngine` only and
455
+ are outside the cascade.
456
+
457
+ ### F-17 — `on_error` is called steps-only but already lives at document defaults.
458
+
459
+ §3.4: *"**graph keys** (`map`, `route`, `inputs`, `output`, `gate`, `retry`, `on_error`,
460
+ `isolation`) are steps-only."* `ProgramDefaults.onError` exists (`program/schema.ts:210`), is
461
+ schema-declared (`akm-workflow.json:199-201`), and is consumed
462
+ (`compile.ts:231`: `unit?.onError ?? defaults?.onError ?? "fail"`). Removing it is an
463
+ unannounced breaking change; keeping it makes the tier rule false as stated.
464
+
465
+ **Recommendation.** Either exempt `on_error` (and note it), or list it in §8's conversion table as
466
+ removed.
467
+
468
+ ### F-18 — Adapter recognition for `.md` tasks is underspecified and partly contradicts `okf-support.md`.
469
+
470
+ §6's last row: *"markdown task recognized by `type: task` / `tasks/` residence."*
471
+
472
+ - `okf-support.md:60-62` states the opposite rule for the `akm` adapter: *"The `akm` adapter still
473
+ derives native identity and capability from its directory, extension, filename, and content
474
+ rules; **frontmatter `type` does not override those rules**."* Residence-based recognition is
475
+ consistent; `type: task`-based recognition is not.
476
+ - The standalone `akm-task` adapter currently keys entirely on `.yml`
477
+ (`akm-task-adapter.ts:42,53,106,144`) and `directoryList()` returns `["."]` — tasks live *anywhere*
478
+ under the component root, so "tasks/ residence" does not describe it either.
479
+ - Ordering: `akmWorkflowAdapter` precedes `akmTaskAdapter` (`adapters/index.ts:81-83`), and its
480
+ `looksLikeRoot` fires on a top-level `.md` with `type: workflow` (`akm-workflow-adapter.ts:141-155`).
481
+ A markdown-task bundle root needs a symmetric probe (`type: task`), and the two probes must be
482
+ proven disjoint — the goldens under `tests/fixtures/format-family-goldens/akm-task/` all assume
483
+ `.yml`.
484
+
485
+ **Recommendation.** Add a §6 sub-item covering the `akm-task` adapter rewrite: `.md` extension,
486
+ `type: task` recognition *within* an akm-task bundle, an explicit `type: task` `looksLikeRoot`
487
+ probe, and a note that the four `akm-task` goldens re-baseline.
488
+
489
+ ### F-19 — `status: draft` is a second, orthogonal installation gate.
490
+
491
+ §4: *"`status: draft` as author-now, arm-later (draft tasks are never installed by `sync`)."* There
492
+ is already `enabled:` (`tasks.ts:445+`, `runner.ts:169`) and a `disabled` run status
493
+ (`runner.ts:179`). Now there are two gates with different vocabularies, and the spec does not say
494
+ what `status: deprecated` does, what `enabled: true, status: draft` means, or how
495
+ `akm task list`/`doctor` report the distinction. Since OKF `status` is a lifecycle field that
496
+ tooling can rewrite, this couples scheduler state to a documentation field.
497
+
498
+ **Recommendation.** Pick one. Recommend: `status:` is documentation only and never gates
499
+ installation; `enabled:` remains the sole gate.
500
+
501
+ ### F-20 — `task_history.target_kind` is a persisted enum the new vocabulary outgrows.
502
+
503
+ `appendHistory` writes `target_kind` ∈ {workflow, command, prompt} and `target_ref` only for
504
+ workflow (`runner.ts:823-824`); `taskHistoryRowToResult` decodes exactly those three
505
+ (`:875-888`), with a `metadataVersion` 1/2 split already in place. The new target space is
506
+ {prose, `uses:commands/*`, `uses:scripts/*`, `uses:workflows/*`, `run:`}. §8's "the `akm task run
507
+ <id>` ABI does not change" is about argv, but history rows are also an ABI (`akm task history` is
508
+ a documented surface).
509
+
510
+ **Recommendation.** Add a `metadataVersion: 3` row to §8 with the old→new `target_kind` mapping and
511
+ a statement that historical rows continue to decode.
512
+
513
+ ### F-21 — Embedded-template enumeration fails silently on format change.
514
+
515
+ `listEmbeddedTasks()` swallows parse errors (`embedded.ts:91-96`: `catch { continue }`) and skips
516
+ non-command targets (`:97`). Converting the ten templates to markdown without updating this
517
+ function yields an **empty template list in `akm setup`** with no error — the wizard just offers
518
+ nothing. §8 says the templates "convert in this change"; it should also say `embedded.ts` converts
519
+ with them and that the silent `continue` becomes a loud failure.
520
+
521
+ ### F-22 — Composition loses provenance and can exceed the instruction cap.
522
+
523
+ `IrUnitNode.source` is a single `SourceRef` (`ir/schema.ts:444-445`, `compile.ts:186`). A step
524
+ composed from `uses: tasks/<id>` has two source files (the task and the workflow). One of them will
525
+ be dropped from every error message and lint pointer. Separately, `instructions` is capped at
526
+ 256 KiB (`ir/schema.ts:444`); task body + appended step section makes overflow reachable in a way
527
+ it was not before, and the error would name only one file.
528
+
529
+ **Recommendation.** Either make `source` an array, or state explicitly that a composed unit's
530
+ `source` points at the *step* and the referenced task's path is carried in a new sibling field.
531
+
532
+ ### F-23 — Format whiplash and an inverted collision guard.
533
+
534
+ `tasks.ts:154-163` exists because *"Pre-0.8.0 tasks were markdown; the 0.8.0 cutover moved them to
535
+ pure YAML … A leftover `<id>.md` still names the same task, so creating `<id>.yml` beside it must
536
+ collide loudly."* §4 calls the new format "the 0.7.x markdown task restored". Users who ran the
537
+ documented `v0.7-to-v0.8` migration will run it back. That is defensible for a pre-1.0 project but
538
+ should be *stated* in §8 rather than framed as pure forward progress — and the guard must be
539
+ inverted (a leftover `<id>.yml` must now collide with `<id>.md`), which is exactly the failure mode
540
+ §8's "never silently invisible" bullet targets.
541
+
542
+ ### F-24 — `prompt: agents/x` is worse than §3.5 says.
543
+
544
+ §3.5: *"the `.yml` task path **drops** an agent asset's model preference — `prompt: agents/x`
545
+ resolves the asset to prompt text only."* `resolvePromptText` does
546
+ `fs.readFileSync(assetPath, "utf8")` with **no frontmatter parse** (`runner.ts:592-593`). So the
547
+ agent's YAML frontmatter — `model:`, `tools:`, `description:` — is sent to the model **as part of
548
+ the prompt text**. Same for `prompt: commands/x`: the raw template with unfilled `$ARGUMENTS`
549
+ placeholders goes out verbatim.
550
+
551
+ This is a real bug in the current implementation and strengthens the case for the change; the spec
552
+ should cite it accurately as a §6 "Today" entry.
553
+
554
+ ---
555
+
556
+ ## 5. Cross-cutting: what this actually costs
557
+
558
+ The spec's §6 "shared plumbing" table reads as a code-*reduction* story. Counting the work the
559
+ findings above surface, the honest ledger is:
560
+
561
+ **Genuinely deleted:** `resolvePromptSource` sniffing, `rejectTargetFields`, `TASK_KEYS`,
562
+ `akm-task.json`'s standalone target `oneOf`, the ~12-flag `task add` surface, the `unit:` bag
563
+ nesting. Real, and worth having.
564
+
565
+ **Genuinely shared for the first time:** target resolution by ref subdir, command-template filling,
566
+ env assembly + redaction, the value-field cascade. Also real.
567
+
568
+ **Net-new, not in the table:** an IR v3 schema extension + version bump (F-02); a shell frozen node
569
+ kind and runner (F-03); an input-hash version bump and preimage extension (F-04); a new config
570
+ `defaults.*` schema surface (F-01); persona system-prompt/tool-policy plumbing with a provenance
571
+ ceiling (F-08); a placeholder-fill grammar (F-10); an env normalizer with a hash-safety rule
572
+ (F-11); a vendored legacy parser + a rename-safe migration phase (F-13); a rewritten `akm-task`
573
+ adapter + four goldens (F-18); `embedded.ts` (F-21).
574
+
575
+ **Test/golden churn:** 14 test files reference `unit:` (`tests/integration/workflows/*`,
576
+ `tests/workflows/*`, including `conformance/`, `driver-parity`, and `fuzz/replay-fuzz`);
577
+ `tests/fixtures/format-family-goldens/akm-task/{recognition,placement,lint,renderer}.json` all
578
+ assume `.yml`; every frozen-plan golden re-baselines on the IR bump; `docs/reference/workflows.md`
579
+ and `stash-conventions-code-spec.md` both document `unit:`.
580
+
581
+ None of that is a reason not to do it. It is a reason the spec should not say §10.
582
+
583
+ ---
584
+
585
+ ## 6. Answer to §9's remaining question
586
+
587
+ > Confirm the cascade model and the dissolution of `llm:`.
588
+
589
+ **Dissolving `llm:` — yes.** The grouping earns nothing: its workflow-side guard is dead code
590
+ (F-07), its task-side guard is a schema `oneOf` that a cascade cannot express anyway, and flat
591
+ `temperature`/`max_tokens`/`extra_params` with engine-capability notices is strictly better than
592
+ today's silent drop. Do fix `freeze.ts:67-73` first so the before/after comparison is honest.
593
+
594
+ **The cascade model — yes in shape, not as specified.** Nearest-wins layering is the right mental
595
+ model and matches what users know. But the spec currently sells it as *exposing an existing
596
+ mechanism* when three of its five layers do not exist (F-01), and buys that framing by asserting a
597
+ no-op change budget (F-02). Sign off on the *model*; require §3.4 and §10 to be rewritten with the
598
+ real layer inventory and the real IR cost before implementation starts. Specifically, before
599
+ sign-off I would want: the config-schema addition enumerated, `params:` de-overloaded (F-09), and
600
+ the merge semantics for mapping-valued fields (`params`, `extra_params`) stated — the spec says
601
+ "merged per-field, nearest wins" but `freeze.ts:211-216` deep-merges `llm` today, and those are
602
+ different answers.
@@ -0,0 +1,27 @@
1
+ # v8 Adversarial Review — new defects
2
+
3
+ critical | with: merge/replace semantics undefined for uses:tasks/x → uses:commands|workflows/y | §2.3 says step keys "override" the referenced task's keys and §2.2 says tasks/<n> inputs are "the task's own, overridable," but §2.2/§4 also declare with: is never cascaded and binds to "one referenced asset" — so when a step does `uses: tasks/x` and task x itself is `uses: commands/y with:{...}` (or `uses: workflows/y with:{...}`), the spec never states whether the step's with: mapping merges key-by-key with task x's own with:, wholly replaces it, or is simply illegal (since with: "isn't cascaded" yet §2.3 implies task/step are "adjacent cascade layers"); the ambiguity is sharper because commands' with: fills `{{name}}`/$ARGUMENTS while workflows' with: maps to declared param flags, so "override" means something different depending on what x ultimately targets (refs: §2.2, §2.3, §4, §5.1 "resolves recursively once").
4
+
5
+ critical | hashVersion 5 preimage omits the persona snapshot | §4 item 2 introduces a frozen persona snapshot (`{systemPrompt, toolPolicy, model, …valueFields}`) resolved through the agent: selector and consumed by dispatch, explicitly to fix runner.ts:592-593 shipping raw agent-file bytes — but the §5.3 hashVersion-5 preimage table only lists "item / inputs / dispatch / invocation / schema | as v4," with no row for the persona snapshot; changing the `agent:` selector or the referenced persona's systemPrompt/toolPolicy (a writable, frequently-edited bundle asset) would not change the unit hash, silently reusing a completed journal row dispatched under the old system prompt — the exact staleness bug hashVersion 5 was introduced to close for `run:`/prose (refs: §4.2, §5.3 table, step-work.ts:376-392 current v4 preimage which this table is supposed to supersede).
6
+
7
+ critical | .yml tombstone hard-error has no remedy for read-only/third-party bundles | §8 says sync/doctor/lint "each hard-error naming the file and the fix" for any `tasks/*.yml`, "never installed and never silently skipped," with no carve-out for bundle writability — but the repo's own existing migrator (`scripts/akm-migrate/migrate/legacy/task-target-ref-migration.ts:253-271`) treats read-only bundles specially precisely because "the migration must not write into a read-only bundle" and a user cannot fix content in a bundle they don't own; v8's tombstone rule regresses that precedent by hard-erroring `sync`/`doctor`/`lint` for the whole bundle (or task) with a "fix" the operator (non-maintainer) cannot apply, and never says whether the error is per-task (skip just that task) or fails the entire sync/install (refs: §8 tombstone rule; scripts/akm-migrate/migrate/legacy/task-target-ref-migration.ts:57-64,253-271).
8
+
9
+ major | mapping-type value fields (extra_params) merge rule contradicts current deep-merge behavior | §4 states cascade fields are "merged per-field, nearest wins" with only `env:` singled out as concatenating; this reads as whole-value replace for `extra_params` (a mapping), but the current implementation (`freeze.ts:211-216` `mergedLlmOverrides`) already deep-merges llm overrides across layers via `deepMergeConfig`. v8 doesn't say whether the new cascade module preserves deep-merge for mapping fields like `extra_params` or silently downgrades to whole-value replace — a real behavior change/regression risk left unstated (refs: §4 value-field table, src/workflows/ir/freeze.ts:211-216).
10
+
11
+ major | shell-kind IrInvocation union has no timeoutMs slot | §5.1/§5.2's discriminated union shows `{ kind: "agent", engine, … }` vs `{ kind: "shell", script, shell, cwd }` — the shell variant lists no timeout field, yet §4's value-field vocabulary explicitly includes `timeout` as legal on any step/task including `run:` shell work, and the current single-shape `IrInvocation` (schema.ts:82, 488-496) carries `timeoutMs` as a top-level required-ish field. The spec never states where a resolved shell-unit timeout is carried in IR v4, nor who enforces it (ShellUnitExecutor per §5.1, but with what deadline). (refs: §5.1, §5.2, §4 value fields, src/workflows/ir/schema.ts:82,488-496.)
12
+
13
+ major | orchestrator-owned shell units: no crash/restart semantics stated | §5.2 says shell units are "orchestrator-owned" and skip harness claiming ("a harness never claims shell work"), but the external driver protocol's claim/lease/reclaim machinery (native-executor.ts's claim-expiry/reclaim logic) exists specifically to recover from a crashed claimant; the spec never states what happens if the orchestrator process itself dies mid-`Bun.spawn()` for a shell unit — on restart, is the child process reattached, killed-and-retried (risking non-idempotent shell side effects), or left orphaned with a journal row stuck "dispatched"? This is unaddressed for the exact "races / blocked semantics / kill-abort paths" surface the spec claims to own (refs: §5.2, §5.1 ShellUnitExecutor, native-executor.ts claim/lease comments ~L87-103,645).
14
+
15
+ major | persona toolPolicy vs engine capability conflict has no resolution rule | §4 introduces both a persona snapshot with `toolPolicy` (item 2) and "capability notices" replacing kind-gates for engine-unconsumed fields (item 4), but never states what happens when a persona grants a tool (e.g. via `agents/<name>` toolPolicy) that the cascade-selected engine cannot support (e.g. an LLM engine with no tool-calling) — is that a lint/freeze notice like unconsumed value fields, a hard error, or silently inert? The two mechanisms (capability notices for value fields; provenance ceiling for persona tools) are never reconciled for this cross-cutting case explicitly called out as an attack surface.
16
+
17
+ major | improve-exclusion "by adapter policy" names no mechanism | §3 asserts "lint runs, improve is excluded by adapter policy" for task bodies, but no such exclusion mechanism (glob list, adapter capability flag, or improve-pipeline consult point) exists in the codebase today (`grep` for improve-exclusion hooks in src/core/adapter and src/improve returns nothing), and §9/§11 (M13) mark this "costed" without naming where in the improve pipeline the check lives — enforceability is asserted, not specified, leaving it plausible that a generic `akm improve` invocation over task-adapter-recognized files never consults "adapter policy" at all.
18
+
19
+ minor | H1-seeding collision when task body already has an H1 | §8's `name:` → body H1 migration doesn't say what happens when the source `.yml`'s `prompt: |` block or an already-inlined file (`prompt: ./file.md`) already begins with its own `#` heading — silently prepend a duplicate `name:` H1, skip seeding, or error? Left unspecified despite the review brief calling this out explicitly.
20
+
21
+ minor | argv shell-escaping portability unaddressed for the POSIX/`powershell` split introduced by §2.1 | §8 says `command: [argv…]` → `run:` "with each element shell-escaped," but §2.1 states `run:` executes via `sh` on POSIX and `powershell` on Windows by default — a single shell-escaping strategy applied at migration time (necessarily POSIX-flavored, since `[argv…]` is a Unix convention) will not portably re-quote for PowerShell's escaping rules, so a migrated array-form command may silently misbehave on the Windows default shell with no `shell:` override emitted by the migrator.
22
+
23
+ minor | with.arguments overflow beyond $9 is unspecified | §2.4 defines `with.arguments`'s whitespace-split words fill `$1`–`$9`; nothing states what happens to the 10th+ word (dropped silently vs concatenated into `$9` vs left for `$ARGUMENTS` only) — a minor but real gap in an otherwise precisely specified filling contract.
24
+
25
+ minor | `{{arguments}}` named-placeholder vs reserved `with.arguments` key collision unaddressed | §2.4's two filling rules ("`with:` mapping keys fill `{{name}}`" and "the reserved key `with.arguments` … fills `$ARGUMENTS`") both key off the literal string "arguments"; if a command template also contains a literal `{{arguments}}` placeholder, it's unstated whether `with: { arguments: "x" }` fills that placeholder too (double duty) or only ever feeds `$ARGUMENTS`/`$1`-`$9`, leaving `{{arguments}}` an "unused-key" or "unmatched-placeholder" lint case despite the value being supplied.
26
+
27
+ minor | draft/deprecated status transition doesn't say whether sync uninstalls a previously-enabled task | §3 pins `status: draft` → sync never installs and `status: deprecated` → installs with a warning, but doesn't address the state transition case: a task already installed (OS cron/launchd/schtasks entry present) whose `status:` later flips to `draft` — does the next `sync` remove/uninstall the existing entry, or just skip re-installing and leave a stale scheduled entry running the old (or edited) body indefinitely?