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.
- package/CHANGELOG.md +41 -0
- package/README.md +10 -10
- package/ROADMAP.md +112 -0
- package/dist/commands/improve/improve-cli.js +33 -12
- package/dist/commands/sources/plugin-upgrade.js +46 -23
- package/dist/core/file-lock.js +10 -1
- package/dist/llm/client.js +33 -3
- package/dist/scripts/akm-migrate-node.js +5 -1
- package/dist/scripts/akm-migrate.js +5 -1
- package/docs/README.md +42 -41
- package/docs/agents/README.md +15 -0
- package/docs/agents/agent-install.md +14 -0
- package/docs/architecture/README.md +67 -0
- package/docs/architecture/adapters.md +272 -0
- package/docs/architecture/akm-architecture-decision-history.md +2055 -0
- package/docs/architecture/akm-core-principles.md +126 -0
- package/docs/architecture/architecture.md +579 -0
- package/docs/architecture/comparisons/claude-code-vs-akm-workflows-full.md +550 -0
- package/docs/architecture/decisions/0001-no-interpolation-attached-structured-context.md +72 -0
- package/docs/architecture/decisions/0002-unit-reuse-and-input-hash-scope.md +142 -0
- package/docs/architecture/decisions/0003-child-env-allowlist-and-provenance.md +189 -0
- package/docs/architecture/decisions/0004-task-input-contract-and-flag-coercion.md +97 -0
- package/docs/architecture/decisions/0005-task-result-vocabulary-and-legacy-read-mapping.md +108 -0
- package/docs/architecture/decisions/0006-task-source-version-routing.md +124 -0
- package/docs/architecture/decisions/0007-workflow-composition-bounds.md +83 -0
- package/docs/architecture/decisions/0008-task-binding-normalization.md +88 -0
- package/docs/architecture/decisions/0009-child-run-publication-column-parity.md +80 -0
- package/docs/architecture/decisions/0010-driver-protocol-cut.md +36 -0
- package/docs/architecture/decisions/0011-engine-run-loop-invariants.md +125 -0
- package/docs/architecture/decisions/README.md +62 -0
- package/docs/architecture/improvement.md +440 -0
- package/docs/architecture/internals/classification.md +154 -0
- package/docs/architecture/internals/fresh-host-rebuild-runbook.md +40 -0
- package/docs/architecture/internals/functional-contract-patterns.md +340 -0
- package/docs/architecture/internals/health-advisories.md +110 -0
- package/docs/architecture/internals/improve-workflow.md +713 -0
- package/docs/architecture/internals/indexing.md +498 -0
- package/docs/architecture/internals/registry-network-boundary.md +57 -0
- package/docs/architecture/internals/search.md +249 -0
- package/docs/architecture/internals/storage-locations.md +943 -0
- package/docs/architecture/persisted-data-compat.md +59 -0
- package/docs/architecture/reviews/env-secret-access.md +319 -0
- package/docs/architecture/runtime-boundary-design.md +153 -0
- package/docs/architecture/specs/0.9.0-close-out-plan.md +361 -0
- package/docs/architecture/specs/0.9.0-decisions.md +528 -0
- package/docs/architecture/specs/0.9.0-docs-code-drift-register.md +745 -0
- package/docs/architecture/specs/0.9.0-open-items-register.md +135 -0
- package/docs/architecture/specs/0.9.0-public-api-issue-backlog.md +740 -0
- package/docs/architecture/specs/0.9.0-release-surface-review.md +636 -0
- package/docs/architecture/specs/agent-command-engine-model-design.md +513 -0
- package/docs/architecture/specs/akm-0.9.0-bundle-adapter-spec.md +501 -0
- package/docs/architecture/specs/akm-0.9.0-ref-grammar-decision.md +291 -0
- package/docs/architecture/specs/akm-format-neutral-bundle-workspace-spec.md +2059 -0
- package/docs/architecture/specs/di-seams-plan.md +539 -0
- package/docs/architecture/specs/driver-protocol-keep-or-cut.md +275 -0
- package/docs/architecture/specs/fact-asset-type.md +142 -0
- package/docs/architecture/specs/improve-collapse-churn-detector-design.md +634 -0
- package/docs/architecture/specs/okf-support.md +148 -0
- package/docs/architecture/specs/ref.md +329 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/README.md +72 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/findings-consolidated.md +152 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-alternatives.md +352 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-bunqueue.md +300 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-openworkflow.md +402 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-1.md +758 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-2.md +645 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-3.md +602 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-adversarial.md +27 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-architecture.md +27 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-coverage.md +74 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-security.md +15 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r2-adversarial.md +132 -0
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r2-verification.md +147 -0
- package/docs/architecture/specs/stash-conventions-code-spec.md +275 -0
- package/docs/architecture/specs/stash-organization-conventions.md +377 -0
- package/docs/architecture/specs/task-workflow-format-unification.md +721 -0
- package/docs/architecture/specs/workflow-engine-buy-vs-build.md +231 -0
- package/docs/architecture/specs/workflow-format-unification.md +437 -0
- package/docs/architecture/testing/manual-testing-checklist.md +3420 -0
- package/docs/architecture/testing/okf-v0.2-conformance-runbook.md +924 -0
- package/docs/architecture/testing/task-workflow-manual-tests.md +1817 -0
- package/docs/architecture/testing/testing-workflow.md +446 -0
- package/docs/architecture/workflow-engine.md +274 -0
- package/docs/guides/README.md +33 -0
- package/docs/guides/author-bundles.md +310 -0
- package/docs/guides/author-workflows.md +520 -0
- package/docs/guides/bundles.md +321 -0
- package/docs/guides/capture-knowledge.md +136 -0
- package/docs/guides/claude-code-vs-akm-workflows.md +30 -0
- package/docs/guides/concepts.md +93 -0
- package/docs/guides/discover-and-load.md +158 -0
- package/docs/guides/getting-started.md +240 -0
- package/docs/guides/improve-the-library.md +246 -0
- package/docs/guides/knowledge-management.md +18 -0
- package/docs/guides/recipes/headless-install.md +97 -0
- package/docs/guides/recipes/website-source.md +28 -0
- package/docs/guides/run-workflows.md +279 -0
- package/docs/guides/scheduling.md +123 -0
- package/docs/guides/use-with-any-agent.md +129 -0
- package/docs/guides/wikis.md +81 -0
- package/docs/maintainers/README.md +19 -0
- package/docs/maintainers/curate-workmap.md +336 -0
- package/docs/maintainers/eval.md +3 -0
- package/docs/maintainers/local-development.md +108 -0
- package/docs/maintainers/release-checklist.md +103 -0
- package/docs/migration/README.md +2 -2
- package/docs/migration/release-notes/0.0.13.md +1 -1
- package/docs/migration/release-notes/0.1.0.md +1 -1
- package/docs/migration/release-notes/0.2.0.md +1 -1
- package/docs/migration/release-notes/0.3.0.md +1 -1
- package/docs/migration/release-notes/0.5.0.md +1 -1
- package/docs/migration/release-notes/0.6.0.md +1 -1
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.3.md +2 -2
- package/docs/migration/release-notes/0.7.4.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +1 -1
- package/docs/migration/release-notes/0.8.0.md +1 -1
- package/docs/migration/release-notes/0.9.0.md +1 -1
- package/docs/migration/release-notes/0.9.14.md +1 -1
- package/docs/migration/release-notes/0.9.15.md +1 -1
- package/docs/migration/release-notes/0.9.16.md +1 -1
- package/docs/migration/release-notes/0.9.17.md +1 -1
- package/docs/migration/release-notes/0.9.19.md +1 -1
- package/docs/migration/release-notes/0.9.2.md +1 -1
- package/docs/migration/release-notes/README.md +27 -31
- package/docs/migration/v0.5-to-v0.6.md +759 -0
- package/docs/migration/v0.8-to-v0.9.md +1 -1
- package/docs/plans/0.9.2-agent-command-workflow-plan.md +423 -0
- package/docs/posts/README.md +30 -0
- package/docs/posts/akm-command-tour.md +495 -0
- package/docs/posts/akm-logo-sized.webp +0 -0
- package/docs/posts/belief-aware-memory-12.md +139 -0
- package/docs/posts/config-v2-lessons-16.md +191 -0
- package/docs/posts/improve-loop-autonomous-10.md +225 -0
- package/docs/posts/improve-pipeline-debugging-0803.md +179 -0
- package/docs/posts/intro-part-01.md +76 -0
- package/docs/posts/intro-part-02.md +221 -0
- package/docs/posts/intro-part-03.md +190 -0
- package/docs/posts/intro-part-04.md +221 -0
- package/docs/posts/llm-mode-reflect-13.md +166 -0
- package/docs/posts/openpalm-built-on-akm.md +86 -0
- package/docs/posts/progressive-disclosure-06.md +129 -0
- package/docs/posts/proposal-safety-net-15.md +112 -0
- package/docs/posts/release-0.5.0.md +247 -0
- package/docs/posts/release-0.6.0.md +117 -0
- package/docs/posts/release-0.7.0.md +159 -0
- package/docs/posts/release-0.8.0.md +332 -0
- package/docs/posts/scattered-skills-05.md +100 -0
- package/docs/posts/task-assets-persistent-workflows-11.md +222 -0
- package/docs/posts/team-skills-07.md +198 -0
- package/docs/posts/wiki-agent-knowledge-08.md +223 -0
- package/docs/posts/workflows-vaults-09.md +321 -0
- package/docs/product-surface.md +54 -0
- package/docs/reference/README.md +1 -2
- package/docs/reference/asset-types.md +168 -0
- package/docs/reference/cli.md +5 -6
- package/docs/reference/env-and-secrets.md +126 -0
- package/docs/reference/memory.md +124 -0
- package/docs/reference/refs.md +156 -0
- package/docs/reference/registry.md +489 -0
- package/docs/reference/website-sources.md +141 -0
- 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 |
|