akm docs

0.9.0 release surface review — v2 (reconciled action list)

Supersedes v1 of this document. v1 was an end-to-end critical sweep of the user-facing surface (41 top-level commands, ~120 subcommands, ~230 flags, the Zod config schema, env vars, output contracts). v2 reconciles it with three inputs:

  1. Owner corrections — --type is free-form by design; the global --format set must work on all commands with no bespoke shims; migration code should live outside the main tool; test-only env vars should not ship; akm bundle should be removed as duplicative; the show view modes should be replaced by #fragment section addressing.
  2. The improve-autonomy review — do not gate the whole akm improve feature; gate autonomy (direct mutation, auto-promotion) behind an explicit opt-in, keep the feature and its schedules visibly alive. (Unattended push was in this list originally; D8 leaves it ungated — see the outcome note in C6.)
  3. A verification pass (8 parallel read-only audits of the tree) that confirmed, refuted, or sharpened each claim. Two v1 claims were wrong and are corrected below.

Corrections to v1:

Grouped by action. Ordering within groups is by cost-of-deferral.


A. Fix — defects, before anything else

A1. akm search --no-project-context is a no-op, and there is no fallback

search-cli.ts:65 declares an arg literally named no-project-context; the parser's --no-* negation makes passing it identical to omitting it. With the env var confirmed dead (see corrections), the boost cannot be disabled at all.

Action: declare project-context (boolean, default true) so --no-project-context works via native negation — the same pattern sync --push/--no-push already uses. Remove the dead AKM_DISABLE_PROJECT_CONTEXT mention from the help text (or implement it deliberately; pick one). Replace the current test (which only asserts the flag doesn't touch process.env) with one that asserts the boost is actually disabled.

Status: superseded by R-039 / X-RULING-9 — semanticSearchMode now defaults to "off" (src/core/config/schema/*.ts: .default("off")), so a bare/headless install no longer attempts semantic search or emits this warning at all; the interactive akm setup wizard still pre-selects it on, with the download warning moved ahead of the prompt. The finding below is kept as the evidence record for the default that motivated the ruling.

Verified end-to-end at the time: semanticSearchMode defaulted to auto (config.ts:108); with no embedding configured the implicit local-transformers path fails (optional dependency, or offline), verifyIndexState writes blocked (indexer.ts:1699-1715), and every subsequent search for 24h-cycles emits "Semantic search is currently blocked … until the semantic backend is healthy again" (db-search.ts:222-226) — describing a fault when the state is "not configured".

Action: split the blocked branch at db-search.ts:196-226 on the same predicate the pending branch already uses (!config.embedding?.endpoint || !config.embedding?.model, line 205):

A3. --type help text: document free-form, stop implying an enum

Types are an open set by design (see corrections). But the help enum on search/curate lists 10 of the 13 placement types (omits task, session, fact), omits instruction, and omits the entire adapter-defined space; cli-hints-full.md:21 repeats the stale list; completions only complete placement types.

Action: rewrite the --type description on search and curate (and cli-hints-full.md): "Asset type filter. Built-in types: skill, command, agent, knowledge, workflow, script, memory, env, secret, lesson, task, session, fact, instruction — or any adapter-defined type (e.g. website, wiki-source, a wiki pageKind). Matching is exact; an unknown type returns no hits. Default: any." No validation added — a typo returning zero hits is the documented contract of a free-form filter.


B. One output contract — every global format on every command

Owner direction: json|jsonl|yaml|text|md|html must all work on all commands; no bespoke shims. The verification pass mapped the full gap: 37 commands silently emit JSON under --format text; md silently emits JSON everywhere except health; html throws everywhere except health; jsonl is per-item only for search; ~10 surfaces bypass output() entirely.

B1. Registry-driven renderers with generic fallbacks

The infrastructure exists — the shapes and text registries already follow the exact pattern (src/output/shapes.ts:78-99, src/output/text.ts:104-133).

Action:

B2. Remove akm graph export --format

Verified clean: the local flag already leaks into the global mode (one token, two parsers), the two payload formats map 1:1 to the global values with the same defaults, and payload (--out file) vs envelope (stdout) never collide.

Action: delete the local arg; the export handler reads getOutputMode().format; keep a UsageError for payload formats other than json|jsonl. This also removes the last subcommand-declared format: arg — which is what forced the citty workarounds in sync and env unset (sources-cli.ts:214-256, env-cli.ts:470-482); delete those too.

B3. Migrate raw-stdout bypasses onto output()

config path (bare), config validate success strings, and migrate status|apply (console.log(JSON.stringify(plan)), unshaped, unstamped) bypass the envelope entirely; env path/secret path print a raw scalar and ignore --format json (breaking | jq pipelines).

Action: give each a passthrough shape and route through output(). Path commands keep raw-scalar output in text mode; gain {path} envelopes under json/yaml/jsonl. (Payload values of env/secret remain excluded from structured output — that security contract is untouched.)

B4. Declare the format-exempt set

Some surfaces are exempt by nature: completions (output is a bash program), env run/secret run/agent child stdio, the interactive setup wizard branch, and document payloads (workflow template, hints, help migrate).

Action: a FORMAT_EXEMPT_COMMANDS set in src/cli/shared.ts that (a) emits a stderr note when a non-default --format is passed to an exempt command, (b) generates the exemption table in docs/reference/cli.md, and (c) powers a test asserting every command is either exempt or 6-format capable.

B5. Cleanup that falls out

Widen OutputConfigSchema.format to the full six values (users currently cannot persist jsonl|md|html); delete the vestigial distill/reflect shape+text registrations (no output() call sites exist); delete the doc-only local format: arg declarations on search/curate/show with their stale enums; fix the stale "fall back to YAML" comments.


C. Remove — deletions while 0.9.x still permits them

C1. Remove akm bundle (list/show/items)

Owner decision, and verification confirmed it is cheap: undocumented in docs/reference/cli.md, absent from STABILITY.md, zero consumers outside its own CLI file and one test.

Action: delete src/commands/bundle/ and the three passthrough shapes. Preserve the unique data additively on akm list (a passthrough shape, so all additive): default marker + top-level defaultBundle, the desired source descriptor (kind/locator/maxPages), registryId, the full lock block (resolvedRevision/integrity/manifestDigest/adapterIds/ installedAt), components, and per-bundle itemCount/byType (one getAllEntries pass grouped by bundleId). Per-bundle item enumeration lands in C2. This also retires the pre-bundle --kind local|managed|remote vocabulary (rename values to the bundle model while touching it).

C2. Replace the <type>: browse grammar with conceptId-prefix browsing

Status: SHIPPED (D4, ruled Q-02). parseRefPrefixQuery now accepts bundle//-qualified and bare conceptId-prefix slash forms; the colon grammar has no code path. See the drift register for the verified-live evidence.

Three confirmed defects in the current grammar: it resurrects the singular type: spelling the release just removed; it validates against placementTypes() so adapter-defined types can never be enumerated; and it matches namePrefix against name-space while every displayed ref is in plural conceptId-space — copying a displayed ref prefix (memories/projecta/) into a query silently degrades to keyword FTS.

Action: replace parseRefPrefixQuery with conceptId-prefix enumeration: akm search "memories/", akm search "memories/projecta/", and bundle-qualified akm search "team//" / "team//skills/". This matches the canonical bundle//conceptId grammar, covers adapter types (their conceptIds are enumerable even when their type strings are foreign), and gives C1 its bundle items replacement (akm search "team//" = all items in bundle team). Also lift the empty-query rejection when --source <bundle> names a configured source, and document named --source in the flag table (today it is prose-only).

C3. Replace akm show <ref> toc|section|lines|frontmatter|full with #fragment

Owner decision. One spec decision is required first, because the verification pass found the grammar is stable-on-paper, rejected-in-practice: STABILITY.md declares [bundle//]conceptId[#fragment] Stable, yet parseRefInput rejects every fragment at every input boundary (resolve-ref.ts:263-269), and the spec defines the fragment as an adapter-owned export selector (spec §11.3, ref.md:24) for the unimplemented Tier-B exports system — not a heading anchor.

Action:

  1. Write the spec decision: for markdown-document assets the fragment resolves to a heading (GitHub-style slug match over parseMarkdownToc headings, falling back to case-insensitive raw heading text — today's extractSection semantics). Fragments remain adapter-owned selectors elsewhere; the two uses coexist per the spec's own "adapter-owned and opaque to the core" language, but written down. Amend ref.md, spec §11.3, and STABILITY.md.
  2. Implement: drop the fragment rejection on the show path (add fragment to AssetRef or re-parse via parseBundleRef in show); akm show knowledge/guide#authentication renders that section. An unmatched fragment errors with the list of available fragment slugs — this absorbs toc's discovery role.
  3. Delete the whole view-mode apparatus: normalizeShowArgv + the process.argv mutation in cli.ts, SHOW_VIEW_MODES, the hidden --akmView/--akmHeading/--akmStart/--akmEnd flags, the KnowledgeView threading, the renderer switch, and the view-only markdown helpers (extractLineRange, extractFrontmatterOnly, formatToc; parseMarkdownToc stays — it has non-view consumers). full is the no-fragment default. lines is dropped (every response carries path; callers can slice the file). frontmatter is dropped; if a raw-YAML projection proves needed it returns later as a --shape value, which STABILITY.md already designates as the projection axis.
  4. CHANGELOG migration note (the positional view grammar was a documented Stable spelling; 0.9.x may break it, but it must be called out).

C3b. Remove akm mv

Outcome: NOT ADOPTED. akm mv ships in 0.9.0. The findings below stand — they are what classifies the command Experimental and outside the stability contract, and why the documented rename procedure is a plain filesystem move plus akm index and akm lint. The removal action list and the transaction-kind cleanup it prescribes are not being carried out. See D3 in the decision record. The rest of this section is the review as written.

Reverses this document's earlier "gate it behind experimental.mv" recommendation. A dedicated review recommended removal; every load-bearing claim in it verified against the tree, and the decisive one is that this is a correctness inversion, not a rough edge — an experimental flag would ship silent content corruption to whoever opts in.

Verified findings:

Action:

  1. Delete the command, src/commands/mv-cli.ts, its tests, its hints, and the mv event type.
  2. Delete the mv transaction kind and the indexer's hook for it. index-written-assets.ts:78-81 hardcodes recoverTxnsForRoot(stashDir, j => j.kind === "mv") on every write-path index refresh, and requireKind throws on an unregistered kind (fs-txn.ts:122-125). Deleting mv-cli.ts removes the registerTxnKind("mv", …) side effect while leaving the caller — so a single leftover journal would hard-fail every subsequent index refresh. Old journals are being ignored by decision, so: remove the hook, and make recoverTxnsForRoot sweep unknown kinds instead of throwing.
  3. Document renames as delete + create: move the file, update intentional refs, akm index, akm lint. The destination gets a fresh identity and fresh learned state. Cross-bundle movement is copy/import + delete, never identity-preserving.
  4. Amend spec §11.2, which currently says a rename "MUST use an explicit state-rekey transaction" — that was a product choice made in the same refactor, not an independent constraint (see D3 in the decision record).
  5. If real usage later shows that preserving learned state through renames matters, reintroduce a narrow same-bundle akm rename built the right way: resolve through the index, adapter-owned placement, qualified refs only, rewrite only anchored bundle//conceptId forms, one old→new state mapping, and rebuild derived index data rather than preserving row IDs.

C4. Remove akm config enable|disable

A hardcoded alias for one vendor registry (skills.sh), currently listed Stable. Action: remove; the replacement is akm registry add https://skills.sh / akm registry remove skills.sh.

C5. Remove the undocumented akm init --stashDir/--stash-dir aliases

They point at a config key 0.9.0 hard-rejects. Action: delete (stash-cli.ts:73-75).

C6. Move upgrade --skip-checksum off the flag surface

A tab-completable way to disable integrity verification on a self-updating binary. Action: remove the flag; keep the recovery hatch as AKM_UPGRADE_SKIP_CHECKSUM=1, documented as internal.

C7. Split akm add --allow-insecure

One flag authorizes two unrelated risks (plain-HTTP transport; installing bundles whose env keys — LD_PRELOAD, PATH — can hijack execution via akm env run). Action: --allow-insecure-transport and --allow-dangerous-env-keys, each gating only its own check.

C8. Scrub internal tracker IDs and retired-grammar examples from help text

"F-6:" ×8, "(F-3 / #384)", "(F-4 / #385)", "(#485)", "(#22)", "(v1 spec §4.2)" in flag descriptions; skill:akm-dream, memory:projectA/old-note, knowledge:auth-flow, npm:…//script:deploy.sh examples in ~8 help strings that use the removed type:name grammar; the stashDir mentions in init --set-default, setup --dir, config get help. Action: one help-text sweep; goldens update.

C9. Env-var hygiene (the shippable subset of "eliminate test-only vars")

Verified disposition — full elimination is not achievable (SIGKILL crash-recovery tests structurally require a real child process; the deterministic embedder exists for cross-version out-of-process benchmarks), but the surface shrinks a lot:


D. Extract — migration machinery out of the core

D1. Move apply/backup/restore into a separately published akm-migrate

Owner direction, and the audit sized it: ~8,820 LOC of production migration code + ~7,130 LOC of tests/fixtures ship inside every binary today, pinned to a one-time 0.8→0.9 cutover, spread across five surfaces (akm migrate, akm backup — which hard-rejects any --for value except the literal 0.9.0 — akm config migrate, the akm-migrate-storage bin, plus startup gates).

Action (target end-state):


E. Improve — gate autonomy, not the feature

Adopting the split model from the autonomy review; the lane audit confirmed its factual basis and added one item it missed. Verified direct-mutation lanes: consolidate (merge writes, archive+hard-delete of secondaries, contradictedBy frontmatter edges), memoryInference (.derived.md children + parent frontmatter rewrites), memory cleanup / belief-state archiving — which runs on any improve run covering memories, not gated by any strategy flag at all (eligibility.ts:500-510), triage applyMode: "promote" (real writeAssetToSource accepts; wired in reflect-distill and proactive-maintenance), extract session indexing (indexSessions default on, direct sessions/** writes), and unattended sync+push (push ?? true, enabled in 8 of 10 strategies). Reflect, distill, extract candidates, validation, and proactiveMaintenance selection are proposal-queued; graphExtraction touches only the regenerable index.

E1. akm improve stays on by default, review-first

Keep the command, the default schedules, and the proposal-generating lanes (reflect, distill, extract, validation, proactiveMaintenance selection, graphExtraction) exactly as they are. No blanket experimental gate.

E2. One autonomy gate for the mutating lanes

Action: a single config opt-in — experimental.improveAutonomy: true (mechanism per F1) — required for: consolidate's merge/delete/contradict operations (its memory→knowledge promotion proposals stay ungated), the memory cleanup + contradiction pre-pass, memoryInference's direct writes, triage applyMode: "promote" (including the judgment tier's accepts), and unattended push (commit stays; push default flips to false — an hourly cron job pushing to a remote is not a defensible default).

Outcome note (D8): the push half was NOT adopted. sync.push keeps its true default and stays outside the gate — it publishes already-committed content to a remote the user configured for that purpose, and it already has sync.push: false and --no-push. The other five lanes are gated as recommended. See D8 in the decision record.

Ship an experimental-autonomous strategy that enables all of it with the existing guardrails (bounded triage ceilings, validators, protected refs, budgets, audit events); reflect-distill and proactive-maintenance keep their promote wiring but refuse to run it without the gate.

Two deliberate scope notes: extract's indexSessions (additive-only sessions/** writes) and distill's salience frontmatter stamp (metadata-only) stay ungated but get documented as direct writes in H1.

E3. Never a silent no-op

A scheduled run that hits a gated lane emits an improve_skipped event with the exact config key to set, surfaces in akm tasks doctor, and raises a health advisory. Existing 0.8-era schedules therefore keep running review-first and tell the operator what changed.

E4. --auto-accept: remove the retired flag

The strict 0.9 runtime does not preserve a dead confidence-gate flag. Scheduled commands must remove it and use the explicit replacement when promotion is desired:

akm improve && akm proposal drain --promote --yes

(defaults: personal-stash policy, 25-accept ceiling), plus the config-only alternative (triage block with applyMode: "promote" — noting it drains the standing backlog, see E5).

E5. Close the same-run promotion gap

Verified: triage runs only as a pre-pass before generation (improve.ts:241-243); with config-only triage, run N's proposals promote at the start of run N+1. The compound command above is currently the only same-slot shape. Action: add an opt-in post-generation drain (triage.postPass: true or akm improve --drain) sharing the same policy, ceilings, and gate — so the documented replacement doesn't need shell && chaining to match 0.8 behavior.

E6. Setup: ask about schedules explicitly; align --yes with interactive

Today interactive setup installs OS scheduler entries (hourly + 4-hourly LLM-calling improve jobs) with only a "server install?" question, while --yes installs none — materially different installs. Action: one explicit question — "Install scheduled maintenance tasks? (review-first; they queue proposals for your review)" — and --yes registers the same review-first task set (autonomous variants additionally require the E2 gate). Upgrades: existing schedules continue review-first with the E3 signposting; customized 0.8 profiles get a documented manual migration.

E7. Declare the tuning config experimental

improve.strategies.*.processes.* (~60 fields, most commented "Default OFF" / "advisory in v1") and index.* (catchall pass names, retired-but- tolerated keys, "accept-any until Chunk 2" fields) are still being designed — the roadmap says so. Action: STABILITY.md declares both key families Experimental (droppable in any 0.9.x/0.10.x release); schema-reject or delete the already-deprecated symmetricValence. The akm improve command surface stays Evolving.


F. Gate — one experimental mechanism for the half-built surfaces

F1. A single experimental.* config namespace

Scattered "EXPERIMENTAL:" help-text labels gate nothing today. Action: one mechanism — experimental.<feature>: true config keys (settable via akm config set). An ungated invocation fails with a structured error naming the exact key (never a silent no-op, per E3). Features:

(akm mv was the third candidate here; C3b recommended removal instead. That recommendation was not adopted — mv ships classified Experimental, with the documented rename procedure routed around it. See D3 in the decision record.)

akm graph needs no gate — it is read-only diagnostics — but gets classified Experimental in H1. health --format html is resolved by B1 (a registered renderer; layout documented as non-contractual).


G. Unify — naming, while breaking is still cheap


H. Document — make STABILITY.md whole


Suggested cut line for 0.9.0

  1. A1–A3 — defects; A1 especially (there is currently no way to disable the boost).
  2. E2 + E3 + E4's warning text — the autonomy gate and the no-silent-no-op rule are the difference between "review-first by default" being true and being marketing. E5/E6 can follow in 0.9.x.
  3. C1–C3b — the four structural removals (bundle, browse grammar, show views→fragment, mv). Each gets dramatically more expensive after release; C3 requires its one-paragraph spec decision first, and C3b must land its txn-kind cleanup in the same change or the indexer breaks.
  4. B1–B2 — the format contract. If the generic renderers slip, the minimum bar is: uniform UsageError for unimplemented md/html (no silent JSON), and the graph export --format removal.
  5. C4–C9 — cheap deletions, all one-way doors after 0.9.x.
  6. D1 — extraction can land in a 0.9.x patch, but the declaration (migrate/backup internal, removal scheduled) must ship in 0.9.0's STABILITY.md.
  7. G, H — naming unification and the docs pass close the release.