akm-cli 0.9.30 → 0.9.32

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 (165) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +10 -10
  3. package/ROADMAP.md +112 -0
  4. package/dist/commands/feedback-cli.js +21 -13
  5. package/dist/commands/improve/improve-cli.js +33 -12
  6. package/dist/core/errors.js +1 -0
  7. package/dist/core/file-lock.js +10 -1
  8. package/dist/indexer/usage/usage-events.js +6 -2
  9. package/dist/llm/client.js +33 -3
  10. package/dist/scripts/akm-migrate-node.js +6 -1
  11. package/dist/scripts/akm-migrate.js +6 -1
  12. package/docs/README.md +42 -41
  13. package/docs/agents/README.md +15 -0
  14. package/docs/agents/agent-install.md +14 -0
  15. package/docs/architecture/README.md +67 -0
  16. package/docs/architecture/adapters.md +272 -0
  17. package/docs/architecture/akm-architecture-decision-history.md +2055 -0
  18. package/docs/architecture/akm-core-principles.md +126 -0
  19. package/docs/architecture/architecture.md +579 -0
  20. package/docs/architecture/comparisons/claude-code-vs-akm-workflows-full.md +550 -0
  21. package/docs/architecture/decisions/0001-no-interpolation-attached-structured-context.md +72 -0
  22. package/docs/architecture/decisions/0002-unit-reuse-and-input-hash-scope.md +142 -0
  23. package/docs/architecture/decisions/0003-child-env-allowlist-and-provenance.md +189 -0
  24. package/docs/architecture/decisions/0004-task-input-contract-and-flag-coercion.md +97 -0
  25. package/docs/architecture/decisions/0005-task-result-vocabulary-and-legacy-read-mapping.md +108 -0
  26. package/docs/architecture/decisions/0006-task-source-version-routing.md +124 -0
  27. package/docs/architecture/decisions/0007-workflow-composition-bounds.md +83 -0
  28. package/docs/architecture/decisions/0008-task-binding-normalization.md +88 -0
  29. package/docs/architecture/decisions/0009-child-run-publication-column-parity.md +80 -0
  30. package/docs/architecture/decisions/0010-driver-protocol-cut.md +36 -0
  31. package/docs/architecture/decisions/0011-engine-run-loop-invariants.md +125 -0
  32. package/docs/architecture/decisions/README.md +62 -0
  33. package/docs/architecture/improvement.md +440 -0
  34. package/docs/architecture/internals/classification.md +154 -0
  35. package/docs/architecture/internals/fresh-host-rebuild-runbook.md +40 -0
  36. package/docs/architecture/internals/functional-contract-patterns.md +340 -0
  37. package/docs/architecture/internals/health-advisories.md +110 -0
  38. package/docs/architecture/internals/improve-workflow.md +713 -0
  39. package/docs/architecture/internals/indexing.md +498 -0
  40. package/docs/architecture/internals/registry-network-boundary.md +57 -0
  41. package/docs/architecture/internals/search.md +249 -0
  42. package/docs/architecture/internals/storage-locations.md +943 -0
  43. package/docs/architecture/persisted-data-compat.md +59 -0
  44. package/docs/architecture/reviews/env-secret-access.md +319 -0
  45. package/docs/architecture/runtime-boundary-design.md +153 -0
  46. package/docs/architecture/specs/0.9.0-close-out-plan.md +361 -0
  47. package/docs/architecture/specs/0.9.0-decisions.md +528 -0
  48. package/docs/architecture/specs/0.9.0-docs-code-drift-register.md +745 -0
  49. package/docs/architecture/specs/0.9.0-open-items-register.md +135 -0
  50. package/docs/architecture/specs/0.9.0-public-api-issue-backlog.md +740 -0
  51. package/docs/architecture/specs/0.9.0-release-surface-review.md +636 -0
  52. package/docs/architecture/specs/agent-command-engine-model-design.md +513 -0
  53. package/docs/architecture/specs/akm-0.9.0-bundle-adapter-spec.md +501 -0
  54. package/docs/architecture/specs/akm-0.9.0-ref-grammar-decision.md +291 -0
  55. package/docs/architecture/specs/akm-format-neutral-bundle-workspace-spec.md +2059 -0
  56. package/docs/architecture/specs/di-seams-plan.md +539 -0
  57. package/docs/architecture/specs/driver-protocol-keep-or-cut.md +275 -0
  58. package/docs/architecture/specs/fact-asset-type.md +142 -0
  59. package/docs/architecture/specs/improve-collapse-churn-detector-design.md +634 -0
  60. package/docs/architecture/specs/okf-support.md +148 -0
  61. package/docs/architecture/specs/ref.md +329 -0
  62. package/docs/architecture/specs/reviews/task-workflow-unification/README.md +72 -0
  63. package/docs/architecture/specs/reviews/task-workflow-unification/findings-consolidated.md +152 -0
  64. package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-alternatives.md +352 -0
  65. package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-bunqueue.md +300 -0
  66. package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-openworkflow.md +402 -0
  67. package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-1.md +758 -0
  68. package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-2.md +645 -0
  69. package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-3.md +602 -0
  70. package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-adversarial.md +27 -0
  71. package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-architecture.md +27 -0
  72. package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-coverage.md +74 -0
  73. package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-security.md +15 -0
  74. package/docs/architecture/specs/reviews/task-workflow-unification/panel-r2-adversarial.md +132 -0
  75. package/docs/architecture/specs/reviews/task-workflow-unification/panel-r2-verification.md +147 -0
  76. package/docs/architecture/specs/stash-conventions-code-spec.md +275 -0
  77. package/docs/architecture/specs/stash-organization-conventions.md +377 -0
  78. package/docs/architecture/specs/task-workflow-format-unification.md +721 -0
  79. package/docs/architecture/specs/workflow-engine-buy-vs-build.md +231 -0
  80. package/docs/architecture/specs/workflow-format-unification.md +437 -0
  81. package/docs/architecture/testing/manual-testing-checklist.md +3420 -0
  82. package/docs/architecture/testing/okf-v0.2-conformance-runbook.md +924 -0
  83. package/docs/architecture/testing/task-workflow-manual-tests.md +1817 -0
  84. package/docs/architecture/testing/testing-workflow.md +446 -0
  85. package/docs/architecture/workflow-engine.md +274 -0
  86. package/docs/guides/README.md +33 -0
  87. package/docs/guides/author-bundles.md +310 -0
  88. package/docs/guides/author-workflows.md +520 -0
  89. package/docs/guides/bundles.md +321 -0
  90. package/docs/guides/capture-knowledge.md +136 -0
  91. package/docs/guides/claude-code-vs-akm-workflows.md +30 -0
  92. package/docs/guides/concepts.md +93 -0
  93. package/docs/guides/discover-and-load.md +158 -0
  94. package/docs/guides/getting-started.md +240 -0
  95. package/docs/guides/improve-the-library.md +246 -0
  96. package/docs/guides/knowledge-management.md +18 -0
  97. package/docs/guides/recipes/headless-install.md +97 -0
  98. package/docs/guides/recipes/website-source.md +28 -0
  99. package/docs/guides/run-workflows.md +279 -0
  100. package/docs/guides/scheduling.md +123 -0
  101. package/docs/guides/use-with-any-agent.md +129 -0
  102. package/docs/guides/wikis.md +81 -0
  103. package/docs/maintainers/README.md +19 -0
  104. package/docs/maintainers/curate-workmap.md +336 -0
  105. package/docs/maintainers/eval.md +3 -0
  106. package/docs/maintainers/local-development.md +108 -0
  107. package/docs/maintainers/release-checklist.md +103 -0
  108. package/docs/migration/README.md +2 -2
  109. package/docs/migration/release-notes/0.0.13.md +1 -1
  110. package/docs/migration/release-notes/0.1.0.md +1 -1
  111. package/docs/migration/release-notes/0.2.0.md +1 -1
  112. package/docs/migration/release-notes/0.3.0.md +1 -1
  113. package/docs/migration/release-notes/0.5.0.md +1 -1
  114. package/docs/migration/release-notes/0.6.0.md +1 -1
  115. package/docs/migration/release-notes/0.7.0.md +1 -1
  116. package/docs/migration/release-notes/0.7.3.md +2 -2
  117. package/docs/migration/release-notes/0.7.4.md +1 -1
  118. package/docs/migration/release-notes/0.7.5.md +1 -1
  119. package/docs/migration/release-notes/0.8.0.md +1 -1
  120. package/docs/migration/release-notes/0.9.0.md +1 -1
  121. package/docs/migration/release-notes/0.9.14.md +1 -1
  122. package/docs/migration/release-notes/0.9.15.md +1 -1
  123. package/docs/migration/release-notes/0.9.16.md +1 -1
  124. package/docs/migration/release-notes/0.9.17.md +1 -1
  125. package/docs/migration/release-notes/0.9.19.md +1 -1
  126. package/docs/migration/release-notes/0.9.2.md +1 -1
  127. package/docs/migration/release-notes/README.md +27 -31
  128. package/docs/migration/v0.5-to-v0.6.md +759 -0
  129. package/docs/migration/v0.8-to-v0.9.md +1 -1
  130. package/docs/plans/0.9.2-agent-command-workflow-plan.md +423 -0
  131. package/docs/posts/README.md +30 -0
  132. package/docs/posts/akm-command-tour.md +495 -0
  133. package/docs/posts/akm-logo-sized.webp +0 -0
  134. package/docs/posts/belief-aware-memory-12.md +139 -0
  135. package/docs/posts/config-v2-lessons-16.md +191 -0
  136. package/docs/posts/improve-loop-autonomous-10.md +225 -0
  137. package/docs/posts/improve-pipeline-debugging-0803.md +179 -0
  138. package/docs/posts/intro-part-01.md +76 -0
  139. package/docs/posts/intro-part-02.md +221 -0
  140. package/docs/posts/intro-part-03.md +190 -0
  141. package/docs/posts/intro-part-04.md +221 -0
  142. package/docs/posts/llm-mode-reflect-13.md +166 -0
  143. package/docs/posts/openpalm-built-on-akm.md +86 -0
  144. package/docs/posts/progressive-disclosure-06.md +129 -0
  145. package/docs/posts/proposal-safety-net-15.md +112 -0
  146. package/docs/posts/release-0.5.0.md +247 -0
  147. package/docs/posts/release-0.6.0.md +117 -0
  148. package/docs/posts/release-0.7.0.md +159 -0
  149. package/docs/posts/release-0.8.0.md +332 -0
  150. package/docs/posts/scattered-skills-05.md +100 -0
  151. package/docs/posts/task-assets-persistent-workflows-11.md +222 -0
  152. package/docs/posts/team-skills-07.md +198 -0
  153. package/docs/posts/wiki-agent-knowledge-08.md +223 -0
  154. package/docs/posts/workflows-vaults-09.md +321 -0
  155. package/docs/product-surface.md +54 -0
  156. package/docs/reference/README.md +1 -2
  157. package/docs/reference/asset-types.md +168 -0
  158. package/docs/reference/cli.md +5 -6
  159. package/docs/reference/data-and-telemetry.md +6 -0
  160. package/docs/reference/env-and-secrets.md +126 -0
  161. package/docs/reference/memory.md +124 -0
  162. package/docs/reference/refs.md +156 -0
  163. package/docs/reference/registry.md +489 -0
  164. package/docs/reference/website-sources.md +141 -0
  165. package/package.json +12 -15
@@ -0,0 +1,550 @@
1
+ Full technical comparison. For a short vendor-neutral decision guide, see docs/guides/claude-code-vs-akm-workflows.md.
2
+
3
+ # Workflow architecture: Claude Code workflows vs. akm workflows
4
+
5
+ A technical comparison of two things that share a name but occupy different
6
+ layers of the stack: the **Claude Code `Workflow` tool** (the harness-native
7
+ orchestration DSL) and **akm workflows** (`akm workflow …`, the workflow
8
+ subsystem in this repo).
9
+
10
+ The short version: they are not competitors. An akm workflow is a durable
11
+ workflow asset authored in either of two peer source formats, Markdown `.md`
12
+ or GitHub-shaped YAML `.yml`, and executed by the Stable
13
+ `akm workflow run` command. Both adapters compile through source IR version 1.
14
+ Every new start freezes the durable plan v4 family's executable `irVersion: 5`
15
+ before execution. The native engine
16
+ fans work out to concurrent runner units, retries, gates on typed artifacts,
17
+ and enforces budget ceilings. Execution is native: akm dispatches each unit to
18
+ a configured agent harness (ten are supported) rather than asking the calling
19
+ session to run it.
20
+
21
+ > **Scope.** This report analyzes akm's own workflow subsystem
22
+ > (`src/workflows/**`, `src/commands/workflow-cli.ts`) against the Claude Code
23
+ > `Workflow` tool. It deliberately **excludes** any `.workflow.mjs` Claude Code
24
+ > workflow scripts that may live elsewhere in this repo (e.g. under internal
25
+ > review tooling) — those are *consumers* of the Claude Code system, not
26
+ > instances of akm's own workflow engine.
27
+
28
+ ---
29
+
30
+ ## Part A — Claude Code workflows: technical details
31
+
32
+ ### A.1 Representation: an imperative JavaScript program
33
+
34
+ A Claude Code workflow **is a program**, not a document. The model authors a
35
+ self-contained JavaScript script, passed inline to the `Workflow` tool via its
36
+ `script` parameter. Every script begins with a **pure-literal** `meta` export:
37
+
38
+ ```js
39
+ export const meta = {
40
+ name: 'review-changes',
41
+ description: 'Review changed files across dimensions, verify each finding',
42
+ phases: [{ title: 'Review' }, { title: 'Verify' }],
43
+ }
44
+ // body: uses agent()/parallel()/pipeline()/phase()/log()
45
+ ```
46
+
47
+ `meta` must be a static literal (no variables, calls, spreads, or template
48
+ interpolation) so the harness can read the workflow's shape — its name,
49
+ description, and declared phases — **without executing the body**. The body
50
+ is arbitrary control flow: loops, conditionals, fan-out, accumulation.
51
+
52
+ The scripting surface is a small set of injected hooks and globals:
53
+
54
+ | Hook / global | Role |
55
+ |---|---|
56
+ | `agent(prompt, opts?)` | Spawn a subagent (a fresh LLM context). Returns its final text, or — with `opts.schema` — a validated structured object (the subagent is forced to call a `StructuredOutput` tool and the result is schema-checked with retries). Returns `null` if the agent is skipped mid-run or dies after retries are exhausted. |
57
+ | `parallel(thunks)` | Run tasks concurrently with a **barrier** — awaits all before returning. Failed thunks resolve to `null` (the call itself never rejects — filter with `.filter(Boolean)`). |
58
+ | `pipeline(items, ...stages)` | Run each item through all stages independently with **no barrier** between stages — item A can be in stage 3 while item B is still in stage 1. Each stage callback receives `(prevResult, originalItem, index)`. |
59
+ | `phase(title)` | Open a progress group; subsequent `agent()` calls are grouped under it. |
60
+ | `log(message)` | Emit a narrator line to the user. |
61
+ | `workflow(nameOrRef, args?)` | Run another workflow inline as a sub-step (one level of nesting only; the nested run shares the parent's concurrency cap, agent counter, and token budget). |
62
+ | `args` | The caller-supplied input value, verbatim. |
63
+ | `budget` | The turn's token target (`total`, `spent()`, `remaining()`) — a hard ceiling once reached. |
64
+
65
+ The unit of work is `agent()` — **a subagent with its own LLM context, model,
66
+ effort, and tool set**. Options include `label`, `phase`, `schema`, `model`,
67
+ `effort`, `isolation: 'worktree'` (an isolated git worktree so parallel file
68
+ mutations don't collide — expensive, so reserved for agents that mutate files
69
+ in parallel and would otherwise conflict; auto-removed if left unchanged), and
70
+ `agentType` (a named custom subagent). A subagent's model/effort/tools default
71
+ to the session's resolved model when unset, and subagents can reach
72
+ session-connected MCP tools via on-demand `ToolSearch`. Subagents are told
73
+ their final text *is* the return value, so a well-written `agent()` prompt
74
+ asks for raw data, not human-facing prose.
75
+
76
+ ### A.2 Execution engine: the harness runs the script
77
+
78
+ The defining fact: **the Claude Code harness itself executes the script**, in a
79
+ controlled JavaScript interpreter. The model *writes* the orchestration; the
80
+ harness *runs* it deterministically, intercepting each `agent()` call to spawn a
81
+ real subagent, meter tokens, and update progress.
82
+
83
+ The script runs in a **restricted sandbox**, not full Node:
84
+
85
+ - Standard JS built-ins are available (`JSON`, `Math`, `Array`, …).
86
+ - **No filesystem or Node API access** — a workflow script cannot read or write
87
+ files directly; only its subagents (which have tools) touch the world.
88
+ - **`Date.now()`, `Math.random()`, and argless `new Date()` throw** — they would
89
+ make replay non-deterministic and break resume (see A.5). Timestamps come in
90
+ via `args`; randomness is faked by varying prompts/labels by index.
91
+ - It is **TypeScript-hostile**: plain JS only, no type annotations/generics.
92
+
93
+ Invocation is **asynchronous / background by default**: the tool returns
94
+ immediately with a `runId` (`wf_…`), and a `<task-notification>` fires when the
95
+ workflow completes. `/workflows` streams live progress in the meantime.
96
+
97
+ ### A.3 Concurrency model: parallel by construction
98
+
99
+ Concurrency is the entire point. Fan-out is first-class:
100
+
101
+ - Concurrent `agent()` calls are capped at `min(16, cores − 2)` per workflow;
102
+ excess calls queue and drain as slots free.
103
+ - A single `parallel()`/`pipeline()` call accepts up to **4096 items**; total
104
+ agents across a workflow's lifetime are capped at **1000** (a runaway
105
+ backstop).
106
+ - `pipeline()` is the default multi-stage primitive precisely because it has no
107
+ barrier: wall-clock equals the slowest single-item chain, not sum-of-slowest
108
+ per stage.
109
+
110
+ On top of these primitives the ecosystem layers *quality patterns* — adversarial
111
+ verify (N skeptics per finding), judge panels, loop-until-dry discovery,
112
+ multi-modal sweeps, completeness critics — all expressed as ordinary control
113
+ flow over `agent()`. A handful of shapes recur often enough to be canonical:
114
+ review-then-verify as a `pipeline()` (each dimension's findings verify as soon
115
+ as that dimension finishes, no cross-dimension barrier), loop-until-count or
116
+ loop-until-budget (keep spawning finders until a target count or
117
+ `budget.remaining()` falls below a threshold), and adversarial/perspective-
118
+ diverse verify (N skeptics per finding, kept only if a majority survive).
119
+ `parallel()`'s barrier is reserved for the cases that genuinely need every
120
+ prior result together (dedup/merge, early-exit on zero); everything else
121
+ defaults to `pipeline()`. The load-bearing point underneath all of this: the
122
+ orchestration, the model policy, and any safety envelope are all *code* —
123
+ versioned, reviewable, and re-runnable, not something reconstructed from
124
+ scratch in an agent's head each time.
125
+
126
+ ### A.4 Progress tracking
127
+
128
+ Progress is **push-based and live**:
129
+
130
+ - `meta.phases` declares named phase groups up front; `phase()` / `opts.phase`
131
+ assign agents to them. Titles are matched exactly.
132
+ - `/workflows` renders a live progress tree (phases → agents → status).
133
+ - `log()` emits narrator lines above the tree.
134
+ - Completion delivers a `<task-notification>` back into the session.
135
+
136
+ Under the hood each run has a **transcript directory** containing
137
+ `journal.jsonl` (each `agent()` call's actual return value) and `agent-<id>.jsonl`
138
+ files (per-subagent transcripts) — the durable record used for diagnosis and
139
+ resume.
140
+
141
+ ### A.5 State, resume, and budget
142
+
143
+ - **Resume** keys on `runId`. Relaunching with `{scriptPath, resumeFromRunId}`
144
+ replays the longest unchanged **prefix** of `agent()` calls from cache
145
+ (same `(prompt, opts)` → instant cached result); the first edited/new call
146
+ and everything after it runs live. Same script + same args → 100% cache hit.
147
+ This is why determinism (A.2) is enforced.
148
+ - **Iteration** is file-based: every invocation persists its script under the
149
+ session directory and returns the path; you edit that file and re-invoke with
150
+ `scriptPath`.
151
+ - **Budget** ties depth to the user's "+Nk" directive. `budget.total` is a hard
152
+ ceiling; `budget.spent()`/`remaining()` are shared across the main loop and
153
+ all workflows, enabling `while (budget.remaining() > 50_000) { … }` scaling.
154
+
155
+ ### A.6 Lifecycle & trust
156
+
157
+ A Claude Code workflow is **session-scoped and turn-shaped**: it is one
158
+ well-bounded fan-out inside the current agent session, gone when the session
159
+ ends (only the transcript persists). Trust is handled by the harness sandbox —
160
+ the script can't touch the filesystem; only its tool-bearing subagents can, under
161
+ the session's normal permission model.
162
+
163
+ ---
164
+
165
+ ## Part B — akm workflows: technical details
166
+
167
+ This part deliberately stays high-level: `docs/reference/workflows.md` is the
168
+ maintained, exhaustive reference for the peer workflow source formats,
169
+ reference grammar, gates, budgets, and the run lease. What follows is the
170
+ comparison-relevant subset — read the reference doc for anything this section
171
+ doesn't answer.
172
+
173
+ ### B.0 Peer source formats, one stable execution surface
174
+
175
+ Peer workflow sources are Markdown `.md` and GitHub-shaped YAML `.yml`, both
176
+ addressed as `workflows/<name>`. The Markdown form declares the orchestration
177
+ graph (`unit`/`map`/`route`, inputs, outputs, retries, isolation, and budgets)
178
+ in frontmatter, while `## <step-id>` body sections carry unit/map
179
+ instructions and optional `### gate` rubrics. The YAML form accepts the
180
+ documented local `name`/`on`/`jobs` subset. Both adapters lower into the same
181
+ strict source IR version 1.
182
+
183
+ `akm workflow run` is the stable, ungated start/resume/execute surface. The
184
+ public `start`/`next`/`complete` lifecycle is removed. `status`, `list`,
185
+ `create`, `resume`, and `abandon` remain stable support commands.
186
+
187
+ There is no external-driver protocol: the former experimental
188
+ `akm workflow brief|report` surface was removed, so `run` is the single
189
+ execution path.
190
+
191
+ ### B.1 Representation
192
+
193
+ The Markdown template (`src/assets/workflows/workflow-template.md`, validated
194
+ against `schemas/akm-workflow.json`) keeps machine orchestration in
195
+ frontmatter and human-authorable prose in the body. The peer `.yml` adapter
196
+ accepts the bounded GitHub-shaped subset documented in
197
+ `docs/reference/workflow-schema.md`. Bare whole-value references
198
+ (`params.<name>` and `steps.<id>.output.<path>`) wire `map.over`,
199
+ `route.input`, and `inputs`; prose is never interpolated. Runtime params, map
200
+ items, and declared input artifacts reach units as attached context.
201
+
202
+ ### B.2 Parsers & compiled models
203
+
204
+ The Markdown and YAML adapters both produce source IR v1 before target
205
+ resolution. For Markdown, `parseWorkflow` (`src/workflows/parser.ts`) parses
206
+ the frontmatter graph and binds body prose into a `WorkflowDocument`
207
+ (`src/workflows/schema.ts`). It accumulates `WorkflowError`s instead of
208
+ throwing, and every element carries a `SourceRef` line span. The IR compiler
209
+ (`src/workflows/ir/compile.ts`) lowers source IR into the durable plan graph
210
+ (`src/workflows/ir/schema.ts`) executed by the engine. Parsed documents
211
+ cached in `index.db` are regenerable; only run state (B.3, below) is not.
212
+
213
+ ### B.3 Persistence: durable SQLite run state
214
+
215
+ Run state lives in **`state.db`**
216
+ (`src/storage/repositories/workflow-runs-repository.ts`; the former
217
+ `workflow.db` was folded into `state.db` in the 0.9.0 three-database cutover),
218
+ whose rows are explicitly **non-regenerable**. Four tables carry current run
219
+ state:
220
+
221
+ - `workflow_runs` — `id`, `workflow_ref`, `workflow_title`, `status`
222
+ (`active|completed|blocked|failed`), `params_json`, `current_step_id`,
223
+ timestamps, `scope_key`, `agent_harness`, `agent_session_id`,
224
+ `checkin_armed_at`, plus the engine's own columns: `plan_json`/`plan_hash`
225
+ (the frozen compiled plan) and `engine_lease_holder`/
226
+ `engine_lease_until` (the run lease, see B.5).
227
+ - `workflow_run_steps` — `(run_id, step_id)` PK, `step_title`, `instructions`,
228
+ `completion_json`, `sequence_index`, `status`
229
+ (`pending|completed|blocked|failed|skipped`), `notes`, `evidence_json`,
230
+ `completed_at`, `summary`.
231
+ - `workflow_run_units` — one row per dispatched unit of
232
+ work under the native engine: `run_id`,
233
+ `unit_id`, `step_id`, `status` (`pending|running|completed|failed|skipped`),
234
+ `input_hash`, `result_json`, `tokens`, `failure_reason`, `worktree_path`,
235
+ `session_id`, `last_checkin_at`, `attempts`, `claim_holder`/
236
+ `claim_expires_at`. Every dispatched unit journals here;
237
+ only historical runs from the removed manual lifecycle can lack unit rows.
238
+ - `workflow_run_unit_attempts` — the append-only durable-v4 dispatch journal,
239
+ keyed by `(run_id, unit_id, attempt)`, with a unique `dispatch_id`. Explicit
240
+ retries append a new attempt; crash reclaim reuses the existing dispatch
241
+ identity.
242
+
243
+ Schema evolves through an additive, idempotent migration engine, recorded in
244
+ `schema_migrations`. The first three tables above are the current baseline
245
+ (`001-initial-schema` in `src/core/state/migrations.ts`), and
246
+ `022-workflow-unit-attempts` adds the append-only attempt journal. The pre-cutover
247
+ `workflow.db` history that produced it is preserved for reference in
248
+ `scripts/akm-migrate/migrate/legacy/workflow-migrations-bodies.ts` —
249
+ `004-workflow-run-units` (the table itself), `005`/`007` (unit session id /
250
+ check-in columns), `006-frozen-plan-and-lease` (the engine's frozen plan +
251
+ run lease), `008`/`009` (unit attempts / claim columns).
252
+
253
+ ### B.4 Execution model: one native surface, durable memory
254
+
255
+ `akm workflow run` is the one stable native execution surface. Peer source formats use the same source IR v1 before durable freezing: Markdown `.md` and YAML `.yml`.
256
+ A new start by ref compiles source
257
+ IR v1 and freezes the resulting durable-v4-family `irVersion: 5` plan on the
258
+ run row (`plan_json`/`plan_hash`); edits to the source file need a new run.
259
+ Pre-`irVersion`-5
260
+ stored plans are rejected rather than keeping a parallel runtime. The
261
+ command dispatches each step's units to the configured runner (`llm`, `agent`,
262
+ or `sdk`), with:
263
+
264
+ - **Real concurrency.** A `map` step's units run through a scheduler
265
+ (`src/workflows/exec/scheduler.ts`) capped by `min(map's concurrency,
266
+ workflow.maxConcurrency, the selected engine's concurrency, a CPU-derived
267
+ host safety limit)`. The CPU-derived default — `min(16, max(1, cores − 2))`
268
+ — is, deliberately, the same formula Claude Code's own `agent()` cap uses
269
+ (A.3).
270
+ - **Retries** (`retry: { max, on: [<failure-reason>…] }`), journaled per
271
+ attempt so no attempt's row is clobbered.
272
+ - **Worktree isolation** (`isolation: worktree`) — a fresh detached git
273
+ worktree per unit attempt for file-mutating agent/sdk units, so parallel
274
+ fan-out can't trample a shared working tree; a clean one is auto-removed,
275
+ a dirty one is retained and its path logged.
276
+ - **Typed step artifacts** — a step's declared `output` JSON Schema validates
277
+ the promoted artifact (a solo unit's result, a `collect` fan-out's array, or
278
+ a `vote` fan-out's majority winner) before the step can complete.
279
+ - **Gates that judge the artifact**, with `gate.max_loops` turning a rejection
280
+ into a bounded evaluator-optimizer loop (re-run with the judge's feedback
281
+ threaded into every unit prompt). Omitted or empty rubrics skip validation;
282
+ criteria-bearing gates freeze `workflow.judgeEngine`, and unavailable or
283
+ malformed verdicts fail closed.
284
+ - **Budget ceilings** (`budget.max_units`/`max_tokens`), enforced across
285
+ resumes because both counters are seeded from the unit journal.
286
+
287
+ Execution is always the engine's: `akm workflow run` dispatches each unit to
288
+ a configured harness (`src/workflows/exec/step-work.ts` holds the shared step
289
+ semantics) and a run lease (B.5) keeps a single invocation on the spine. akm
290
+ formerly shipped an experimental `brief`/`report` protocol that let a calling
291
+ agent session execute units itself; it was removed in favour of the single
292
+ native path, since native dispatch already covers ten harnesses — Claude Code
293
+ among them — for less code than the protocol cost.
294
+
295
+ ### B.5 Scoping & concurrency guards (two different guards)
296
+
297
+ Runs are partitioned by **`scope_key`** — a `sha256` of the nearest project
298
+ anchor (`.akm/config.json` root → git root → bundle dir → cwd),
299
+ `src/workflows/authoring/scope-key.ts`. Within a `(workflow_ref, scope_key)`
300
+ pair, `startWorkflowRun` enforces a **single active run** unless `--force` is
301
+ passed internally. The public CLI exposes no parallel-start flag: invoking
302
+ `workflow run` by ref continues the active run in that scope. This guard is
303
+ scope-local by design (two unrelated projects sharing one `state.db` must be
304
+ able to run the same-named workflow independently), so two *different*
305
+ scopes can each hold their own active run of the same ref — starting one
306
+ warns about an active run in another scope (naming its id and scope) rather
307
+ than blocking it, and `akm workflow list --all-scopes` / `status` / `resume`
308
+ / `abandon <run-id>` see and act across scopes (#942).
309
+
310
+ Orchestrated runs add a second, unrelated guard: the **run lease**. `akm
311
+ workflow run` takes a lease (a random holder id, 90s expiry, renewed between
312
+ steps) before dispatching anything; a second `run` invocation against a
313
+ live-leased run refuses. The native engine owns the step spine while it drives.
314
+ An expired lease is claimable, so a crashed engine never
315
+ wedges a run. `status` stays read-only and always works.
316
+
317
+ ### B.6 Progress tracking
318
+
319
+ Progress inspection is **pull-based**: `workflow status` reports step rows,
320
+ notes, evidence, and summaries; there is no daemon.
321
+
322
+ What's new: **`akm log --run <run-id>`** reads a run's `workflow_*` /
323
+ `workflow_unit_*` events off the general append-only events stream;
324
+ `--since '@offset:<id>'` resumes from the last seen row-id cursor, so a
325
+ cooperating process can poll it on an interval — there's no daemon, and
326
+ no dedicated workflow-watch command (0.9.0 dropped one; this is the general
327
+ `log` surface instead). It's not a push channel in Claude Code's
328
+ `/workflows`-live-tree sense (there's no server pushing to a client — the
329
+ poller polls under the hood), but it closes most of the practical gap: a
330
+ second terminal polling `akm log --run <run-id> --since '@offset:<id>'` next
331
+ to `akm workflow run <run-id>` gets a live-feeling tail with no daemon of its
332
+ own. Event metadata is ids/status/enums only, never workflow-authored
333
+ content, so it's safe to pipe into logs or dashboards.
334
+
335
+ ### B.7 Quality gates
336
+
337
+ Native completion evaluates the step's **promoted artifact**
338
+ (real JSON data, clipped at 4000 chars) against the rubric, not a
339
+ machine-generated prose summary. A non-empty rubric requires the frozen
340
+ `workflow.judgeEngine`, which may be an LLM or agent engine. Only a well-formed
341
+ affirmative verdict advances the step; missing, failed, and malformed verifier
342
+ results reject. Invoked gates are journaled. `akm workflow resume` can reopen a
343
+ failed or blocked run after the underlying issue is corrected.
344
+
345
+ ### B.8 Check-in: stall nudging without a daemon
346
+
347
+ Unchanged from before the engine: akm records the invoking harness/session
348
+ context (`agent_harness`, `agent_session_id`) from environment hints
349
+ (`src/workflows/runtime/agent-identity.ts`), arms a **check-in** timestamp
350
+ (`checkin_armed_at`) — not a background thread — and on the next `workflow
351
+ status` poll by a supervising observer, `evaluateCheckin()`
352
+ (`src/workflows/runtime/checkin.ts`) compares `now` against
353
+ `max(updated_at, checkin_armed_at)`; past a 90s stall window it surfaces a
354
+ `continue` notification through the normal command output. The design ADR
355
+ explicitly rejects a background-thread alternative: "No daemon in a CLI… the
356
+ command loop is already the heartbeat." A parallel, independent check-in
357
+ exists at the *unit* level (`last_checkin_at` marks a long-running unit live
358
+ so a stalled one can be reclaimed) — same no-daemon, pure-timestamp design,
359
+ different granularity.
360
+
361
+ The supervising observer receives that `continue` notification only while
362
+ polling; akm does not push it in the background.
363
+
364
+ ### B.9 CLI surface
365
+
366
+ `akm workflow` (`src/commands/workflow-cli.ts`) exposes exactly six public subcommands, derived here from `workflowCommand.subCommands`, not hand-listed:
367
+
368
+ ```
369
+ akm workflow status|list|create|resume|abandon|run
370
+ ```
371
+
372
+ All six are stable and ungated. `create --print` prints a starter without writing, and `akm lint
373
+ --type workflows` structurally validates it. The removed `start`/`next`/
374
+ `complete` lifecycle and older `template`/`validate`/`watch` subcommands have
375
+ no aliases. Bare `akm workflow` is a usage error (exit 2).
376
+
377
+ ---
378
+
379
+ ## Part C — Side-by-side
380
+
381
+ akm has peer `.md` and `.yml` asset source formats with one stable native
382
+ orchestration surface.
383
+
384
+ | Dimension | Claude Code workflow | akm workflow |
385
+ |---|---|---|
386
+ | **Artifact** | Imperative JS program (`script`) | Declarative workflow asset (`.md` or `.yml`), compiled through source IR v1 to durable-v4-family `irVersion: 5` |
387
+ | **Authored by** | The agent, inline, per-task, ephemeral | Human or agent, saved as a reusable bundle asset |
388
+ | **Who executes work** | The harness runs the script; subagents do the work | The native engine, dispatching to a configured harness |
389
+ | **Unit of work** | `agent()` — a fresh LLM subagent context | One dispatch or one `map` item under the engine |
390
+ | **Concurrency** | Massively parallel (≤16 concurrent, ≤1000 total, `pipeline`/`parallel`) | Bounded map concurrency, frozen into the plan |
391
+ | **Control flow** | Full JS: loops, conditionals, fan-out, budget-scaled | `unit`/`map`/`route`, bare reference strings, and bounded gate retries; no backward routes |
392
+ | **State store** | Transcript dir (`journal.jsonl`, `agent-*.jsonl`) | SQLite `state.db`; native-engine runs add unit rows and a frozen `plan_json`/`plan_hash` |
393
+ | **Scope / lifetime** | One session, one turn-shaped fan-out | Cross-session, per-project `scope_key`, resumable indefinitely |
394
+ | **Progress model** | Push: live `/workflows` tree + `task-notification` | Pull via `status`, with near-live NDJSON event polling via `akm log` |
395
+ | **Resume** | Prefix-cache replay keyed on `runId` (needs determinism) | Durable row resume; native-engine units add journaled replay keyed on content-derived identity |
396
+ | **Quality gates** | Agent-authored (adversarial verify, judge panels, schemas) | Fail-closed LLM or agent verification of typed artifacts; `gate.max_loops` bounds retries |
397
+ | **Sandbox / trust** | Restricted JS interpreter, no FS; subagents use tools | No sandbox; engine units can opt into `isolation: worktree`, which is not a security boundary |
398
+ | **Nesting** | `workflow()`, one level deep, sharing the parent's concurrency cap/budget | None built in |
399
+ | **Stability** | Stable harness feature | Format and `run` both Stable and ungated |
400
+
401
+ ---
402
+
403
+ ## Part D — Where they overlap
404
+
405
+ Despite living on different layers, they converge on several ideas — and the
406
+ native engine (Part B.4) made two of these convergences much closer than they
407
+ used to be:
408
+
409
+ 1. **Task decomposition into named units** — phases/agents vs. steps/units.
410
+ 2. **Durable run identity and resume** — `runId` prefix-cache vs. `state.db`
411
+ workflow-run rows. Both are built to survive interruption and pick up where
412
+ they left off; the native engine's resume is also **journaled replay**
413
+ keyed on content-derived unit identity, close in spirit to Claude Code's
414
+ cache-and-replay.
415
+ 3. **Per-unit status + evidence** — journal return values vs. step
416
+ `status`/`notes`/`evidence`/`summary`, plus per-unit
417
+ `workflow_run_units` rows with the same shape.
418
+ 4. **A "keep going" nudge** — Claude Code's `task-notification`/resume vs.
419
+ akm's run-level `continue` check-in directive and its unit-level
420
+ `last_checkin_at` heartbeat.
421
+ 5. **Structured validation of results** — Claude Code's `schema` option
422
+ (forced `StructuredOutput`, retried) vs. akm's per-unit `output` JSON
423
+ Schema and fail-closed artifact verifier under the native engine.
424
+ 6. **Scaffolding + validation of the artifact** — `meta` shape checks vs.
425
+ `akm workflow create --print` / `akm lint --type workflows`, the peer
426
+ source adapters, and their shared source IR.
427
+ 7. **Bounded, capped concurrency with the same default formula.** Claude
428
+ Code's `agent()` cap and akm's unit scheduler both default to
429
+ `min(16, cores − 2)` — not a coincidence; the engine's CPU-derived default
430
+ was written to match it (B.4).
431
+ 8. **Awareness of the invoking session** — Claude Code owns the session; akm
432
+ *records* it (`CLAUDE_SESSION_ID` → `claude`). Under the engine this
433
+ awareness becomes load-bearing, not just descriptive: the run lease (B.5)
434
+ uses it to arbitrate which invocation may hold a given run right now.
435
+ 9. **Native dispatch spans harnesses.** akm executes each unit by invoking a
436
+ configured agent CLI — ten harnesses are supported, Claude Code among them —
437
+ so an akm run composes with the same tools without either system embedding
438
+ the other.
439
+
440
+ ---
441
+
442
+ ## Part E — Where they still diverge, and where they no longer do
443
+
444
+ The old axis — "who holds the execution loop" — is still the right frame.
445
+ Claude Code owns its session-native loop; akm's one public execution surface,
446
+ `akm workflow run`, owns the durable workflow loop.
447
+
448
+ - **Claude Code workflows always own execution.** The harness is the runtime;
449
+ the script is the plan; subagents are the workers. Because the harness
450
+ replays the script to resume, it must constrain the script (no wall-clock,
451
+ no randomness, no FS) and keep it ephemeral. Parallelism is free because the
452
+ runtime schedules it.
453
+
454
+ - **An akm workflow under `akm workflow run` owns execution.** The native engine
455
+ dispatches real concurrent units through the multi-harness
456
+ substrate: `RunnerSpec` `llm|agent|sdk` + `executeRunner`, the same spawner
457
+ `akm improve`/`reflect` already uses. It retries them, isolates them in
458
+ worktrees, judges their typed artifacts, and enforces budget ceilings — a
459
+ genuine, Stable executor. It keeps a durable spine through a frozen plan and
460
+ journaled resume rather than trading those properties away for parallelism.
461
+
462
+ - **akm owns the plan and the durable completion semantics** — routing,
463
+ artifact promotion, verification, and replay — while the harness it
464
+ dispatches to owns only the work inside a single unit.
465
+
466
+ Concretely, per surface:
467
+
468
+ | | Claude Code workflow | akm native engine (`run`) |
469
+ |---|---|---|
470
+ | Lifetime | Ephemeral (session-scoped) | Durable (SQLite, cross-session) |
471
+ | Parallel? | Yes, by construction | Yes, bounded (B.4) |
472
+ | Self-contained? | Yes — carries its own workers | Yes, when a configured engine is available (or `opencode` is on PATH) |
473
+ | Sandbox | Restricted JS interpreter, no FS for the script itself | None for the shell/agent substrate; optional `isolation: worktree` is not a security boundary |
474
+ | Artifact | Executable script | Managed `.md` or `.yml` workflow asset, compiled and frozen |
475
+
476
+ The genuinely durable conclusion, restated for 0.9.2: Claude Code is strong
477
+ at in-session parallel LLM execution, while akm is strong at durable,
478
+ cross-session procedures. The native engine keeps the second half of that
479
+ trade while buying back some of the first — dispatching units to whichever
480
+ harness the operator configured, Claude Code included.
481
+
482
+ ---
483
+
484
+ ## Part F — What's left to integrate
485
+
486
+ Most of what this section used to propose as *future* integration work has
487
+ **shipped** as the native engine described in Part B — not as
488
+ Claude-Code-specific features, but as harness-agnostic ones:
489
+
490
+ | Formerly-proposed idea | Status |
491
+ |---|---|
492
+ | A blessed loop pattern for invoking an akm run with structured per-step results | **Shipped**, generalized: an invoking agent or orchestrator invokes the native `akm workflow run`, which dispatches to any of ten configured harnesses (Part B.4) — not Claude-Code-specific |
493
+ | Machine-readable, near-live progress instead of polling `status` | **Shipped**: `akm log --run <id> --since '@offset:<id>'` (Part B.6) |
494
+ | Structural (schema) validation of step output, not just an LLM prose judge | **Shipped**: per-unit `output` JSON Schema + typed step artifacts, validated before a gate ever runs (Part B.4/B.7) |
495
+ | An explicit fan-out step type | **Shipped**: `map` steps with `over`/`concurrency`/`reducer` (Part B.4) |
496
+
497
+ What's genuinely still not built, as of this writing:
498
+
499
+ ### F.1 Correlate an akm run with the invoking harness's own run/session id
500
+
501
+ `agent-identity.ts` captures `agent_harness`/`agent_session_id` from
502
+ environment hints, and each unit records its harness-native session id
503
+ (Part B.3). Neither captures a *workflow-
504
+ level* id from an external orchestrator (e.g. a Claude Code `Workflow` tool's
505
+ own `runId`) when it invokes an akm run — there is no env-hint or CLI flag for
506
+ it today, so correlating an akm run with its invoking Claude Code workflow
507
+ invocation still has to be done by hand (matching timestamps, logs).
508
+
509
+ ### F.2 Notify a supervising observer instead of waiting for its next poll
510
+
511
+ The check-in (Part B.8) is still exactly as passive as the ADR mandates: it
512
+ surfaces only on the next `workflow status` poll, never proactively.
513
+ There is no signal-file or other out-of-band mechanism that lets a stalled
514
+ run *notify* a supervising or polling observer capable of receiving one (the ADR's own design
515
+ contemplates "a best-effort checkin signal file under the run scope" as a
516
+ future extension) — this remains unbuilt.
517
+
518
+ ### F.3 Two-way compilation between the artifacts
519
+
520
+ Neither direction exists: there is no `akm workflow export` that emits a
521
+ script/pattern specialized to a given workflow for a specific harness, and
522
+ there is no `akm workflow ingest-journal` (or equivalent) that converts an
523
+ external orchestration journal into an akm run. The former live
524
+ external-driver protocol is retired; an external agent can invoke
525
+ `akm workflow run` and inspect `status` or `akm log`, but it does not drive an
526
+ alternate akm execution surface.
527
+
528
+ ### F.4 Suppress the check-in when an observer already provides liveness
529
+
530
+ The check-in fires uniformly for any active run past the stall window
531
+ (`evaluateCheckin`, Part B.8) — it does not special-case a recorded
532
+ invoking harness/session context when an external observer already supplies
533
+ its own liveness/orchestration (so it would not stall the way a free-form chat
534
+ agent can). This is a small, purely local trim on top of the existing check-in
535
+ logic, not a new subsystem, but it is not implemented today.
536
+
537
+ ### F.5 Distribute non-akm executable workflow scripts as akm assets
538
+
539
+ The `workflow` asset type carries akm's peer `.md` and `.yml` source formats.
540
+ There is no mechanism for it to carry or reference an
541
+ externally-executable script (a Claude Code `Workflow` tool script, or
542
+ anything else) as an alternate form alongside the runbook, which would let
543
+ akm's package-manager strengths (`add`, search, `curate`, version pinning,
544
+ feedback/improve) apply to *that* artifact too. This is the largest and most
545
+ speculative item on this list.
546
+
547
+ The guiding principle is to preserve each layer's strength. The native engine
548
+ gives akm concurrency without discarding its durable, verified spine, and it
549
+ dispatches the work itself to whichever harness the operator configured —
550
+ Claude Code included — instead of either side reimplementing the other.
@@ -0,0 +1,72 @@
1
+ # 0001 — No interpolation: data reaches units as attached structured context
2
+
3
+ ## Context
4
+
5
+ The pre-unification engine spliced data into a unit's prompt by string
6
+ substitution (`${{ item }}`-style interpolation). That created an injection
7
+ class: a producer's output could itself contain `${{ ... }}`-shaped text,
8
+ and a re-scan of the already-substituted prose would interpret it as more
9
+ grammar (the "P1 `{{item}}` re-scan injection" the essay below names). The
10
+ workflow-format-unification work (spec §2.3) closed that class structurally
11
+ rather than by escaping or sanitizing.
12
+
13
+ ## Decision
14
+
15
+ The essay, moved verbatim from `src/workflows/exec/native-executor.ts`'s
16
+ module header:
17
+
18
+ > Data flow (workflow-format-unification, spec §2.3): there is NO
19
+ > interpolation language. A unit's instructions are the step's body prose
20
+ > BYTE-EXACT — prose is never scanned for reference syntax, so a literal
21
+ > `${{` in a body is content, not grammar. Data reaches the unit as ATTACHED
22
+ > STRUCTURED CONTEXT rather than string splices: `buildUnitPrompt`
23
+ > (`exec/step-work.ts`) wraps the verbatim instructions with JSON blocks for
24
+ > the run params, a map unit's item + index, and the artifacts named by the
25
+ > step's `inputs:`. Because nothing is ever substituted INTO the prose, the
26
+ > P1 `{{item}}` re-scan injection class is structurally impossible.
27
+ >
28
+ > References survive only in the three whole-value FRONTMATTER positions the
29
+ > closed two-root grammar occupies (`program/expressions.ts`): `map.over`,
30
+ > `route.input`, and each `inputs[]` entry. They resolve ONCE per step
31
+ > against `{ params, stepOutputs }`. There is NO ambient key search: a
32
+ > `steps.<id>.output.<path>` reference addresses INTO that step's recorded
33
+ > output explicitly.
34
+ >
35
+ > Step outputs (`steps.<id>.output…`): every engine-executed step journals a
36
+ > promoted ARTIFACT under `evidence.output` — the solo unit's result/text,
37
+ > the collect reducer's per-item array, or the vote reducer's winner — and
38
+ > that artifact is what the reference scope exposes
39
+ > (`projectStepOutput`). The documented addressing
40
+ > (`steps.discover.output.files`) therefore resolves against real step
41
+ > results, never the raw evidence envelope (peer review R1). Steps completed
42
+ > manually (no `output` key in their evidence) expose their recorded
43
+ > evidence object as-is.
44
+
45
+ ## Consequences
46
+
47
+ - A workflow author can never accidentally (or deliberately) inject
48
+ reference grammar through step output content — the prose a unit receives
49
+ is always byte-exact, and the only place reference syntax is EVER parsed
50
+ is the three closed frontmatter positions, resolved once, before dispatch.
51
+ - `steps.<id>.output` addressing is stable and explicit: it always resolves
52
+ against the PROMOTED artifact a step recorded, never against an internal
53
+ evidence shape that could change without the reference contract changing
54
+ with it.
55
+ - Peer review R1 flagged an earlier draft where the documented addressing
56
+ risked resolving against the raw evidence envelope instead of the
57
+ promoted artifact; the fix (route addressing through `projectStepOutput`)
58
+ is what the essay above describes as settled behavior, not a proposal.
59
+ - Because references resolve ONCE per step (not per unit, not lazily),
60
+ resuming a run against the same frozen plan recomputes the identical
61
+ resolution — this is part of what makes `computeStepWorkList`
62
+ (`step-work.ts`, ADR 0002) a pure function of (plan, params, journaled
63
+ results).
64
+
65
+ ## Provenance
66
+
67
+ - Source: `src/workflows/exec/native-executor.ts`, module header (the "Data
68
+ flow" / "References" / "Step outputs" paragraphs).
69
+ - Spec: workflow-format-unification, spec §2.3.
70
+ - Review round: peer review R1 (addressing must resolve against the
71
+ promoted artifact, not the raw evidence envelope).
72
+ - Extracted: P4 (`docs/plans/specs/p4-deletions-closeout.md` §4.2), 2026-08-27.