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,83 @@
1
+ # 0007 — Recursive child-workflow composition bounds
2
+
3
+ ## Context
4
+
5
+ P3a made a workflow step able to compose another workflow as a child — via
6
+ a direct `uses: workflows/<ref>` step or a task-wrapped `uses: tasks/<ref>`
7
+ step whose target is itself a workflow — and made that composition
8
+ recursive: a child can itself compose a grandchild, bounded only by policy.
9
+ Recursion without a bound is a corruption/DoS surface at both ends of the
10
+ plan's lifecycle: an attacker-controlled or merely buggy source could
11
+ either recurse arbitrarily deep at freeze time, or (once persisted) blow up
12
+ the aggregate bytes a parent plan carries once every descendant's frozen
13
+ plan is embedded inside it.
14
+
15
+ ## Decision
16
+
17
+ Moved verbatim from `src/workflows/resource-limits.ts`'s composition-bounds
18
+ section:
19
+
20
+ > Enforced ONCE, at freeze, before publication, in
21
+ > `src/workflows/freeze/targets/child-workflow.ts` — the ONE resolver both
22
+ > the direct `uses: workflows/<ref>` form and the task-wrapped form route
23
+ > through — and re-enforced as a corruption gate whenever a parent plan is
24
+ > DECODED (`src/workflows/ir/schema-v4.ts`'s recursive
25
+ > `decodeChildWorkflowTarget`).
26
+ >
27
+ > `WORKFLOW_MAX_COMPOSITION_DEPTH` (= 8): max workflow composition depth —
28
+ > the root workflow plus this many descendant levels (root is depth 0, so 8
29
+ > descendant levels freeze and a 9th fails). Deep enough that no legible
30
+ > authored composition hits it (the per-workflow bounds — 256 steps, 64
31
+ > engines, 10 000 map expansion — are the practical ceilings), shallow
32
+ > enough that the worst case is bounded recursion during freeze and decode.
33
+ >
34
+ > The max AGGREGATE canonical-JSON bytes of every embedded child plan in
35
+ > ONE root freeze (the sum across the whole composition tree, not per
36
+ > child) is deliberately HALF of `WORKFLOW_MAX_PLAN_BYTES` rather than a
37
+ > larger value layered on top of it: an embedded child plan lives INSIDE
38
+ > its parent's own plan bytes, so a cap independent of (and comparable to)
39
+ > the total plan cap would let composition alone exhaust it. Halving keeps
40
+ > the parent's own content always able to claim at least half the budget,
41
+ > and lets this actionable, freeze-time `COMPOSITION_INVALID` (which names
42
+ > the offending child ref and the running total) fire before the terse,
43
+ > unlocated decoder message a reviewer would otherwise have to debug.
44
+ > Rejected alternative: raising `WORKFLOW_MAX_PLAN_BYTES` itself, which
45
+ > would relax a corruption/DoS bound for every plan — including ones with
46
+ > no children — to serve a bound only composition needs (A-N6).
47
+
48
+ ## Consequences
49
+
50
+ - The bound is enforced TWICE by design, not once: at freeze (where a
51
+ violation gets an actionable `COMPOSITION_INVALID` naming the offending
52
+ ref) and again at decode (where it acts purely as a corruption gate for a
53
+ persisted plan that should already be valid). A future change that
54
+ removes the decode-time re-check on the theory that "freeze already
55
+ checked it" would remove the last line of defense against a corrupted or
56
+ hand-edited `plan_json` row.
57
+ - `WORKFLOW_MAX_COMPOSITION_DEPTH` and the aggregate-bytes cap are both
58
+ named in `docs/plans/specs/p4-deletions-closeout.md` §0 as one of the
59
+ three numbers ("`WORKFLOW_MAX_*` limits, plan `irVersion` 5, `hashVersion`
60
+ 6") that P4 is expressly forbidden from changing — a P4 commit that
61
+ touches either is a review-blocking violation, and the same discipline
62
+ should apply to any later phase unless a spec explicitly re-opens it.
63
+ **2026-08-27 update:** `hashVersion` itself moved on from that pinned
64
+ value after this ADR was written — P4's R-R15 advisory (a reference
65
+ binding's resolved value sitting outside the unit-input hash) was closed
66
+ by bumping `hashVersion` 5 → 7 to fold `taskInputs` into the preimage
67
+ (`src/workflows/exec/step-work.ts`, `.update("akm.workflow.unit\0v7\0")`,
68
+ `hashVersion: 7`); see ADR 0002's "Consequences" entry for the full
69
+ supersession. The composition-bounds discipline this entry describes
70
+ still applies to `WORKFLOW_MAX_COMPOSITION_DEPTH` and the aggregate-bytes
71
+ cap unchanged — only the `hashVersion` figure in the quoted §0 sentence is
72
+ now stale.
73
+ - The half-of-plan-bytes ratio is a relationship, not an independent
74
+ constant — if `WORKFLOW_MAX_PLAN_BYTES` itself is ever revisited, the
75
+ composition cap should move with it rather than being independently
76
+ retuned, per the rejected-alternative reasoning above.
77
+
78
+ ## Provenance
79
+
80
+ - Source: `src/workflows/resource-limits.ts`, the "Recursive child-workflow
81
+ composition bounds" section.
82
+ - Spec: `docs/plans/specs/p3a-plan-v5-child-freeze.md` §4.5, A-N6.
83
+ - Extracted: P4 (`docs/plans/specs/p4-deletions-closeout.md` §4.2), 2026-08-27.
@@ -0,0 +1,88 @@
1
+ # 0008 — Task input binding normalization and re-binding across a composition
2
+
3
+ ## Context
4
+
5
+ P2b gave a task-composing workflow step a `with:` record that freezes into
6
+ typed `TaskInputBinding[]` against the target's declared `inputs:`
7
+ contract. P3a then made workflows composable — a task-wrapped step can
8
+ target a child WORKFLOW rather than a plain task — which raises a second
9
+ problem `freezeTaskInputBindings` alone does not solve: a v4 task's own
10
+ already-classified bindings (literal or reference) need to be re-expressed
11
+ against the CHILD workflow's declared `params:` contract, by name, without
12
+ re-deriving each entry's classification from its value's shape a second
13
+ time.
14
+
15
+ ## Decision
16
+
17
+ Moved verbatim from `src/workflows/freeze/task-bindings.ts`'s module
18
+ header:
19
+
20
+ > The pure `with:` -> `TaskInputBinding[]` normalizer (spec
21
+ > docs/plans/specs/p2b-input-bindings.md §3.2 A2, §3.3), plus the pure
22
+ > re-binder a recursive composition needs on top of it (spec
23
+ > docs/plans/specs/p3a-plan-v5-child-freeze.md A-N8).
24
+ >
25
+ > `freezeTaskInputBindings` classifies a genuinely AUTHORED `with:` record
26
+ > against a contract; `src/workflows/freeze/targets/task.ts`'s
27
+ > `taskDispatch` calls it with THIS step's own authored `with:` against the
28
+ > composed task's own `InputContract`, never anything from an outer
29
+ > composition (§3.5 — there is no merge across a composition chain).
30
+ > `rebindTaskInputBindings` instead takes an ALREADY-classified
31
+ > `TaskInputBinding[]` — a v4 task's own effective inputs — and re-binds it
32
+ > by name against a DIFFERENT contract, trusting each entry's existing
33
+ > `kind` rather than re-deriving it from the value's shape;
34
+ > `src/workflows/freeze/targets/child-workflow.ts`'s `childWorkflowDispatch`
35
+ > is its only caller, re-binding a task-wrapped composition's effective
36
+ > inputs against the child workflow's declared `params:`.
37
+ >
38
+ > Everything decidable at FREEZE time is decided here: an unknown `with:`
39
+ > key, a missing required-without-default input, a literal value failing
40
+ > its declared schema, and a reference's syntax + "names an earlier step
41
+ > that exists" / "names a declared workflow param" structural check. A
42
+ > reference's *resolved* value is validated PRE-ATTEMPT instead
43
+ > (`src/workflows/exec/step-work.ts`, §3.6) — this module never resolves
44
+ > anything, so it needs no run params and no step outputs.
45
+ >
46
+ > Pure function: no IO, no config reads. Imports only
47
+ > `src/execution/input-contract.ts`, `src/workflows/program/expressions.ts`,
48
+ > and `src/core/errors.ts`.
49
+
50
+ And from `rebindTaskInputBindings`'s own doc comment — the part explaining
51
+ WHY re-binding cannot simply reuse `freezeTaskInputBindings`:
52
+
53
+ > This exists because a `kind: "literal"` binding's VALUE can be an
54
+ > arbitrary JSON value, including one shaped exactly like the `{from:
55
+ > "<ref>"}` reference grammar (e.g. a declared default for an object-typed
56
+ > input). Round-tripping such a binding back through a `with:`-shaped
57
+ > record and re-running `freezeTaskInputBindings`'s value-shape-driven
58
+ > classification (`normalizeOneEntry`) would silently reinterpret that
59
+ > literal as a live reference binding — the composing task's OWN contract
60
+ > already settled `kind` once; this function trusts that answer instead of
61
+ > asking the value's shape again (code-review finding, docs/plans/specs/
62
+ > p3a-plan-v5-child-freeze.md).
63
+
64
+ ## Consequences
65
+
66
+ - `freezeTaskInputBindings` and `rebindTaskInputBindings` must never be
67
+ merged into one shape-sniffing function, even though they look similar —
68
+ doing so would reintroduce exactly the silent-reinterpretation bug the
69
+ code-review finding above caught: a literal value that happens to look
70
+ like `{from: "..."}` would flip from literal to reference on its second
71
+ pass through composition.
72
+ - A reference binding's `from` target (an earlier step or declared param)
73
+ never changes across a re-bind — only which CONTRACT it validates against
74
+ changes. A future caller that tries to re-target a reference's `from`
75
+ during re-binding is outside this module's contract and needs new
76
+ reasoning, not an extension of `rebindTaskInputBindings`.
77
+ - Both functions stay pure and IO-free by construction — a caller that
78
+ needs a resolved value (not just a structurally-validated binding) is
79
+ looking for `step-work.ts`'s pre-attempt resolution instead, not a change
80
+ to this module.
81
+
82
+ ## Provenance
83
+
84
+ - Source: `src/workflows/freeze/task-bindings.ts`, module header and
85
+ `rebindTaskInputBindings`'s doc comment (code-review finding).
86
+ - Spec: `docs/plans/specs/p2b-input-bindings.md` §3.2 A2, §3.3;
87
+ `docs/plans/specs/p3a-plan-v5-child-freeze.md` A-N8.
88
+ - Extracted: P4 (`docs/plans/specs/p4-deletions-closeout.md` §4.2), 2026-08-27.
@@ -0,0 +1,80 @@
1
+ # 0009 — Child-run publication's hand-duplicated INSERT column list
2
+
3
+ ## Context
4
+
5
+ `WorkflowRunsRepository.insertRun` is the ordinary path that creates a
6
+ top-level `workflow_runs` row: a 13-column `INSERT` built from
7
+ `InsertRunInput`. P3a added `publishChildWorkflowRun`, the idempotent
8
+ publication path a parent step uses to create (or, on a race, discover) a
9
+ child run — `SELECT`-else-`INSERT` inside one `BEGIN IMMEDIATE` transaction,
10
+ so two concurrent callers racing on the same `(parentRunId, invocationKey)`
11
+ serialize on SQLite's write lock instead of double-inserting. Its own
12
+ `INSERT` is NOT a call to `insertRun` — it is a second, hand-written
13
+ 16-column `INSERT` (the same 13 columns plus `parent_run_id`,
14
+ `parent_unit_id`, `invocation_key`) inside the same prepared statement as
15
+ the idempotency check.
16
+
17
+ This is not an oversight. P3a's review log (R11) considered sharing one
18
+ `INSERT` builder between the two paths and explicitly declined to guess at
19
+ the right shape for it with the caller (`publishChildWorkflowRun`) barely a
20
+ commit old — a signature refactor made in haste, before the shape had
21
+ proven itself in use, was judged a worse bet than a documented duplication.
22
+
23
+ ## Decision
24
+
25
+ **Keep the duplication as an explicit, cross-referenced invariant rather
26
+ than refactoring it.** `insertRun`'s 13-column list and
27
+ `publishChildWorkflowRun`'s first 13 columns must stay byte-identical by
28
+ hand — each site carries a one-line comment naming the other and pointing
29
+ here. This was re-affirmed rather than fixed during the P4 close-out
30
+ (`docs/plans/specs/p4-deletions-closeout.md` §8, row R-R3): "A signature
31
+ refactor now, with P3b's caller one commit old, is exactly the guess R11
32
+ declined to make."
33
+
34
+ The two INSERTs are NOT candidates for a trivial "extract the shared 13
35
+ columns" refactor either, despite looking like one: they run inside
36
+ different transaction shapes (`insertRun` runs standalone or inside
37
+ whatever transaction its caller already holds; `publishChildWorkflowRun`
38
+ runs inside its own `immediateTransaction`, specifically `BEGIN IMMEDIATE`
39
+ so the idempotency check and the insert are atomic against a racing
40
+ publisher) and `publishChildWorkflowRun`'s version is followed by
41
+ `insertSteps`, a `plan_json`/`plan_hash`/`plan_ir_version` update, and an
42
+ idempotent `workflow_started` event insert — all inside the SAME
43
+ transaction, none of which `insertRun`'s callers do at the same call site.
44
+ A shared builder would need to either accept both shapes as parameters
45
+ (defeating the point of extraction) or leave the transaction/follow-up
46
+ choreography to the caller anyway (in which case the "shared" part is only
47
+ the column list, which is exactly what the hand-duplication already is).
48
+
49
+ ## Consequences
50
+
51
+ - Any future change to `workflow_runs`'s schema that adds or removes a
52
+ column BOTH paths write must update both `INSERT`s in the same commit.
53
+ The two comments (at `insertRun` and at `publishChildWorkflowRun`'s
54
+ `INSERT`) exist specifically so a reviewer checking one site is pointed at
55
+ the other.
56
+ - A drift between the two column lists is a silent correctness bug, not a
57
+ compile error — SQLite's parameter binding is positional, so a column
58
+ added to `insertRun` but not to `publishChildWorkflowRun` (or reordered in
59
+ one but not the other) would silently bind the wrong value to the wrong
60
+ column rather than failing loudly. This is precisely why the invariant
61
+ comment exists as a standing reminder rather than being left implicit.
62
+ - A future refactor attempt should re-read R11 and this record before
63
+ concluding the duplication is accidental — it is a deliberate,
64
+ re-affirmed choice made twice (P3a, then P4), not an oversight waiting to
65
+ be cleaned up.
66
+
67
+ ## Provenance
68
+
69
+ - Sites: `src/storage/repositories/workflow-runs-repository.ts` —
70
+ `WorkflowRunsRepository.insertRun` and
71
+ `WorkflowRunsRepository.publishChildWorkflowRun`.
72
+ - Source: P3a Review log, finding R11 (the original "should these share a
73
+ builder?" question, declined).
74
+ - Disposition: `docs/plans/specs/p4-deletions-closeout.md` §8, row R-R3
75
+ ("RESOLVED AS AN INVARIANT, not a refactor").
76
+ - Extracted: P4, 2026-08-27. Unlike ADRs 0001–0005/0007–0008, this record is
77
+ new content written for the close-out, not a moved essay — there was no
78
+ pre-existing long comment block to extract; R11's finding lived only in a
79
+ review log until this record and the two source comments it backs were
80
+ added.
@@ -0,0 +1,36 @@
1
+ # 0010 — The external driver protocol was cut
2
+
3
+ ## Context
4
+
5
+ An earlier design considered an external driver protocol for workflow
6
+ execution: `workflow brief` / `workflow report` verbs plus a `--settle`
7
+ flag, giving an outside process a way to drive a run step-by-step instead
8
+ of `akm`'s own native engine executing it end to end.
9
+
10
+ ## Decision
11
+
12
+ Cut. AKM's native engine (`src/workflows/exec/native-executor.ts`) is the
13
+ only executor; no external driver protocol was implemented or shipped in
14
+ any 0.9.x release.
15
+
16
+ This ADR is a stub, not the normative record. The full decision — including
17
+ the option considered, the evidence gathered, and the verification greps
18
+ proving nothing in `src/` implements the cut surface — lives in
19
+ `docs/architecture/specs/driver-protocol-keep-or-cut.md`, which this stub
20
+ exists only to make discoverable from the ADR index (this refactor's design
21
+ history otherwise lives entirely under `docs/architecture/decisions/`).
22
+
23
+ ## Consequences
24
+
25
+ See `docs/architecture/specs/driver-protocol-keep-or-cut.md` for the full
26
+ consequence analysis. In short: `workflow_run_units` has had exactly one
27
+ consumer (the native executor) throughout, with no dual-executor
28
+ compatibility surface to maintain.
29
+
30
+ ## Provenance
31
+
32
+ - Normative record: `docs/architecture/specs/driver-protocol-keep-or-cut.md`.
33
+ - Cross-referenced from: `docs/plans/0.9.2-architecture-deletion-audit.md`'s
34
+ "Deleted architecture" table; `docs/architecture/specs/
35
+ workflow-engine-buy-vs-build.md` §7 (Recommendation).
36
+ - Extracted: P4 (`docs/plans/specs/p4-deletions-closeout.md` §4.2), 2026-08-27.
@@ -0,0 +1,125 @@
1
+ # 0011 — The engine run loop's invariants: gate spine, frozen plan, run lease, process lifecycle
2
+
3
+ ## Context
4
+
5
+ `src/workflows/exec/run-workflow.ts` is `akm workflow run`'s
6
+ start/resume/execute path — the single execution surface that walks a
7
+ frozen plan and dispatches every unit. It sits one layer above
8
+ `native-executor.ts` (unit dispatch, ADR 0001) and `step-work.ts` (pure
9
+ step semantics, ADR 0002): this module is where the ENGINE LOOP's own
10
+ invariants live — never bypassing the gate spine, reading the plan from
11
+ its frozen row rather than live source, holding a run lease, running
12
+ bounded gate loops, and not leaking OS handles on exit.
13
+
14
+ ## Decision
15
+
16
+ Moved verbatim from `run-workflow.ts`'s module header:
17
+
18
+ > Engine-driven workflow execution — the `akm workflow run`
19
+ > start/resume/execute path, and the single execution surface for a run:
20
+ > akm walks the frozen plan and dispatches every unit itself.
21
+ >
22
+ > **Invariant (plan §*Never bypass the gate spine*):** every step advances
23
+ > through `completeWorkflowStep`, never by writing step rows directly, so
24
+ > the summary-validation gate and run-state derivation stay authoritative.
25
+ > A gate rejection (`SummaryValidationFailure`) STOPS the engine and
26
+ > surfaces the corrective feedback — a gate is a gate, even for the engine.
27
+ >
28
+ > **Artifact-judging gates (redesign addendum, R2):** when a step declares
29
+ > completion criteria, the engine hands the gate a summary BUILT FROM the
30
+ > step's promoted artifact (canonical JSON, clipped, prefixed with a
31
+ > one-line unit count — `buildArtifactSummary`) instead of the machine-prose
32
+ > execution summary, so the judge evaluates real results. Each
33
+ > engine-driven judge call is journaled as a unit row (`node_id
34
+ > "<stepId>.gate"`, `unit_id "<stepId>.gate:l<loop>"`, runner "llm",
35
+ > `result_json` = the verdict) through the writer queue — it is an LLM call
36
+ > like any other. Human approvals are never cached: a blocked gate stays
37
+ > blocked.
38
+ >
39
+ > **Bounded gate loops (`gate.max_loops`, addendum R2):** a rejection on a
40
+ > step with `maxLoops > 1` re-executes the step subgraph with the judge's
41
+ > feedback + `missing[]` threaded into every unit prompt (`gateFeedback` on
42
+ > `StepExecutionContext`) — the feedback changes each unit's input hash, so
43
+ > the loop re-dispatches naturally instead of reusing the rejected rows.
44
+ > After `maxLoops` rejections the engine stops with the gate feedback,
45
+ > exactly like the one-shot case. A typed-artifact schema mismatch feeds
46
+ > the same loop (the validation errors are the feedback; no judge ran, so
47
+ > no gate unit is journaled for that attempt) — only the FINAL loop's
48
+ > mismatch fails the run. `effectiveGateMaxLoops` caps a step to a SINGLE
49
+ > loop whenever its subgraph's frozen target is anything other than a
50
+ > `command` (agent/SDK/LLM-runner) target — which covers both an `exec`
51
+ > unit (a `shell`/`script` target) and a composed `child-workflow` step,
52
+ > for two DIFFERENT reasons that both land on the same cap. An exec unit's
53
+ > argv is frozen and never interpolated, so it cannot read the feedback: a
54
+ > second loop would only re-run the identical side effect. A child-workflow
55
+ > unit has no prompt to thread feedback into either — its only frozen input
56
+ > is the composing step's resolved `with:` bindings, published once to the
57
+ > child run — so a second loop would re-publish the same child with
58
+ > byte-identical bindings rather than asking it anything new. Only a
59
+ > `command`-targeted (engine) step, whose feedback is threaded into its
60
+ > prompt, can loop past one; an authored `gate.max_loops` on an engine step
61
+ > is untouched.
62
+ >
63
+ > **Frozen plan (redesign addendum, R1):** the plan graph is read from the
64
+ > run row (`plan_json`, persisted by `startWorkflowRun` under migration
65
+ > 006) with a `plan_hash` integrity check — the workflow asset file is
66
+ > NEVER re-read for an in-flight run, so a mid-run asset edit cannot change
67
+ > behavior. Durable-row resume: re-invoking a partially-executed run
68
+ > re-dispatches only work that never completed.
69
+ >
70
+ > **Run lease (redesign addendum, R2):** exactly one engine invocation
71
+ > drives a run at a time. The lease (random holder id + 90s expiry on the
72
+ > run row) is acquired before any dispatch, renewed between steps, and
73
+ > released in a `finally` unless a failed run retains it as forensic
74
+ > state; a second `workflow run` on a live-leased run refuses up front, and
75
+ > an expired lease is claimable (crash recovery). While the lease is live,
76
+ > any competing spine advance is refused — the engine owns the run while
77
+ > driving (enforced inside `completeWorkflowStep`).
78
+ >
79
+ > **Process-lifecycle contract (owner finding 4 — no leaked handles):** the
80
+ > SDK dispatch path caches `opencode serve` CHILD PROCESSES in a per-env
81
+ > registry for reuse across units. Each live child is an OS handle that
82
+ > keeps Bun's event loop open; the registry's own teardown is wired only to
83
+ > `process.once('exit')`, which never fires while a child holds the loop
84
+ > open. That deadlock hangs a one-shot CLI (`akm workflow run` has no
85
+ > `process.exit` on success — it relies on the loop draining). The engine
86
+ > therefore DRAINS the dispatch registry (`disposeDispatchResources`) in its
87
+ > run `finally`, on EVERY exit path, so the process exits cleanly the
88
+ > moment the run resolves. The drain is synchronous, idempotent, and a
89
+ > no-op when no SDK server started.
90
+
91
+ ## Consequences
92
+
93
+ - Any future code path that advances a step must go through
94
+ `completeWorkflowStep` — a direct write to a step row bypasses both the
95
+ summary-validation gate and run-state derivation, silently breaking the
96
+ gate-is-a-gate guarantee even if the write itself succeeds.
97
+ - Because the frozen plan is read from `plan_json` (never the live asset),
98
+ editing a workflow's Markdown/YAML source after a run has started has
99
+ **no effect** on that run — this is the same "child source edits cannot
100
+ alter an in-flight parent run" property child workflows rely on (P3a),
101
+ applied to the root run itself.
102
+ - A new dispatch backend that spawns a long-lived child process (the way
103
+ the SDK harness does) must register its teardown with the SAME drain the
104
+ engine already calls in its run `finally` — relying on `process.exit` or
105
+ `process.once('exit')` alone reintroduces the exact hang class "owner
106
+ finding 4" fixed.
107
+ - The run lease is the ONLY exclusivity mechanism for driving a run;
108
+ anything that touches a run's step spine outside `completeWorkflowStep`
109
+ while a lease might be live needs to reason about this contract
110
+ explicitly rather than assuming single-writer access.
111
+
112
+ ## Provenance
113
+
114
+ - Source: `src/workflows/exec/run-workflow.ts`, module header.
115
+ - Review markers: "redesign addendum" R1/R2, "owner finding 4".
116
+ - Extracted: P4 (`docs/plans/specs/p4-deletions-closeout.md` §4.2), 2026-08-27.
117
+ Added as an 11th record, past the ten-item minimum set the spec named —
118
+ `run-workflow.ts` carries "(comment only)" on Lane B's file list (§6) but
119
+ was not cited by name in §4.2's minimum-ADR-set table; this essay does not
120
+ fit cleanly inside any of the other ten without either duplicating engine-
121
+ loop-specific reasoning into a step- or unit-scoped record or dropping it,
122
+ both of which the spec's no-reasoning-deleted rule forecloses. §4.2
123
+ explicitly permits the implementer to merge related essays and record the
124
+ final set in the commit body — this is the converse case, an essay that
125
+ does not merge cleanly, so it gets its own number instead.
@@ -0,0 +1,62 @@
1
+ # Architecture decision records
2
+
3
+ This directory holds the design history that used to live as long comment
4
+ blocks inside the modules the 0.9.2 task/workflow refactor touched. Brief
5
+ §9 named the problem directly: *"Historical essays inside large modules |
6
+ Keep invariant comments locally; move design history to ADRs | Reduce
7
+ cognitive load without deleting important reasoning."* These records are
8
+ that move.
9
+
10
+ ## Format
11
+
12
+ - One file per decision: `NNNN-kebab-title.md`, numbered in the order the
13
+ decision was extracted, not the order the underlying work happened.
14
+ - No front matter.
15
+ - Four sections, in this order:
16
+ - **Context** — the problem the module was solving and the constraints in
17
+ play when the decision was made.
18
+ - **Decision** — what was decided, including the essay's own reasoning
19
+ verbatim where it was already written as reasoning. Nothing here is
20
+ deleted or "cleaned up" on the move — see the binding rule below.
21
+ - **Consequences** — what the decision implies for callers, for future
22
+ changes, and (where the essay named one) for alternatives that were
23
+ considered and rejected.
24
+ - **Provenance** — the source file(s) and, where the essay named one, the
25
+ spec section or review round the reasoning came from, plus the date and
26
+ phase this record was extracted.
27
+
28
+ ## What moves here and what stays in the code
29
+
30
+ Per the brief's own framing (§15, quoted in
31
+ `docs/plans/specs/p4-deletions-closeout.md` §1.5): a code comment that
32
+ explains *why mutation is delayed, why a hash includes a field, why a
33
+ transaction boundary exists, why a source is revalidated, why a child
34
+ invocation is idempotent* — a short, load-bearing invariant a maintainer
35
+ needs sitting right next to the code — **stays in the code**. A comment
36
+ block that is historical narrative — peer-review findings, an earlier
37
+ draft, a superseded alternative, a phase-by-phase chronology of how a
38
+ design arrived where it is — moves here instead, replaced in the source by
39
+ a one-line invariant summary plus a relative link back to the ADR that
40
+ carries the full reasoning.
41
+
42
+ **Binding rule carried from the spec:** no ADR may delete reasoning. An
43
+ essay's text moves; it is not summarized away. Where an essay describes
44
+ behavior a later phase deleted, the ADR records the behavior **and** its
45
+ removal date, rather than being quietly rewritten as if the behavior never
46
+ existed.
47
+
48
+ ## Index
49
+
50
+ | ADR | Title | Source essay | Extracted |
51
+ |---|---|---|---|
52
+ | [0001](./0001-no-interpolation-attached-structured-context.md) | No interpolation — data reaches units as attached structured context | `src/workflows/exec/native-executor.ts` module header (data flow / reference scope / peer-review-R1 narrative) | P4 |
53
+ | [0002](./0002-unit-reuse-and-input-hash-scope.md) | Unit reuse and the input-hash scope | `src/workflows/exec/step-work.ts` (module purity contract; `computeUnitInputHash`'s doc comment) | P4 |
54
+ | [0003](./0003-child-env-allowlist-and-provenance.md) | The exec unit's child-environment allowlist and provenance | `src/workflows/exec/exec-unit.ts` (module header; the default-allowlist rationale; capture/overflow semantics; `childEnv`'s layering) | P4 |
55
+ | [0004](./0004-task-input-contract-and-flag-coercion.md) | The shared input contract, generalized from workflow params | `src/execution/input-contract.ts` module header | P4 |
56
+ | [0005](./0005-task-result-vocabulary-and-legacy-read-mapping.md) | The D8 result-vocabulary re-code and its legacy read mapping | `src/tasks/run/task-result.ts` + `src/tasks/run/task-history.ts` headers (the mapping's own one-line invariant stays in `task-history.ts`) | P4 |
57
+ | [0006](./0006-task-source-version-routing.md) | Task source version routing, from three generations to one | `src/tasks/source/parse-task-source.ts` routing-table header | P4 |
58
+ | [0007](./0007-workflow-composition-bounds.md) | Recursive child-workflow composition bounds | `src/workflows/resource-limits.ts` (the composition-depth and aggregate-plan-bytes section) | P4 |
59
+ | [0008](./0008-task-binding-normalization.md) | Task input binding normalization and re-binding across a composition | `src/workflows/freeze/task-bindings.ts` (module header; `rebindTaskInputBindings`'s doc comment) | P4 |
60
+ | [0009](./0009-child-run-publication-column-parity.md) | Child-run publication's hand-duplicated INSERT column list | New content — the R11/R-R3 disposition (`docs/plans/specs/p4-deletions-closeout.md` §8), not a moved essay | P4 |
61
+ | [0010](./0010-driver-protocol-cut.md) | The external driver protocol (`workflow brief`/`workflow report`/`--settle`) was cut | Stub pointing at `docs/architecture/specs/driver-protocol-keep-or-cut.md`, which stays the normative decision record | P4 |
62
+ | [0011](./0011-engine-run-loop-invariants.md) | The engine run loop's invariants: gate spine, frozen plan, run lease, process lifecycle | `src/workflows/exec/run-workflow.ts` module header | P4 |