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

28 lines (14 loc) • 8.16 kB
# v8 spec architecture review major | task path has no freeze stage today, and the spec never says whether it gets one | `runPromptTask`/`runCommandTask` in `src/tasks/runner.ts` resolve engine/model/cascade against *live* `loadConfig()` synchronously inline (no compile/resolve/freeze staging, no persisted plan), while §5.4's two-stage freeze is specified purely in workflow terms (`compileResolveFreezeWorkflow`) — the spec asserts cascade.ts is "consumed at freeze (workflows) and dispatch (tasks)" but never states whether task dispatch runs compile→resolve→freeze in-memory-only or calls cascade.ts directly, skipping the asset-loader injection seam that persona/`uses:` resolution depends on. | spec §4.3, §5.4; `src/tasks/runner.ts:461-567`; `src/workflows/ir/freeze.ts:43` major | AgentUnitExecutor seam glosses over two structurally different dispatch surfaces | Workflow dispatch already flows through `UnitDispatchRequest` (frozen `FrozenEngineSnapshot`, `IrInvocation`, `env: Record<string,string>`, journaled via `workflow_run_units`) in `src/workflows/exec/unit-dispatch.ts`, but the task-prompt path (`runPromptTask`) builds a `RunnerSpec` from live config via `resolveEngine`/`resolveLlmEngineUse` and calls `executeRunner()` directly, then writes to `task_history`/`logs.db` — a completely different type and persistence shape; "one AgentUnitExecutor, two callers" needs an adapter layer the spec doesn't name, not just a shared strategy interface. | `src/workflows/exec/unit-dispatch.ts:9-31`; `src/tasks/runner.ts:479-539` major | ShellUnitExecutor claim ignores that the task runner's shell path is a distinct, already-hardened subsystem | `runCommandTask` already owns process-group kill-ladders, `AKM_EVENT_SOURCE` stamping, `resolveNestedAkmCommand`, and its own log/history sinks (`src/tasks/runner.ts:250-341`); folding this into one `ShellUnitExecutor` shared with the orchestrator's `Bun.spawn()` path is plausible but the spec treats it as "just" a strategy swap (§5.1) when it actually requires reconciling two independent timeout/kill/logging contracts — not costed anywhere in §9. | `src/tasks/runner.ts:273-296`; spec §5.1, §9 major | cascade module's two consumption times are not obviously bitemporally safe for `uses: tasks/<ref>` composition | A workflow step referencing `tasks/<id>` resolves that task's cascade layers at the workflow's freeze time (baked into the frozen plan), but the *same* task run standalone via `akm task run <id>` re-resolves cascade against config as of the actual run — the spec never states which config snapshot a step's `uses: tasks/<ref>` composition should read (config at workflow-freeze time vs. config at task-file's own authoring), so two runs of "the same" referenced task can diverge without either being wrong per the spec's own text. | spec §4 layers table, §5.4; `src/workflows/ir/freeze.ts:41` ("only source-to-runtime boundary") major | persona snapshot / "show layer" reuse is directionally sound but the show layer isn't a resolver today, it's a CLI-output formatter | The provenance ceiling the spec cites is real (`src/commands/read/show.ts:433-445`, `isPrimaryStash` gate deleting `toolPolicy` for non-writable stashes) but it lives inside `akm show`'s response-shaping code, not a reusable resolution function; extracting a `{systemPrompt, toolPolicy, model, …}` snapshot for freeze means carving a pure resolver out of a command handler that currently also does display formatting — a real but unscoped refactor, understated as "consumes the show layer's verdict." | `src/commands/read/show.ts:433-445`; spec §4 item 2 minor | IrInvocation as currently typed (`engine: string` required, `model`, `timeoutMs`, `llm?`) is agent/llm-shaped only — extending it to a `{kind:"agent",…}|{kind:"shell",…}` discriminated union is the right shape given `IrRuntimeKind` already exists as `"llm"\|"agent"\|"sdk"`, but the spec should say explicitly whether `IrRuntimeKind` gets a `"shell"` member or whether shell units carry no `runner` field at all — `decodeWorkflowPlanV3`'s `validateInvocation`/`assertUnitEngineCompatibility` (`src/workflows/ir/schema.ts:481-527`) hard-requires `invocation.engine` on every unit today, so the decoder rewrite is nontrivial, not a narrow "instructions become optional" patch. | `src/workflows/ir/schema.ts:78-84, 383-452, 517-527`; spec §5.2 minor | hashVersion 5 preimage table is a clean, additive extension of the existing pattern | `step-work.ts`'s `computeStepWorkList` already documents its hash preimage exhaustively (hashVersion 4, lines 375-392) and explicitly walks through why each field is in/out; the v5 additions (target kind + ref + content hash, shell text, env literal values) fit the same discipline and the existing code's own doc comments make clear this is a well-understood seam to extend, not a risky one. | `src/workflows/exec/step-work.ts:333-392`; spec §5.3 minor | "one cascade module" name is right, but engine/model resolution logic that would feed it is currently duplicated three ways with subtly different fallback rules (`exactModel`/`effectiveTimeout` in freeze.ts, `resolveEngine`+inline overrides in runner.ts, `resolveLlmEngineUse` in engine-resolution.ts) — each has bespoke opencode-sdk-fallback handling; cascade.ts absorbing all three without losing the sdk-fallback edge cases (`freeze.ts:174-186, 199-208`) is the real engineering cost, understated by calling it "one cascade module." | `src/workflows/ir/freeze.ts:163-209`; `src/tasks/runner.ts:479-514`; spec §4 item 3 minor | env literal/ref provenance split matches an idiom the codebase already uses elsewhere (names-only hashing, resolved-value redaction sets) | `step-work.ts` already hashes `env: template.env ?? null` as ref *names* only and documents exactly the "hashing a resolved secret would leak it" rationale the spec restates in §5.5 — this is not a new pattern, it's naming an existing invariant, which is the correct minimal move. | `src/workflows/exec/step-work.ts:353-358`; spec §5.5 minor | `type: task` adapter-ordering claim is correct and lint-checkable today | `BUILTIN_ADAPTERS` in `src/core/adapter/adapters/index.ts:73-90` does put `akmWorkflowAdapter` before `akmTaskAdapter`, both before the loose `akmAdapter`, confirming the spec's M8 claim that "workflow adapter is ordered first and claims `.md` files" is grounded, not asserted. | `src/core/adapter/adapters/index.ts:73-90`; spec §3 minor | GHA-alignment naming (`uses:`/`run:`/`with:`) is clean and the mutual-exclusion rule maps directly onto a single discriminated `Target` union — no simpler alternative stands out; the one soft spot is overloading `uses: tasks/<n>` to mean "compose this task's fields into me" while `uses: workflows/<r>` means "invoke and return," two different composition semantics under one keyword, which the spec acknowledges (no nesting rule) but doesn't name as an asymmetry worth flagging to authors beyond a doc note. | spec §2.2, §5.1 minor | over-engineered: threading `llm` overrides validation (`assertUnitEngineCompatibility`, `validateLlmOverrides`) through a *shell* invocation union member that never carries them is dead branch surface — the discriminated union (§5.1 `{kind:"shell",...}`) should structurally exclude `llm`/`engine` fields rather than relying on decoder-time cross-checks the way today's single-shape `IrInvocation` must; the spec doesn't say the shell variant drops the whole `invocation` wrapper, so decoder complexity may grow rather than shrink under v4. | `src/workflows/ir/schema.ts:481-498, 517-527`; spec §5.2 minor | under-specified: scheduler-id mapping (`/` → `--` with collision lint) and per-engine capability-declaration shape are both explicitly deferred to the implementer (§11 "Open items") — reasonable to defer, but subdir task ids interacting with `validateTaskId`'s current rejection of `/` (cited in §9) means the migration step and the new id grammar must land atomically, which isn't sequenced anywhere in §8's migration steps. | spec §8, §11; `src/tasks/task-id.ts` (not read in depth but referenced by spec)