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,758 @@
1
+ # Critical Review — `task-workflow-format-unification.md` v7
2
+
3
+ Reviewer #1 of 3. Branch `claude/akm-markdown-tasks-history-75l6du`, baselined on
4
+ `claude/release-0-9-0-polish-d6sycl`. All citations are `path:line` against the
5
+ working tree.
6
+
7
+ **Verdict.** The *direction* is right and several of the spec's baseline claims
8
+ about the code check out exactly. But the document systematically understates
9
+ its own blast radius: §10's "no changes to IR, freeze, journal, replay" is
10
+ false at least four times over, and three load-bearing factual claims about the
11
+ current code (the "duplicate `output`", the command asset "already reads
12
+ `agent:`", "the assembled prompt is a frozen string produced at one seam") are
13
+ wrong in ways that invalidate the design decisions they justify. One change —
14
+ executing script assets' `run:`/`setup:` frontmatter — silently reverses a
15
+ documented security boundary.
16
+
17
+ ---
18
+
19
+ ## Findings table
20
+
21
+ | # | Sev | Title |
22
+ |---|---|---|
23
+ | C1 | Critical | `uses: scripts/*` executing `run:`/`setup:` reverses a documented "advisory, not executed" security boundary |
24
+ | C2 | Critical | The "duplicate `output`" is not a duplicate — `unit.output` and `step.output` are two different schemas; the flatten silently deletes per-unit/per-item structured output |
25
+ | C3 | Critical | §10's "no IR/freeze/journal/replay change" is false: shell units, `env:` mappings, optional prose and composition each force IR + input-hash changes |
26
+ | C4 | Critical | `run:`/shell work has no representation in the dispatch seam — `IrInvocation.engine` and `UnitDispatchRequest.engine` are required, and freeze hard-fails with no engine configured |
27
+ | M1 | Major | "Command assets already read an `agent:` frontmatter field today" is false — it is a *recognition heuristic*, nothing consumes it |
28
+ | M2 | Major | Command-template filling by named `params:` conflicts with the actual command placeholder contract (`$ARGUMENTS`/`$1`), and with §10's non-goal |
29
+ | M3 | Major | The `config defaults:` cascade layer largely does not exist — `defaults.model` is unschema'd/unread and `defaults.llm` is *explicitly retired in 0.9* |
30
+ | M4 | Major | §3.5's alias-portability claim is false for LLM and `opencode-sdk` engines — `model: sonnet` passes through verbatim |
31
+ | M5 | Major | The prose-append rule ("append to the assembled prompt at one seam") does not match how prompts are assembled; appending there puts call-site context after the item/inputs/gate/schema blocks |
32
+ | M6 | Major | `env:` literal values break the "names only, never values" plan/hash invariant and put literals into `brief` output |
33
+ | M7 | Major | `params` as a cascade-merged value field collides with workflow frontmatter `params:` (schema declarations) and with the "params are non-secret and un-redactable" contract |
34
+ | M8 | Major | Freeze must become IO-dependent (and probably async) to resolve `uses: tasks/<id>`, breaking its stated purity contract; lint never freezes, so composition refs go unvalidated until run start |
35
+ | M9 | Major | Migration losslessness is overclaimed: `name:`, `llm.supportsJsonSchema`/`contextLength`/`enableThinking`, and `prompt: agents/x` semantics are all lost or changed |
36
+ | M10 | Major | "Post-migration `sync`/`doctor` name any stray `.yml` by path" is not structural — the placement table and sync both key on extension, so strays become invisible or double-installed |
37
+ | m1 | Minor | Value fields legal "at every layer" are not legal on the engine node (`env`/`cwd`/`shell`/`params` silently pass through `.passthrough()` and are ignored) |
38
+ | m2 | Minor | Value fields on a `uses: workflows/*` task are structurally inert — the workflow run re-freezes from its own layers |
39
+ | m3 | Minor | Frontmatter `agent:`/`model:` on a task collide with indexer type-classification heuristics |
40
+ | m4 | Minor | `status: draft` as an execution gate overloads the OKF lifecycle field and duplicates `enabled:` |
41
+ | m5 | Minor | Setup wizard's YAML round-trip and `EmbeddedTask.command` will destroy markdown task bodies |
42
+ | m6 | Minor | Body-section requirement in the current parser contradicts optional sections for `uses:`/`run:` steps |
43
+ | m7 | Minor | `run:` shell semantics silently drop the argv-based nested-akm optimization and change the injection surface |
44
+ | m8 | Minor | Existing wart the plan inherits: `prompt: agents/x` sends the agent asset's raw YAML frontmatter to the model |
45
+ | m9 | Minor | okf-support.md explicitly names "YAML tasks" as a non-Markdown native asset and gates the promotion allowlist on `workflow` alone |
46
+ | m10 | Minor | Test/golden churn is larger than "schema and golden churn" implies |
47
+
48
+ ---
49
+
50
+ ## Critical
51
+
52
+ ### C1 — `uses: scripts/*` executing `run:`/`setup:` reverses a documented security boundary
53
+
54
+ **Spec claim.** §3.2: "`uses: scripts/<name>` — Execute the script asset per its
55
+ own `run`/`setup`/`cwd` metadata — no AI." §6: "one executor: inline shell +
56
+ script assets per `run`/`setup`/`cwd`, both surfaces."
57
+
58
+ **Evidence.** Those three fields are today explicitly, deliberately non-executable:
59
+
60
+ ```
61
+ src/indexer/passes/metadata.ts:140
62
+ // SECURITY NOTE: run, setup, and cwd are advisory metadata fields for AI agent consumers.
63
+ // They are NOT executed by akm directly. Consumers should validate and sanitize before execution. doclint:ignore
64
+ ```
65
+
66
+ This is not incidental. It sits alongside the sync attribution boundary whose
67
+ comment states the invariant plainly — "registering a bundle never activates
68
+ code" (`src/commands/tasks/tasks.ts:397-401`). Under the proposal, any installed
69
+ third-party bundle's `scripts/*.md` frontmatter `setup:` / `run:` becomes
70
+ executable the moment a local workflow or task names it. `setup:` in particular
71
+ implies "run an install command," which is the highest-value supply-chain target
72
+ in the whole asset model.
73
+
74
+ **Why it matters.** The spec's §10 non-goals say "No change to how `command`,
75
+ `script`, ... assets are authored or stored — this proposal executes and embodies
76
+ them." That sentence *admits* the change and then classifies it as a non-goal.
77
+ Executing them is the entire security delta.
78
+
79
+ **Recommendation.** Either (a) drop `uses: scripts/*` from v1 and keep `run:`
80
+ inline-only, or (b) add an explicit section covering: trust scope (own bundle
81
+ only? a per-bundle `executable: true` config opt-in?), whether `setup:` runs at
82
+ all, whether `cwd:` may escape the bundle root, and how `akm bundle add` warns.
83
+ Update `metadata.ts:140`'s SECURITY NOTE in the same change so the comment and
84
+ the behavior cannot disagree.
85
+
86
+ ---
87
+
88
+ ### C2 — The "duplicate `output`" is not a duplicate; the flatten deletes a real feature
89
+
90
+ **Spec claim.** §3.6: "`output:` is declared **both** on the step and inside the
91
+ bag — a live wart in `schemas/akm-workflow.json`" … "The duplicate `output`
92
+ declaration disappears with the bag."
93
+
94
+ **Evidence.** They are two distinct schemas with two distinct consumers:
95
+
96
+ - `ProgramUnit.output` (`src/workflows/program/schema.ts:124-125`, "JSON Schema
97
+ the unit's structured result must validate against") → compiled to
98
+ `WorkflowUnitDraft.schema` (`src/workflows/ir/compile.ts:229`) → frozen as
99
+ `IrUnitNode.schema` → becomes `UnitDispatchRequest.schema`
100
+ (`src/workflows/exec/native-executor.ts:663`), the LLM `responseSchema`
101
+ (`native-executor.ts:1092`), the structured-output retry trigger
102
+ (`native-executor.ts:928-929`), the `--result-file` contract in the driver
103
+ brief (`src/workflows/exec/brief.ts:574,656-657`), and per-unit result
104
+ validation (`src/workflows/exec/report.ts:1597-1618`).
105
+ - `ProgramStep.output` (`program/schema.ts:185-186`, "Step artifact schema") →
106
+ `WorkflowStepDraft.outputSchema` (`compile.ts:204`) → `IrStepPlan.outputSchema`
107
+ → validates the *promoted step artifact* (`src/workflows/exec/step-work.ts:555-561`).
108
+
109
+ For a `map:` step the distinction is load-bearing: `map.unit.output` types each
110
+ per-item result, `step.output` types the reduced array. `workflow-format-unification.md`
111
+ itself documents exactly that: "`output` describes the REDUCER RESULT: the
112
+ default `collect` reducer folds per-item unit results into an array"
113
+ (workflow-format-unification.md:127-129) — on a step whose `map.unit` also
114
+ carries settings.
115
+
116
+ **Consequence.** Collapsing both onto one step-level `output:` either (a) drops
117
+ per-unit structured output entirely — losing the LLM response-schema path, the
118
+ structured-retry path, and the driver `--result-file` contract — or (b) silently
119
+ changes the meaning of `output:` on map steps from "reducer result" to "per-item
120
+ result", breaking the shipped example and the reducer contract.
121
+
122
+ **Recommendation.** Fix the spec: there is no duplicate. If the bag must go, the
123
+ flatten needs two step-level keys with unambiguous names (e.g. `output:` = step
124
+ artifact, `unit_output:` / `item_output:` = per-dispatch schema), and §3.6 must
125
+ say which one a map step's `output:` means. Also drop the "live wart" framing
126
+ from §6's table row, which currently justifies the deletion on a false premise.
127
+
128
+ ---
129
+
130
+ ### C3 — §10's "no IR / freeze / journal / replay change" is false four times over
131
+
132
+ **Spec claim.** §10: "no changes to `map`/`route`/`gate` semantics, IR, freeze,
133
+ journal, leases, or replay (the §3.6 flatten and §3.4 cascade are
134
+ authoring-surface changes…)".
135
+
136
+ **Evidence — each of these is an IR-level change:**
137
+
138
+ 1. **Optional prose.** `decodeWorkflowPlanV3` rejects an empty
139
+ `instructions` (`src/workflows/ir/schema.ts:437-439`: `!node.instructions`
140
+ → `fail`). A `run: bun test` step with no `## run-tests` section produces an
141
+ empty-instructions unit. The workflow *parser* rejects it even earlier
142
+ (`src/workflows/parser.ts:282-296`, and workflow-format-unification.md:220-224
143
+ body rule 2).
144
+ 2. **`env:` shape.** `IrUnitNode.env` is `string[]`, validated by
145
+ `validateStringArray` (`ir/schema.ts:447`). §3.7's list-of-(ref-string |
146
+ mapping) is not a string array. The spec calls this "extends the existing
147
+ schema additively" — additive for the *authoring* JSON Schema, breaking for
148
+ the IR decoder.
149
+ 3. **Input hash.** `env` is part of the unit input-hash preimage
150
+ (`src/workflows/exec/step-work.ts:388`, `hashVersion: 4`). Changing `env`'s
151
+ shape changes the preimage → `hashVersion` must bump to 5 → every in-flight
152
+ run's journaled rows stop matching → replay divergence
153
+ (`native-executor.ts` `classifyUnitReuse`, "diverge" arm). Worse: if `run:` /
154
+ `cwd:` / `shell:` are added to `IrUnitNode` but *not* to the preimage
155
+ (the spec never mentions the hash), editing a step's shell command reuses the
156
+ completed row and never re-dispatches — a silent correctness bug.
157
+ 4. **Composition.** §3.3 "Resolution happens at freeze" means the frozen unit's
158
+ `instructions` now derive from a *different asset*. Nothing in the plan or
159
+ hash records which task version was composed, so `akm workflow report` cannot
160
+ attribute a unit's prose to its source task, and the plan-hash integrity check
161
+ (`src/workflows/runtime/runs.ts:236-241`, `requireExecutableWorkflowPlan`)
162
+ gives no signal that an upstream task changed between two runs of the same
163
+ workflow.
164
+
165
+ **Recommendation.** Rewrite §10 honestly: IR v3 gains fields, `hashVersion`
166
+ bumps, and pre-existing frozen plans become undecodable. Given 0.9.0 is
167
+ unreleased this is cheap — but it must be stated, and the hash preimage must
168
+ explicitly enumerate the new dispatch-significant fields (`run`, `shell`, `cwd`,
169
+ `env` in its new shape, and the composed task's identity).
170
+
171
+ ---
172
+
173
+ ### C4 — Shell work has no representation in the dispatch seam
174
+
175
+ **Spec claim.** §3.6: "A shell unit inside a workflow journals like any unit
176
+ (`workflow_run_units`): no tokens, but status, timing, attempts, and
177
+ `failure_reason` behave normally." §2 leans on `unit-dispatch.ts` being "the one
178
+ dispatch seam."
179
+
180
+ **Evidence.** That seam is agent-shaped, top to bottom:
181
+
182
+ - `UnitDispatchRequest` requires `prompt: string` and `engine: FrozenEngineSnapshot`
183
+ (`src/workflows/exec/unit-dispatch.ts:15,19` — neither optional).
184
+ - `IrUnitNode.invocation` is required and `validateInvocation` requires a
185
+ non-empty `engine` string (`ir/schema.ts:85, 481-497`).
186
+ - Freeze throws `ConfigError` "No workflow engine is selected" for *any* unit
187
+ when no engine resolves (`src/workflows/ir/freeze.ts:52-62`). **A workflow
188
+ consisting entirely of `run:` steps would refuse to freeze on a machine with
189
+ no agent CLI configured** — which is precisely the machine most likely to want
190
+ a shell-only workflow.
191
+ - `runUnit` bails with `dispatch_error` when `!workUnit.engine`
192
+ (`native-executor.ts:636-643`).
193
+ - The `brief`/`report` driver protocol surfaces a prompt for an external driver
194
+ to execute (`brief.ts:571-577`). What does a shell unit's brief contain? The
195
+ spec never says. If it emits the shell text, an external driver is now being
196
+ asked to execute arbitrary shell.
197
+
198
+ **Recommendation.** §6's "honest boundary" paragraph acknowledges *two agent*
199
+ dispatch paths; it must acknowledge a *third, non-agent* one. Concretely the
200
+ spec needs: a discriminated `IrUnitNode` (`kind: "unit" | "shell"`), an optional
201
+ `invocation`, a `ShellDispatchRequest` variant on the seam, an explicit
202
+ "shell-only workflows freeze without an engine" rule, defined `failure_reason`
203
+ mapping for exit codes/timeouts, and a defined `brief`/`report` behavior for
204
+ shell units (most likely: refuse — shell units are engine-executed only).
205
+
206
+ ---
207
+
208
+ ## Major
209
+
210
+ ### M1 — "Command assets already read an `agent:` frontmatter field today" is false
211
+
212
+ **Spec claim.** §3.4: "(Command assets already read an `agent:` frontmatter field
213
+ today — a referenced asset naming its preferred persona is existing behavior,
214
+ corroborating the asset-as-layer model.)"
215
+
216
+ **Evidence.** `agent` in a markdown file's frontmatter is a *type-classification
217
+ signal only*:
218
+
219
+ ```
220
+ src/indexer/walk/matchers.ts:234-236
221
+ if ("agent" in fm) { return { type: "command", specificity: 18 }; }
222
+ src/core/adapter/adapters/tool-dir-shared.ts:201
223
+ // command-shaped body/frontmatter signals … an `agent` frontmatter key.
224
+ ```
225
+
226
+ Nothing reads the *value*. There is no code path that resolves a command asset's
227
+ `agent:` to a persona at dispatch. A repo-wide search for a consumer turns up
228
+ only these two classification sites.
229
+
230
+ **Why it matters.** This is the sole cited empirical corroboration for the
231
+ asset-as-cascade-layer model in the round-6/7 redesign — the reversal
232
+ Decision 14 asks for explicit sign-off on. Sign-off is being requested partly on
233
+ a false premise.
234
+
235
+ **Recommendation.** Strike the parenthetical, or restate it accurately ("`agent:`
236
+ exists on command assets today as a recognition signal; this design gives it
237
+ semantics for the first time"). Note that giving it semantics also means an
238
+ untrusted bundle's command asset can now select a persona — worth a sentence.
239
+
240
+ ---
241
+
242
+ ### M2 — Template filling by named `params:` conflicts with the real command placeholder contract
243
+
244
+ **Spec claim.** §3.2: "`uses: commands/<name>` — Fill the command asset's
245
+ template (its type contract — 'a prompt template with placeholders'), dispatch
246
+ an agent with the result | `params:` fill the placeholders." "a placeholder with
247
+ no matching param is an error at fill time."
248
+
249
+ **Evidence.** The command asset's placeholder vocabulary is **positional**, not
250
+ named:
251
+
252
+ ```
253
+ src/indexer/walk/matchers.ts:126
254
+ const COMMAND_PLACEHOLDER_RE = /\$ARGUMENTS|\$[123]\b/;
255
+ ```
256
+
257
+ There is no named-placeholder syntax in command assets. `params: { scope: team }`
258
+ (the spec's own §4 example) has nothing to bind to. Introducing named
259
+ placeholders is a change to how command assets are authored — which §10
260
+ explicitly declares a non-goal.
261
+
262
+ The one named-substitution mechanism that exists is the workflow prompt
263
+ preamble's `{{PARAMS_JSON}}` (`src/workflows/exec/step-work.ts:469`), which is a
264
+ whole-blob injection, not per-placeholder filling — and workflow-format-unification
265
+ §2.3/§1.3 spent a whole section *removing* moustache templating from prose
266
+ because "the fake templating trap" (workflow-format-unification.md:57-63) was a
267
+ standing cost. §3.2 re-introduces a templating language into asset bodies.
268
+
269
+ **Recommendation.** Pick one and specify it: (a) positional — `params:` becomes a
270
+ list and `$1`/`$ARGUMENTS` fill from it; (b) named — declare the syntax, declare
271
+ it a change to the command asset contract, and remove the §10 non-goal; or (c)
272
+ no filling — `params:` are attached as context the way workflow params are, which
273
+ is consistent with the "no interpolation" decision the sibling spec already made.
274
+ Also state whether the "no matching param is an error" rule is symmetric (an
275
+ unused param — error or notice?).
276
+
277
+ ---
278
+
279
+ ### M3 — The leftmost cascade layer (`config defaults:`) largely does not exist
280
+
281
+ **Spec claim.** §3.4's cascade begins at "config defaults: (global)", and the
282
+ worked example is
283
+ `{ "defaults": { "engine": "claude", "model": "sonnet" } }`.
284
+
285
+ **Evidence.**
286
+
287
+ - `DefaultsSchema` declares only `engine`, `llmEngine`, `improveStrategy`
288
+ (`src/core/config/config-schema.ts:102-108`). It is `.passthrough()`, so
289
+ `defaults.model` would *validate* — and be read by nobody.
290
+ - `exactModel()` never consults `config.defaults` — it walks `layers`, then falls
291
+ back to `engine.model` (`src/workflows/ir/freeze.ts:169-171`). Same for
292
+ `effectiveTimeout` (`freeze.ts:193-209`).
293
+ - `defaults.llm` and `defaults.agent` are **explicitly hard-rejected**:
294
+ `` `defaults.${key} is retired in 0.9` `` (`config-schema.ts:234-241`). The
295
+ spec proposes putting `temperature`, `max_tokens`, `extra_params` at exactly
296
+ that scope.
297
+
298
+ So the "one vocabulary appears at every scope" claim requires: new
299
+ `DefaultsSchema` fields, new readers in `exactModel`/`effectiveTimeout`/
300
+ `mergedLlmOverrides`, `akm config set defaults.*` support, and an explicit
301
+ un-retirement of a key the 0.9 cutover deliberately killed. None of this is
302
+ scoped in §6's cost table.
303
+
304
+ Second-order problem: a global `defaults.model` applies to **all** engines, and
305
+ model strings are per-platform. A user with a claude engine and an LLM endpoint
306
+ gets one `model` value cascading into both. §3.5 says aliases save this — see M4,
307
+ they don't.
308
+
309
+ **Recommendation.** Enumerate the new config-schema surface in §6. Explain the
310
+ interaction with the `defaults.llm` retirement (why is it coming back, in flat
311
+ form, one release after being killed?). Decide whether `defaults.model` is
312
+ per-engine-kind or genuinely global, and if global, say what happens when it is
313
+ an alias with no column for the selected platform.
314
+
315
+ ---
316
+
317
+ ### M4 — Alias portability across engine kinds is claimed but not implemented
318
+
319
+ **Spec claim.** §3.5: "Aliases are **per-platform**, which is what makes a shared
320
+ asset portable: `model: sonnet` resolves to the right string under a claude
321
+ engine, an opencode engine, or an LLM endpoint." And: "Aliases are therefore
322
+ legal at every layer — a persona node saying `model: opus` works under any
323
+ engine."
324
+
325
+ **Evidence.** `BUILTIN_ALIASES` has exactly two platform columns — `claude` and
326
+ `opencode` (`src/integrations/agent/model-aliases.ts:45-76`). `resolveModel`
327
+ returns `entry?.platforms[platform] ?? model` — **verbatim pass-through** when
328
+ the column is missing (`model-aliases.ts:103`).
329
+
330
+ - **LLM endpoints.** `resolveLlmModel(selected, name, config.modelAliases)`
331
+ (`freeze.ts:189`) passes the *engine name* as the platform column and adds an
332
+ `"llm"` fallback column (`model-aliases.ts:107-109`). Neither the engine name
333
+ nor `"llm"` exists in `BUILTIN_ALIASES`. So `model: sonnet` under an LLM engine
334
+ resolves to the literal string `"sonnet"` — which almost every OpenAI-compatible
335
+ endpoint will reject.
336
+ - **`opencode-sdk`.** `freeze.ts:190` passes `engine.platform`, which for the SDK
337
+ engine is `"opencode-sdk"` — also not a `BUILTIN_ALIASES` column, also verbatim.
338
+
339
+ The claim is only true if the user has hand-authored a `modelAliases` table, which
340
+ is not what "portable by construction" means.
341
+
342
+ **Recommendation.** Either add `"*"`-style built-in columns / an `opencode-sdk`
343
+ alias to `BUILTIN_ALIASES`, or downgrade §3.5's claim to "aliases are portable
344
+ across configured platform columns; an unmatched alias passes through verbatim
345
+ and will typically fail at the endpoint." Given §3.4 makes a global
346
+ `defaults.model` the recommended shape, an unresolvable alias silently becoming a
347
+ literal model string is a bad default — consider making it a notice at freeze.
348
+
349
+ ---
350
+
351
+ ### M5 — The prose-append rule does not match how prompts are assembled
352
+
353
+ **Spec claim.** §3.3: "Investigated (round 6): the assembled prompt is a frozen
354
+ string produced at one seam, so appending is a deterministic concat — easy…
355
+ the step's section text is appended to the assembled prompt after a blank line,
356
+ byte-exact."
357
+
358
+ **Evidence.** There is no frozen assembled prompt. What is frozen is
359
+ `IrUnitNode.instructions` (byte-exact body prose). The *prompt* is built at
360
+ dispatch time, per unit, by `buildUnitPrompt`:
361
+
362
+ ```
363
+ src/workflows/exec/step-work.ts:500
364
+ return `${preamble}\n${instructions}${itemBlock}${inputsBlock}${gateBlock}${schemaDirective}`;
365
+ ```
366
+
367
+ Appending the step section to "the assembled prompt" places the call-site context
368
+ *after* the fan-out item block (`step-work.ts:473-475`), the declared-inputs
369
+ block, the gate feedback block, and the schema directive. "In this workflow,
370
+ focus on X" lands after "here is your item" and after "your result must match
371
+ this JSON Schema" — the worst position in the prompt.
372
+
373
+ Appending to `instructions` instead is the right answer, but that is a
374
+ **freeze-time / compose-time** concat, not a dispatch-seam one, and it changes
375
+ the hash preimage (`step-work.ts:381` hashes `template.instructions`) — see C3.
376
+
377
+ **Recommendation.** Restate the rule as "the referenced task's body and the
378
+ step's section are concatenated at freeze into the unit's `instructions`". Also
379
+ specify: does the step's `### gate` sub-heading participate? (The parser already
380
+ splits the gate rubric out of the section — `src/workflows/parser.ts:379-381` —
381
+ so it does not, but the spec's "section text" wording implies it might.) And
382
+ specify ordering for a `uses: commands/*` target: task body, then filled command
383
+ template, then step section? The spec says "appended to the assembled prompt"
384
+ for both cases without disambiguating.
385
+
386
+ ---
387
+
388
+ ### M6 — `env:` literals break the "names only, never values" plan invariant
389
+
390
+ **Spec claim.** §3.7 allows literal mappings inline (`LOG_LEVEL: debug`) and
391
+ `DATABASE_URL: secrets/db-url`, and says this "extends the existing schema
392
+ additively."
393
+
394
+ **Evidence.** The current invariant is explicit and deliberate:
395
+
396
+ ```
397
+ src/workflows/exec/step-work.ts:354-358
398
+ // `env` carries NAMES ONLY, never resolved values: hashing a resolved secret
399
+ // would leak it into a durable hash oracle and would spuriously re-dispatch
400
+ // on every secret rotation.
401
+ ```
402
+
403
+ and, from the driver-protocol contract:
404
+
405
+ ```
406
+ src/workflows/exec/param-secrets.ts:17-19
407
+ secrets belong in **env bindings** (`env:` refs), which `brief` surfaces by
408
+ NAME ONLY and never resolves.
409
+ ```
410
+
411
+ Under §3.7, literal env values are frozen into the plan (`plan_json` in the run
412
+ row), hashed into the unit input hash, and surfaced verbatim by `brief`
413
+ (`brief.ts:577` passes `unit.env` straight through). A user who writes
414
+ `env: { API_KEY: sk-… }` — a natural GHA-muscle-memory mistake the spec
415
+ *encourages* with the bare-mapping shorthand — has just written a credential into
416
+ a durable journal and a driver-visible brief.
417
+
418
+ **Recommendation.** Keep the shape but keep the invariant: freeze/hash/brief must
419
+ carry env **keys and ref values only**; literal values must either be (a) hashed
420
+ as `key → sha256(value)` or `key → <literal>` sentinel, or (b) rejected for
421
+ workflow units and permitted only on tasks. Add a lint that flags secret-shaped
422
+ literal env values — `detectSecretShapedParams` (`param-secrets.ts:87`) is a
423
+ ready-made heuristic. State explicitly which of `env/` group values,
424
+ `secrets/` values, and inline literals join `sensitiveValues`.
425
+
426
+ ---
427
+
428
+ ### M7 — `params` as a cascade value field collides with two existing meanings
429
+
430
+ **Spec claim.** §3.4 lists `params` in the flat value vocabulary, "legal at every
431
+ layer, merged per-field with the nearest layer winning."
432
+
433
+ **Three collisions:**
434
+
435
+ 1. **Within one workflow file.** Top-level frontmatter `params:` already means
436
+ *param schema declarations* (`schemas/akm-workflow.json:22-32`,
437
+ `program/schema.ts:99-100`). §3.4 makes `params` legal at the "document
438
+ defaults" layer — i.e. `defaults: { params: … }` — meaning *values*. Two
439
+ meanings of one word in one file, one nesting level apart.
440
+ 2. **Across targets.** On a `uses: workflows/*` task, `params:` are declared
441
+ run parameters coerced through frozen schemas
442
+ (`src/workflows/ir/params.ts:34-72`). On `uses: commands/*`, they are template
443
+ placeholder fills (M2). On `uses: tasks/*`, they are "the task's own inputs".
444
+ These have different validation, different coercion, and different failure
445
+ modes; per-field cascade merging across them is undefined.
446
+ 3. **Secrets.** Params are contractually **non-secret and un-redactable** because
447
+ they are in the prompt and the input hash (`param-secrets.ts:9-19`). Making
448
+ `params` inheritable from a global config layer means a global value silently
449
+ flows into every prompt of every workflow. That is a new, quiet exfiltration
450
+ path for anything a user puts in `defaults.params`.
451
+
452
+ **Recommendation.** Remove `params` from the cascade value vocabulary. It is a
453
+ call-site input, not a setting. If a default-params feature is wanted, scope it
454
+ to one layer with an unambiguous key name and re-run the secret analysis.
455
+
456
+ ---
457
+
458
+ ### M8 — Freeze must become IO-dependent; lint never freezes
459
+
460
+ **Spec claim.** §3.3: "Resolution happens at freeze. The referenced task compiles
461
+ into the frozen plan… the existing snapshot rule, unchanged." §10: "§3.3
462
+ composition resolves at the existing freeze step."
463
+
464
+ **Evidence.** Freeze is deliberately pure over `(asset, config)`:
465
+
466
+ ```
467
+ src/workflows/ir/freeze.ts:39-43
468
+ * The only source-to-runtime boundary. Source compilation remains pure; engine
469
+ * selection and every dispatch-significant setting are resolved here once.
470
+ export function compileResolveFreezeWorkflow(asset: WorkflowAsset, config: AkmConfig): FrozenWorkflow
471
+ ```
472
+
473
+ Resolving `uses: tasks/<id>` requires `resolveAssetPath` + a file read, which is
474
+ **async** (`src/sources/resolve.ts`, used async everywhere:
475
+ `src/tasks/runner.ts:159`, `src/tasks/validator.ts:54`). Making freeze async is
476
+ mechanically fine at the one call site (`src/workflows/runtime/runs.ts:241` is in
477
+ an async function) but it destroys the purity property the file's own doc
478
+ comment advertises, and it means freeze can now fail with `ASSET_NOT_FOUND` /
479
+ bundle-resolution errors it has never produced before.
480
+
481
+ Second problem: **workflow lint does not freeze.** `compileResolveFreezeWorkflow`
482
+ has exactly one caller (grep: `runs.ts:241`). So `uses: tasks/does-not-exist`,
483
+ `uses: tasks/x` where `x` targets a workflow (the §3.3 "no nesting through the
484
+ back door" error), and a composed task with an unresolvable engine all become
485
+ **run-start failures, not lint failures** — the opposite of what §6's lint row
486
+ promises.
487
+
488
+ Third: cross-bundle. Which bundle does `uses: tasks/lint-check` resolve against?
489
+ Tasks already carry bundle attribution through the scheduler (`--bundle` token,
490
+ `src/tasks/scheduler-invocation.ts:59-78`); a workflow composing a task has no
491
+ such context. Undefined in the spec.
492
+
493
+ **Recommendation.** State that freeze becomes async and IO-bearing; add a
494
+ compile-time composition-resolution pass that lint can also run (a resolver
495
+ callback injected into `compileWorkflowPlan`, keeping `compile` pure and moving
496
+ the IO to the caller is the cleaner shape). Define bundle scoping for `uses:`.
497
+
498
+ ---
499
+
500
+ ### M9 — Migration losslessness is overclaimed
501
+
502
+ **Spec claim.** §8: "No key survives with a changed meaning — the `.yml`
503
+ vocabulary maps onto different spellings, so nothing is silently reinterpreted."
504
+
505
+ **Gaps, all against `src/tasks/parser.ts` + `src/tasks/schema.ts`:**
506
+
507
+ | Lost / changed | Evidence |
508
+ |---|---|
509
+ | `name:` — a real, parsed, displayed field | `parser.ts:89`, `schema.ts:88-89` ("Human-readable display name shown in `akm show` and search results"). Not in the §8 table; not in the envelope (`schemas/akm-asset-envelope.json` has no `name` definition); §4's envelope list omits it. |
510
+ | `llm.supportsJsonSchema`, `llm.contextLength`, `llm.enableThinking` | Parsed and validated today (`parser.ts:331-357`), frozen into `IrInvocation.llm` for workflows. §3.4's value vocabulary is a *closed nine-field list* that omits all three; §8's row is `llm.maxTokens / llm.temperature / llm.extraParams / …` — the ellipsis is doing load-bearing work for three fields with no destination. |
511
+ | `prompt: agents/x` → `agent: agents/x` | This *is* a changed meaning. Today it reads the agent asset file and sends its bytes as the prompt (`runner.ts:581-593`). After: the agent becomes a persona node and the body is "seeded". §3.5 correctly frames this as a fix, but §8's blanket "nothing is silently reinterpreted" is then false for the one row that matters most. |
512
+ | `command:` as an **array** | `readCommand` accepts `string[]` (`parser.ts:286-299`), and `akm-task.json:19-24` declares the `oneOf`. §8 maps only `command: <shell>` → `run: <shell>`. An array command has no lossless single-line shell spelling (quoting/escaping is now the migrator's problem, per-platform). |
513
+ | `timeoutMs: null` | `readTimeout` treats `null` as "explicitly no timeout" (`parser.ts:318-323`). Does `timeout: none` (the workflow spelling, `schemas/akm-workflow.json:59-70`) survive the conversion? Unstated. |
514
+
515
+ **Recommendation.** Complete the §8 table (all keys, including `name` and the
516
+ three dropped `llm` fields), decide where `name` goes (`description`? a new
517
+ envelope key? dropped with a migration warning?), and either extend the §3.4
518
+ vocabulary to the full `llm` set or state explicitly that those three fields are
519
+ being dropped and why.
520
+
521
+ ---
522
+
523
+ ### M10 — "Strays named by path" is not structural
524
+
525
+ **Spec claim.** §8: "The 0.7→0.8 lesson — leftover files must never be **silently
526
+ invisible** — is honored structurally: 0.9.0 commands already refuse an
527
+ un-migrated installation rather than migrating as a side effect, and
528
+ post-migration `task sync` / `task doctor` name any stray `.yml` file by path
529
+ instead of skipping it."
530
+
531
+ **Evidence, both halves:**
532
+
533
+ - **"Already refuse."** The existing refusals are keyed on *config shape*
534
+ (`config-schema.ts:170,207`; `config-walker.ts:446-447`) and *DB schema
535
+ version* (`src/storage/engines/sqlite-migrations.ts:242`). Neither observes a
536
+ leftover `tasks/foo.yml`. After a successful cutover both gates are satisfied
537
+ and the stray file trips nothing.
538
+ - **"Name any stray by path."** Nothing does this today, and the structure works
539
+ against it. `sync` enumerates `*.yml` only
540
+ (`src/commands/tasks/tasks.ts:414`, `.filter((f) => f.endsWith(".yml"))`,
541
+ and `:433` `path.join(typeRoot, \`${id}.yml\`)`). Flip that to `.md` and the
542
+ stray is invisible; leave it and the stray is **installed into the OS
543
+ scheduler**. The placement table (`src/core/asset/asset-placement.ts:159-169`,
544
+ `isRelevantFile: ext === ".yml"`) has the same property in the indexer.
545
+ - Note the existing precedent runs the *other* direction: `akm task add` already
546
+ guards against a leftover pre-0.8 `<id>.md` shadowing a new `<id>.yml`
547
+ (`tasks.ts:155-164`). That guard will need inverting, and it demonstrates the
548
+ hazard is real and recurring.
549
+ - Worst case during the window: `<id>.yml` and `<id>.md` both exist. Both are
550
+ recognized (by the `akm-task` and a new markdown-task adapter respectively),
551
+ both produce conceptId `tasks/<id>`, and the index gains a duplicate identity.
552
+
553
+ **Recommendation.** Replace "structurally" with a concrete mechanism: a named
554
+ migration step that fails loudly on any surviving `tasks/*.yml`, plus an explicit
555
+ raw-directory scan in `sync` and `doctor` that reports `.yml` files by path with
556
+ a non-zero exit. Add a `tasks/<id>.{yml,md}` collision check to the adapter or
557
+ indexer.
558
+
559
+ ---
560
+
561
+ ## Minor
562
+
563
+ ### m1 — "Legal at every layer" is not true of the engine node
564
+
565
+ §3.4: "Everything else is a flat set of value fields, legal at every layer."
566
+ Then the exception carves out only *strictness*. But four of the nine fields —
567
+ `env`, `cwd`, `shell`, `params` — have no declaration on either engine schema
568
+ (`src/core/config/schema/engines.ts:66-88, 90-131`). Both schemas are
569
+ `.passthrough()` and neither superRefine reject-list mentions them
570
+ (`engines.ts:84, 106-117`), so writing `env:` on an engine node **validates
571
+ silently and is ignored** — the exact "silently load-bearing" failure §3.4 says
572
+ it is avoiding. Recommend: add the four fields to both engine schemas, or add
573
+ them to both reject lists, and say which in the spec.
574
+
575
+ ### m2 — Value fields on a `uses: workflows/*` task are structurally inert
576
+
577
+ Today the parser forbids them: `rejectTargetFields(data, ["params"])` for
578
+ workflow targets (`src/tasks/parser.ts:113`), mirrored in
579
+ `schemas/akm-task.json:31-34`. Under the cascade they become legal at the task
580
+ layer — but a workflow run re-freezes every unit from its *own* layers
581
+ (`freeze.ts:50`: `[documentDefaults, unit]`), and `RunWorkflowOptions`
582
+ (`src/workflows/exec/run-workflow.ts:98-148`) has no env/model/timeout channel.
583
+ So `model: haiku` on a workflow-target task does nothing. The spec never says
584
+ whether this is a notice, an error, or plumbed through. Same for §3.7's "For a
585
+ `uses: workflows/*` target, `env:` merges into the run's process environment" —
586
+ that needs a new `RunWorkflowOptions` field and a decision about whether it is
587
+ frozen (a resumed run would otherwise lose it).
588
+
589
+ ### m3 — Frontmatter `agent:`/`model:` on a task collide with type classification
590
+
591
+ `matchers.ts:234` maps `agent` in frontmatter → `type: command` (specificity 18);
592
+ `matchers.ts:243-245` maps `model` in frontmatter → `type: agent` (specificity 8).
593
+ A markdown task with `agent: agents/reviewer` and `model: opus` in frontmatter
594
+ carries two mis-classification signals. Directory residence should win, but the
595
+ spec's §6 adapter row says recognition is "`type: task` / `tasks/` residence" —
596
+ both need to be *stronger* than these heuristics, and the interaction is
597
+ untested. Add a specificity note and a golden.
598
+
599
+ ### m4 — `status: draft` as an execution gate overloads OKF lifecycle
600
+
601
+ §4: "`status: draft` as author-now, arm-later (draft tasks are never installed by
602
+ `sync`)." `status` is the OKF v0.2 lifecycle family (okf-support.md:88-93,
603
+ `schemas/akm-asset-envelope.json`), shared by every asset type. Making it a
604
+ scheduler gate for one type means the same value has execution semantics on
605
+ tasks and none elsewhere, and it duplicates `enabled:` — two off switches with
606
+ different names, different origins, and no stated precedence. Recommend: drop it,
607
+ or specify the precedence table (`status: draft` + `enabled: true` = ?).
608
+
609
+ ### m5 — Setup wizard will destroy markdown task bodies
610
+
611
+ `prepareSetupTaskDefinitions` does a full YAML round-trip on the template —
612
+ `yamlParse(plan.task.yaml)` → mutate `schedule`/`enabled` → `yamlStringify`
613
+ (`src/setup/steps/tasks.ts:161-169`) — and the existing-file path uses a
614
+ line-based `setEnabledInYaml`. Both destroy a markdown body. `EmbeddedTask` also
615
+ hard-filters `.yml` and `target.kind === "command"` and exposes
616
+ `command: string` (`src/tasks/embedded.ts:81,96,42-45`). §8's "the ten embedded
617
+ templates convert in this change" covers the ten files but not these three code
618
+ seams. (There are ten templates: five in `src/assets/tasks/core/`, five in
619
+ `src/assets/tasks/improve/` — that count checks out.)
620
+
621
+ ### m6 — The parser's must-have-a-section rule contradicts optional sections
622
+
623
+ `src/workflows/parser.ts:282-296` hard-errors on a missing or empty section for
624
+ any non-route step, per workflow-format-unification.md:220-224 body rule 2.
625
+ §3.3/§3.6 require that a `run:` or `uses:` step need no section. The sibling spec
626
+ must be amended in the same change, or the two specs are in direct conflict.
627
+
628
+ ### m7 — `run:` shell semantics change more than the spec says
629
+
630
+ Today `command:` tasks spawn argv with no shell (`runManagedSubprocess(spawnCmd,
631
+ …)`, `src/tasks/runner.ts:277-289`), which is why `resolveNestedAkmCommand`
632
+ (`runner.ts:344-348`) can rewrite a bare `akm` token to the resolved
633
+ invocation — an argv-only optimization that a shell string breaks. Moving to
634
+ `sh -c` also (a) introduces shell metacharacter interpretation over strings that
635
+ today are safe, which matters once `env:` values can come from `secrets/`, and
636
+ (b) makes the `AKM_ITEM` JSON injection of §3.6 a quoting hazard inside the shell
637
+ text. Also: `cwd` defaults to `process.env.HOME ?? os.tmpdir()` today
638
+ (`runner.ts:279`) — what is the default under the new `cwd:` field? Unstated.
639
+
640
+ ### m8 — Inherited wart: `prompt: agents/x` ships raw frontmatter to the model
641
+
642
+ `resolvePromptText` reads the asset file whole — `fs.readFileSync(assetPath,
643
+ "utf8")` (`src/tasks/runner.ts:592-593`) — so the agent asset's YAML frontmatter
644
+ delimiters and keys are sent to the model as prompt text. The spec's §3.5
645
+ correctly identifies that this path drops the model preference; it does not
646
+ mention that it also sends frontmatter. Worth naming, since §8's migration
647
+ "seeds the body from the agent asset's prompt" and should seed the *body*, not
648
+ the file.
649
+
650
+ ### m9 — okf-support.md consistency is not as clean as claimed
651
+
652
+ The proposal lists okf-support.md as a "Related" consistency anchor. Two frictions:
653
+
654
+ - okf-support.md:20-22 lists "**YAML tasks**" among things OKF is explicitly
655
+ *not* "a serialization imposed on". Converting tasks to OKF-enveloped markdown
656
+ doesn't violate the letter (nothing is imposed), but the sentence goes stale and
657
+ should be updated in the same change.
658
+ - okf-support.md:131-134: "`workflow` is the only type whose frontmatter is parsed
659
+ against a closed allowlist, and that allowlist admits these keys". A markdown
660
+ task with a closed frontmatter schema becomes the second such type, so
661
+ `promoteProposal`'s OKF v0.2 stamping needs the task allowlist to admit
662
+ `generated`/`verified`/`provenance`/`status`/`stale_after` — or stamping falls
663
+ back to "promote unstamped, warn". Not mentioned in §4 or §6.
664
+
665
+ ### m10 — Golden/test churn is understated
666
+
667
+ §3.6 prices the flatten as "schema and golden churn on a pre-release format —
668
+ approved." The actual surface: the `akm-task` conformance oracle is marked
669
+ **"authored, DO NOT modify"** and has four goldens
670
+ (`tests/fixtures/format-family-goldens/akm-task/{recognition,placement,lint,renderer}.json`)
671
+ plus a fixture bundle (`tests/fixtures/bundles/akm-task/`); `looksLikeRoot`
672
+ (`akm-task-adapter.ts:136-155`) is an install-time probe keyed on top-level
673
+ `.yml`; there are ~20 task test files, two CLI goldens
674
+ (`tests/fixtures/goldens/cli/c-tasks-*.json`), a schema-drift test pinning
675
+ `akm-workflow.json` enums against `program/schema.ts` constants
676
+ (`program/schema.ts:22-24`), a `tests/integration/tasks-schema.test.ts` that is
677
+ the sole runtime consumer of `akm-task.json`, and an
678
+ `all-types` stash fixture (`tests/fixtures/stashes/all-types/tasks/*.yml`). Add a
679
+ work-item line for the oracle rewrite — "DO NOT modify" implies an explicit
680
+ owner decision to re-author it.
681
+
682
+ ---
683
+
684
+ ## Claims verified as accurate (for the record)
685
+
686
+ Not everything is wrong; these check out precisely and should not be re-litigated:
687
+
688
+ - §2 "`run` is the canonical orchestrator; `start`/`next`/`complete` removed" —
689
+ `src/commands/workflow-cli.ts:522` subcommands are status/list/create/run/brief/
690
+ report/abandon/resume.
691
+ - §2 "Freeze already implements a configuration cascade … `layers:
692
+ EngineUseConfig[] = [documentDefaults, unit]`, selects engine by nearest layer,
693
+ resolves model/timeout/request overrides nearest-wins / deep-merged" —
694
+ `freeze.ts:50, 151-155, 163-191, 193-209, 211-216`. Exactly right, and it is
695
+ the strongest argument in the document.
696
+ - §2 `UnitDispatchRequest` carries prompt / frozen engine snapshot / timeoutMs /
697
+ env / cwd / sensitiveValues, results carry `failureReason` —
698
+ `unit-dispatch.ts:9-43`. (Nit: `failureReason` is typed `string`, not the
699
+ `AgentFailureReason` union.)
700
+ - §3.4 "`temperature` on an agent-kind engine node remains a config validation
701
+ error … `engines.ts` already enforces this, while `model` remains legal on both
702
+ kinds" — `engines.ts:105-120` (reject list) and `:71` / `:99`.
703
+ - §3.5's description of `resolveModel`'s four-tier chain, `"*"` fallback column,
704
+ `resolveLlmModel`'s `llm` column, one resolution level, no recursion —
705
+ `model-aliases.ts:11-16, 89-109`. Accurate.
706
+ - §3.5 "today the `.yml` task path drops an agent asset's model preference" —
707
+ `runner.ts:581-593` resolves the ref to text only; no model is read.
708
+ - §6 "`akm-task.json` standalone (87 lines, loaded by nothing at runtime)" — 87
709
+ lines, one consumer and it is a test (`tests/integration/tasks-schema.test.ts:8`).
710
+ - §6 "`parseTaskDocument()` + `TASK_KEYS` + `rejectTargetFields()` +
711
+ `resolvePromptSource()` (368 lines)" — `src/tasks/parser.ts` is 368 lines.
712
+ - §6 "`runCommandTask` runs raw argv only" — `runner.ts:277-289`.
713
+ - §8 "task ids and the `akm task run <id>` ABI do not change, so nothing is
714
+ reinstalled" — `buildScheduledTaskInvocation` /`parseScheduledTaskArgv`
715
+ (`scheduler-invocation.ts:59-78, 198-227`) are extension-agnostic; ids derive
716
+ from the basename. Correct, provided the id grammar is unchanged
717
+ (`src/tasks/task-id.ts`).
718
+ - §8 "0.9.0 already has an explicit, journaled, crash-resumable migration … Task
719
+ conversion joins it as one more content step" — `scripts/akm-migrate/migrate/
720
+ legacy/content-migration.ts:5-45` is exactly that, and its "PRE-RELEASE
721
+ EXTENSION NOTE" explicitly blesses growing the fold set in-branch.
722
+
723
+ ---
724
+
725
+ ## Cross-cutting: what will actually be hard
726
+
727
+ Ranked by hidden cost, not by spec word count:
728
+
729
+ 1. **A second, non-agent dispatch path through the journal** (C4). Everything
730
+ downstream of `runUnit` assumes an engine, a prompt, and token usage. Retry,
731
+ worktree isolation, budget accounting, `brief`/`report`, and `failure_reason`
732
+ mapping all need shell-shaped answers. This is the single largest unscoped item.
733
+ 2. **IR + hash version bump with a defined preimage** (C3). Mechanically small,
734
+ semantically dangerous — a missed field is a silent replay-reuse bug.
735
+ 3. **Composition at freeze** (M8): async freeze, bundle scoping, lint-time
736
+ validation, and provenance of the composed prose in the report surface.
737
+ 4. **The cascade's config half** (M3): new `DefaultsSchema` fields, new readers in
738
+ three freeze helpers, `akm config set` support, and un-retiring `defaults.llm`
739
+ in flat form one release after killing it.
740
+ 5. **The task-runner / orchestrator split** (§6's "honest boundary") is the right
741
+ call but leaves the observable-semantics divergence unaddressed: the *same*
742
+ task, run by cron, resolves config at dispatch (`runner.ts:476-514`); composed
743
+ into a workflow, it resolves at freeze. A user who edits `config.defaults.model`
744
+ mid-flight sees one surface change and the other not. §3.5 notes the timing
745
+ difference in one clause; it deserves a paragraph and a documented rule.
746
+
747
+ ## Recommended pre-implementation actions
748
+
749
+ 1. Correct or strike C2, M1, M5 — three design decisions rest on inaccurate
750
+ readings of the code.
751
+ 2. Rewrite §10 to enumerate the real IR/freeze/hash changes (C3).
752
+ 3. Add a §3.8 "shell units in the orchestrator" covering C4 end to end.
753
+ 4. Add a security paragraph for `uses: scripts/*` (C1) and update
754
+ `metadata.ts:140` in the same commit.
755
+ 5. Remove `params` from the cascade vocabulary (M7); decide the command-template
756
+ placeholder syntax explicitly (M2).
757
+ 6. Complete the §8 migration table and replace "honored structurally" with a
758
+ named mechanism (M9, M10).