UNPKG

akm-cli

Version:

akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur

276 lines (221 loc) • 12.9 kB
# The External Driver Protocol: Keep, Cut, or Cheapen Status: RESOLVED (2026-08-27) — Option B, cut. Executed ahead of this record by the 0.9.2 reconstruction. See §10 for the decision and its verification evidence. Date: 2026-08-02 Baseline: `origin/main` @ `17a0dca` Subject: `akm workflow brief` / `akm workflow report` (+ `report --settle`) Related: [`workflow-engine-buy-vs-build.md`](./workflow-engine-buy-vs-build.md) (which flagged this surface as the subsystem's largest removable block), [`task-workflow-format-unification.md`](./task-workflow-format-unification.md) --- ## 1. The question The driver protocol is 2,690 LOC of source — **31% of the workflow engine** — plus a test surface of comparable size. It is Experimental, opt-in behind `experimental.workflowEngine`, and has exactly three non-test callers (all CLI handlers in `src/commands/workflow-cli.ts`). It is a leaf. Is the capability worth its cost, now that `akm workflow run` is the canonical, Stable, ungated orchestrator? ## 2. What it actually is `brief` (713 LOC) is **read-only**: resolve a run, project the active step's work-list, emit JSON. Most of its length is the published contract — seven exported interfaces external harnesses code against. `report` (1,977 LOC) is the **mutating** half, and the weight is not duplicated engine logic. Its header states a "no duplicated semantics (the cardinal rule)" and it imports 19 shared functions from `step-work.ts` — the same `computeStepWorkList`, `reduceStepOutcomes`, `finalizeExecutedStep` the native executor calls. `driver-parity.test.ts` asserts the R4 contract: an engine-driven run and a driver-driven run of the same frozen plan produce **byte-identical unit graphs**. The lines are the cost of exposing the engine's step transitions to an external actor safely: | Concern | ~LOC | |---|---| | Transactional phases (load → resolve → write → finalize) | ~605 | | Step finalization (reducers, gates, spine advance) | ~491 | | Claim + hash-divergence guards | ~212 | | Helpers incl. returned-text redaction | ~216 | | The `settle` verb (route-only/empty steps) | ~168 | | Published contract interfaces | ~106 | It must assume a hostile or buggy caller, tolerate two drivers racing plus a possibly-live engine lease, treat a re-report with a matching hash as idempotent and a differing hash as replay divergence, enforce budgets, and redact text the driver hands back. The engine skips nearly all of this because it trusts its own call stack. **This is a fixed floor, not bloat.** Slimming it is not the lever; scoping it is. ## 3. Two corrections to the case for keeping Both were assumptions I had asserted and have since verified false or overstated. They materially narrow the keep-argument, so they lead. **(a) "Harness coverage collapses" — largely false.** Native dispatch is not limited to claude/opencode. `HARNESS_ID_TABLE` (`src/integrations/harnesses/ids.ts:34-45`) lists **ten** harnesses, all `agentDispatch: true`: opencode, claude, opencode-sdk, codex, copilot, pi, gemini, aider, amazonq, openhands. **(b) The protocol's own documented users are, with one exception, natively dispatchable.** `docs/reference/workflows.md:418` names them: *"any agent session — Claude Code, opencode, Codex, or a human at a shell."* Three of those four are in the table above. ## 4. What genuinely survives as unique value Not harness neutrality — **in-session execution**. `run` with `defaults.engine: claude` spawns a fresh `claude -p` subprocess. The driver protocol lets the session you are *already in* do the work. Even for a natively-dispatchable harness these differ: - **Tooling.** A spawned subprocess does not inherit the calling session's MCP servers or loaded tools. Work requiring them can only run in-session. - **Engine-free operation.** `run` hard-fails without a configured engine (`freeze.ts:54`, `INVALID_CONFIG_FILE`, exit 78). In a bare container or CI image where the operator *is* an agent but no agent CLI is configured, the driver protocol works and `run` does not. - **Harnesses outside the ten** — Cursor, Zed, Windsurf — and a human at a shell. Counter-consideration, stated fairly: fresh context per unit is often a *feature* (the subagent argument), so "nested agent" is not by itself a cost. ## 5. The cost of keeping - **~2,690 LOC of the highest-risk code in the subsystem.** Untrusted mutation, claim races, lease contention, crash-mid-finalize. - **It constrains engine evolution.** `report` is built on the `WorkflowRunUnitRow` shape via `withWorkflowRunsRepo` (no raw SQL — the coupling is through the repository layer, which is the good case, but it is still a second consumer). The R4 parity contract additionally requires `step-work.ts` to stay provably identical across two callers. The companion spec's IR v4 / hashVersion 5 work must keep both working. - **A whole stability tier**: the `experimental.workflowEngine` gate, its refusal envelopes, `task doctor` reporting, and doc surface across five files. ## 6. The cost of cutting — measured, not estimated **Fully deletable test files (4,013 LOC):** `brief.test.ts` (850), `report.test.ts` (1,844), `conformance/driver-parity.test.ts` (1,319). **Not deletable — rewrite required.** Five suites use the protocol as a *test harness* for unrelated invariants and would need porting to `run`: | Suite | LOC | Protocol call-sites | |---|---|---| | `chaos.test.ts` | 1,175 | 11 | | `step-work.test.ts` | 1,127 | 4 | | `run-lease.test.ts` | 841 | 5 | | `frozen-plan.test.ts` | 342 | 5 | | `params-validation.test.ts` | 159 | 5 | That is ~3,644 LOC of coverage that must be **preserved through a rewrite**, not deleted — and `chaos.test.ts` in particular uses `report` to inject precisely the interleavings that make crash-safety testable from outside the engine. Losing the ability to drive a run step-by-step from a test is a real, under-appreciated cost of cutting. **Documentation.** `docs/guides/claude-code-vs-akm-workflows.md:288` calls the protocol *"the sharpest point of contact between the two systems in this whole comparison."* Cutting means rewriting a positioning guide, not just deleting code. **It is the last external-driving mechanism.** `start`/`next`/`complete` — the cheap manual-stepping loop that served the same need — were removed when `run` became canonical. This is not "cut the expensive one, keep the cheap one." Cut it and native dispatch is the only execution model, permanently. ## 7. The three options ### Option A — Keep as-is Accept ~2,690 LOC and the R4 constraint as the price of in-session execution and test-harness driving. Reasonable if akm intends execution neutrality as a product property. *Cost:* the engine's hardest surface stays, and every IR change carries a second consumer. ### Option B — Cut both verbs Delete `brief`, `report`, `--settle`, the experimental gate, and 4,013 LOC of tests; port ~3,644 LOC of chaos/lease/frozen-plan coverage to `run`; rewrite the positioning guide; accept that only the ten natively-dispatchable harnesses can execute workflows and that a configured engine becomes mandatory. *Gain:* the largest single simplification available to the subsystem — ~4× more code than the best third-party adoption would have removed, with no new dependency. *Risk:* irreversible in practice, and it forecloses in-session execution. ### Option C — Cheapen: single-driver mode *(recommended for evaluation)* Keep the capability, shed the concurrency machinery it no longer needs. When the protocol was designed, `start`/`next`/`complete` still existed and multiple actors could touch a run. Today the **run lease already arbitrates exclusivity** — `run` takes it, and `report` already refuses while a live engine lease is held. If the protocol declares *one driver at a time, enforced by a driver lease*, then per-unit claim ownership, contention resolution, and the contended-finalize paths become redundant with a guard already in place. What must stay regardless: **hash-divergence detection** (the replay contract), budget enforcement, redaction of returned text, and the shared `step-work.ts` semantics that make R4 hold. What plausibly goes: `assertClaimHeldByOrFree`, the contended-finalize result paths, and parts of the barrier decomposition — the ~212-LOC guard block plus a share of the phase machinery. **I am not putting a number on the saving without a design pass**; the honest claim is that the machinery targeted was built for a concurrency model the current architecture no longer permits. *Gain:* keeps in-session execution and the chaos-test harness; removes the hardest correctness surface. *Cost:* one design pass; concurrent drivers become an explicit refusal rather than a resolved race. ## 8. Recommendation **Evaluate C first, decide between A and B only if C fails.** The strongest argument for cutting was never code size — it was that the protocol carries concurrency machinery for a world that no longer exists. If C confirms that, akm keeps a genuine capability at a fraction of the maintenance cost, and the R4 constraint is the only thing left to weigh. If C does not hold up, the tiebreaker is a product question rather than an engineering one: **is executing akm workflows from inside an arbitrary agent session a property akm wants to guarantee?** - **Yes → A.** Then stop treating ~2,690 LOC as a liability; it is close to the floor for a safe mutating protocol, and §2 shows the weight is structural. - **No → B.** Native dispatch covers ten harnesses; take the largest simplification available and budget the test-port work honestly. What should *not* happen is the status quo by default: an Experimental, opt-in surface holding 31% of the engine and a parity constraint on every future IR change, with no decision recorded either way. ## 9. Sequencing note If B is chosen, it should land **before** the task/workflow unification work, not after. That change is already an IR v4 + hashVersion 5 + schema migration; doing it with one consumer of `workflow_run_units` and no R4 parity obligation is materially cheaper than doing it with two. If A or C is chosen, the unification spec's existing accounting already covers the second consumer and needs no revision. This condition is satisfied: B landed before the task/workflow unification work. `workflow_run_units` had exactly one consumer (the native executor) throughout P1–P3, and P3a's `irVersion` 5 + `hashVersion` 6 bump (the "schema migration" this note anticipated) was done against that single-consumer reality — there was never a second R4 parity obligation to carry through it. ## 10. Decision and evidence (2026-08-27) **Decision: Option B — cut both verbs.** `brief`, `report`, `--settle`, and the `experimental.workflowEngine` gate are gone. This was not reached by this document's own §8 process (evaluate C, fall back to A/B) — the 0.9.2 task/workflow reconstruction deleted the subject before this record was ever flipped from PROPOSAL. §9's sequencing note turned out to hold anyway, by construction rather than by decision: the reconstruction was never built with a driver-protocol consumer in the first place, so there was nothing to sequence around. This record is therefore ratified **after the fact** — ticking a box the code had already made true — not evaluated in the forward-looking sense §7/§8 describe. A future reader should not infer that Option C was tried and found wanting; it was never evaluated, because the code was gone before the question was revisited. **Verification.** All three greps return zero results against the current tree, confirming no trace of the protocol, its experimental gate, or its CLI verbs survives: ``` $ rg -n 'brief\.ts|report\.ts' src/workflows/exec/ (no output) $ rg -n 'experimental\.workflowEngine|workflowEngine' src/ (no output) $ rg -n 'workflow (brief|report)|--settle' src/commands/ (no output) ``` **Cross-references.** - [`workflow-engine-buy-vs-build.md`](./workflow-engine-buy-vs-build.md) §7 (Recommendation) — the build verdict this decision completes. That document recommended building AKM's own native engine rather than adopting a third-party one; this record settles the one piece of the native engine's own surface (the external driver protocol) that was still an open question at PROPOSAL status. - `docs/plans/0.9.2-architecture-deletion-audit.md`'s "Deleted architecture" table carries the corresponding row: *External driver protocol (`workflow brief` / `workflow report` / `--settle`) — Removed; decision record RESOLVED (Option B).* **§9's sequencing condition:** satisfied, as recorded in §9 above — B landed before the task/workflow unification work, so `workflow_run_units` had one consumer throughout P3a's `irVersion` 5 + `hashVersion` 6 bump.