Design: bundle organization & back-linking conventions
Status: accepted (conventions shipped in the bundle skeleton) Author: akm Date: 2026-07-11
Amended by the 0.9.0 surface decisions. Two mechanisms this document records as closed are superseded; the sections below that narrate the 2026-07-12 amendments are kept as a historical record, not current guidance:
- A tooled rename (
akm mv, SPEC-7) — the command still ships, but only as an Experimental surface outside the stability contract: its inbound-ref rewriting is inverted relative to the body-ref grammar. A rename is delete plus create: the destination is a new identity and learned state does not follow it. The recommended forced-rename procedure is the manual one (move, fix inbound refs,akm index,akm lint). See 0.9.0-decisions.md D3.- The ref-prefix filter (SPEC-4) — the
<type>:<prefix>/spelling is retired in favour of conceptId prefixes (memories/projecta/,bundle//skills/). See 0.9.0-decisions.md D4.The no-rename default this document argues for is therefore stronger under 0.9.0, not weaker: no tool you may rely on makes a rename cheap.
Problem
The bundle skeleton already ships per-type authoring conventions
(facts/conventions/assets/<type>.md) that tell an agent how to write each
asset type. Nothing told an agent where to place an asset (which
subdirectory) or how to cross-link it. Yet placement and linking are exactly
what determine whether the same knowledge is retrievable two sessions later, or
re-derived from scratch — and whether cross-project reuse happens or duplicates
accumulate.
The request that drove this: define conventions that instruct agents how to organize assets under their type directories (subdirectories for projects, domains, etc.) and how to back-link them to improve retrieval during agentic tasks. This document records the analysis, a structured debate, and the resulting conventions.
What AKM's mechanics dictate (the ground truth)
Any organization scheme has to fit how AKM actually stores and retrieves. The load-bearing facts, each verified in code:
- Path becomes the ref. A file's subpath under its type dir is part of its
ref name —
memories/projectA/auth-tip.md→memory:projectA/auth-tip. Works for every type. A ref-prefix query syntax exists since SPEC-4 landed (amended 2026-07-11):akm search "memory:projectA/"enumerates exactly that subtree (typed, recursive,/-boundary exact) and a barememory:enumerates the whole type. At review time the idiom was false —sanitizeFtsQuerystrips:and/andentry_typeis not an FTS column, so the query degenerated to token noise (empirically tested; see the Review round).akm search "projectA" --type memoryremains the token-match alternative. The path is therefore the one facet you cannot express twice and cannot change without breaking the ref (and every inbound reference to it). - Retrieval is search, not browse — and folders are highly visible to the
ranker. There is no folder walk at query time — agents
akm search/akm curate, thenakm show <ref>. A subdirectory's real payoff: its tokens join the FTSnamecolumn at the highest bm25 weight (10.0), always merge intotags(since SPEC-2 landed; see fact 6), and match the cwd project-context boost (projectContextRankingContributor, +0.2/token, cap 0.5). No "indexing-confidence bump" for subdirectories exists in code. - The FTS surface is
name,description,tags,hints,content(entries_fts,src/storage/repositories/index-schema.ts). There is noprojectfield — a bareproject:frontmatter value is invisible to search. Off-axis facets must betagsto be retrievable. xrefs:fold into the FTShintsfield for all types (src/indexer/search/search-fields.ts:58). Back-links are a retrieval signal, not decoration — which means both too few and too many degrade ranking.- Project relevance is recovered at query time. Ranking blends
a per-project usage signal —
scopedUtility * 0.7 + globalUtility * 0.3(SCOPED_UTILITY_BLEND_SCOPED,src/indexer/search/ranking-contributors.ts), keyed by a cwd-anchor (a hash of the querying project root), not the asset's path. It is NOT rename-proof for the asset:entry_keyincludes the full name, so renaming a file mints a new entry row and orphans its accumulated global + scoped utility history. As of 0.9.0 that is the defined semantics — a rename is delete plus create, and no command preserves learned state across one (see 0.9.0-decisions.md D3). A reusable asset does not need the project baked into its path to rank well inside a project — and a manual rename costs learned ranking. - Directory (scope/domain) tokens always merge into
tags— since SPEC-2 landed (extractDirTagsFromName,src/indexer/passes/metadata.ts) they are derived from the canonical ref subpath, so explicittagsno longer suppress the scope token, both indexing walks agree, and the exact-tag ranking boost fires for the path token without restating it. Filename tokens are still auto-derived only whentagsis empty (they already live in the FTSnamecolumn and in aliases). The old explicit-tags footgun is gone; existing installs pick the merged tags up on the next reindex. - The entity/relation graph boost extracts from body content, not the path
(
graph-extraction.ts). Canonical entity naming must live in the prose. category: convention|metafacts are surfaced to every non-wiki authoring flow (resolveStashStandards);facts/conventions/assets/<type>.mdis surfaced type-scoped (resolveTypeConventions). This is the only channel that reaches a browse-blind agent mid-task — so conventions must ship as facts.- Non-wiki xref breakage is caught by
akm lint, not at write time. The deterministicmissing-refcheck covers body refs, therefs:frontmatter array, and (since SPEC-1 landed) thexrefs:/supersededBy:/contradictedBy:frontmatter channels. A rename dangles inbound links silently — nothing catches them until the nextakm lintrun flags them, so runakm lintas the last step of any rename. (akm mvautomates this, but its rewriting is inverted relative to the body-ref grammar, so it ships Experimental in 0.9.0 and does not substitute for the lint run — see 0.9.0-decisions.md D3.) Wikis are excluded fromakm lint's directory sweep; the equivalent orphan/broken-xref/broken-source/stale-index/uncited-raw structural checks are implemented in the LLM Wiki adapter (core/adapter/adapters/llm-wiki-adapter.ts, ported from the pre-0.9.0akm wiki lint). Amendment: theakm wikicommand family, includingakm wiki lint, was removed in the 0.9.0 bundle-adapter cutover (see wikis.md) — there is currently no CLI surface that invokes these checks.
Research inputs
Three parallel research briefs (Karpathy's LLM wiki; agent memory / RAG / GraphRAG; PKM taxonomies — Zettelkasten, PARA, Johnny.Decimal) converged on a few transferable principles:
- Separate raw from synthesized, and keep raw immutable. Karpathy's core invariant: interpret and correct in a synthesized layer that cites the raw source; never edit the source. Gives every claim an auditable provenance chain.
- The index is the catalog — don't hand-maintain one. Karpathy's
index.mdexists because a filesystem has no ranker. AKM already has one (plus a utility signal), so effort should go into per-asset retrievability (strong title, accurate first paragraph, tags, xrefs), not a hand-kept catalog. - Metadata/facets beat folder depth for retrieval. The literature repeatedly measures metadata-enriched and "contextual"/self-situating retrieval as materially better (e.g. contextual headers cutting failed retrievals by a large margin) — because the reader is a ranker, not a browser.
- Atomicity + sparse, real links (Zettelkasten), not dense mandatory links. Backlinks help multi-hop recall, but forced density produces junk edges.
- Controlled vocabulary prevents bucket drift, but deep hierarchies and opaque codes (Johnny.Decimal) fail when nobody browses.
The debate
Four positions were argued and adversarially critiqued:
| Position | Thesis | Verdict |
|---|---|---|
| Domain-first taxonomy | Path = stable subject domain; lowest rename rate, highest lexical signal. | reject-keep-ideas |
| Scope-first namespacing | Path = project/client/team; scope is the dominant retrieval filter. | reject-keep-ideas |
| Flat + faceted + dense backlinks | Minimal folders; organizing signal lives in frontmatter + a dense link graph the index can read. | adopt-with-changes |
| Hybrid "narrowest stable scope" ladder | One folder = the narrowest stable boundary (client > project > domain > root), rest in facets. | adopt-with-changes |
The critiques were decisive:
- A single global axis fails both ways. Domain-first makes project scope
invisible (no
projectFTS field; agents that writeproject:frontmatter get zero retrieval benefit) and splits "everything about project A's auth" across three query idioms. Scope-first strands reusable assets behind an arbitrary project home and creates ashared/dumping ground. - The per-asset "narrowest scope" ladder is non-deterministic. Two agents run
the delete/archive test on equivalent insights and pick different rungs,
fragmenting a topic across
memory:projectA/andknowledge/auth/with no reconciliation pass — and the wrong guess is baked into an immutable ref. - Mandatory dense xrefs actively hurt. Because ref strings fold into the FTS
hintsfield, a link quota smears a popular source's ref token across dozens of citers (destroying its ranking signal) and fills the entity graph with plausible-but-wrong edges. - Per-namespace hub wikis rot. As an obligation they contend on concurrent writes, generate stale-index lint noise, and flatten the multi-hop graph into a namespace-wide star.
Decision
Resolve the project-vs-domain tension by asset TYPE, not per-asset judgment. This is the one rule that is both deterministic mid-task and mechanically justified:
- Scope-born types (
memory,lesson,task,env,secret) → the current project/client slug. They are born bound to the work in front of the agent, so the working context is the answer — zero counterfactual reasoning, andakm search "projectA" --type memoryreconstructs the project. - Reuse-born types (
knowledge,skill,wiki,fact,script) → a stable domain from a short closed vocabulary. Project relevance is already recovered by scoped-utility ranking (fact 5), so spending the path on the project axis is redundant; the domain prefix is the only handle that co-locates cross-project reuse. For a wiki, the domain names the wiki itself. - Global-by-nature types (
command,agent,workflow, bundle-wideenv) → type root or a tool slug.
Supporting rules, all shipped as convention facts:
- One path axis; depth 1 (2 max for strict containment); lowercase-hyphen semantic segments; no volatile facets in the path (status/date/version/…); flat type-root fallback instead of inventing a one-off domain.
- Off-axis facets go in
tags(never a bareproject:field); the path scope/domain token merges intotagsautomatically, so it never needs restating (fact 6). - A provenance xref is mandatory when the asset derives from another (never
invented for original observations). Associative xrefs are discretionary and
capped (~5, a heuristic). Corrections are new assets that xref what they
correct, paired with
beliefState: superseded/supersededByon the stale asset; immutability applies to ingested material (wikiraw/, vendored docs, transcripts) — synthesized knowledge pages are the rewritable layer (Karpathy immutability inside AKM's flat model). - Self-situating text on every asset — the orientation line goes in
description:/when_to_use:(the higher-weight indexed fields; bounded body prose also enters the lowest-weightcontentfield). The body header feeds the entity graph (extracted from body prose) and human readers atakm show. Anthropic's Contextual Retrieval figures (35%/49%/67% top-20 failure-rate reduction) were measured on chunk-prepended context in a chunked RAG pipeline — an analogy motivating the description-field placement, not a measurement of it. - Hubs are optional wiki assets for a few high-traffic domains, never a per-write obligation. The FTS index is the catalog.
- A ref is an address chosen once: default to not renaming; if unavoidable,
move the file, grep the bundle for the old ref and fix inbound xrefs in the
same pass, then
akm indexandakm lint. The destination is a new identity and starts with fresh learned state.
Rejected over-builds: mandatory dense/bidirectional xrefs, per-namespace hub wikis, the per-asset scope ladder, and any hand-maintained catalog.
What shipped
Convention facts in the bundle skeleton (src/assets/stash-skeleton/facts/conventions/):
organization.md(category: convention) — the partition-by-type placement rules. Surfaced to every non-wiki author.backlinks.md(category: convention) — the cross-linking / provenance / canonical-naming rules. Surfaced to every non-wiki author.domains.md(category: convention) — the editable domain vocabulary and canonical entity spellings for reuse-born assets.- A short Placement & linking section added to each
facts/conventions/assets/<type>.md, stating that type's default axis (surfaced type-scoped).
Because these are category: convention facts, resolveStashStandards injects
them into the authoring context automatically — no code change was required to
enforce the surfacing. They are soft guidance; a bundle owner edits them to match
how their bundle is queried.
The code changes the finalized conventions imply are specced separately in stash-conventions-code-spec.md (8 specs, prioritized and sequenced; nothing implemented in this change).
Review round
A two-reviewer adversarial review (retrieval mechanics / IR + KM methodology / agent usability), with cross-rebuttals, tuned the shipped conventions. Decisive corrections, each code-verified or empirically tested:
- The ref-prefix search idiom was false.
akm search "<type>:<prefix>/"never worked (sanitizeFtsQuerystrips:and/;entry_typeis not an FTS column) — live-tested at zero hits. All convention text now usesakm search "<slug>" --type <type>. Amendment (2026-07-11): SPEC-4 has since made the idiom real —<type>:<prefix>/(and bare<type>:) queries now translate to a typed subtree enumeration (parseRefPrefixQuery). The finding was correct at review time; the shipped convention facts keep the--typeidiom for now (re-adoption deferred one release — see open questions). - "Folders are invisible to the ranker" was backwards. Subpath tokens are
indexed in the FTS
namecolumn at the highest weight and earn the cwd project-context boost; the claimed "indexing-confidence bump" does not exist. - xrefs never feed the entity graph — extraction reads body prose (memory + knowledge) with frontmatter stripped, so graph-relevant relationships must be stated in the body.
- Body prose is not FTS/embedding-indexed — self-situating orientation
belongs in
description:/when_to_use:; the body header serves the graph and human readers. - "Rename-proof" was misleading — a rename orphans the asset's utility
history (new
entry_keyrow), strengthening the no-rename rule. Amendment (2026-07-12): SPEC-7 (akm mv) made a tooled rename preserve that history. Superseded (0.9.0): a rename is delete plus create and orphans the utility history.akm mv's in-place re-key still ships, but it is Experimental and nothing may be built on it, so the original finding stands unqualified. - Corrections now pair with
beliefState: superseded/supersededBy(parsed for all markdown types, demoted at rank time), and immutability was rescoped to ingested material only, resolving a contradiction with the knowledge type conventions. - Completeness:
scriptjoined the reuse-born bucket; wikis take the domain as the wiki NAME;envgot a decision test; aconventionsdomain entered the vocabulary (the shipped facts had violated their own list); a two-domain tie-break was added; and duplicated pointer text was trimmed from the nine co-injected per-type files.
Open questions / future work
Items with an implementation path were specced in
stash-conventions-code-spec.md, and all
eight specs have since landed (amended 2026-07-12): the lint frontmatter
channels (SPEC-1), the tag merge (SPEC-2), --xref (SPEC-3), the ref-prefix
filter (SPEC-4), --supersedes demotion (SPEC-5), category capture (SPEC-6 —
shipped capture-only; the rank-time demotion was dropped after measurement
showed no crowding), akm mv (SPEC-7), and bounded native body indexing
(SPEC-8) — see the closed bullets below. What remains genuinely open:
vocabulary governance, scheduled consolidation (argued down in the spec — the
improve pipeline already injects the amended conventions), and the typed
provenance channel.
- A thin HARD floor.
akm lintruns a deterministicmissing-refcheck over body text andrefs:frontmatter for non-wiki markdown assets; the gap was precisely thexrefs:/supersededBy:/contradictedBy:frontmatter channels the conventions mandate — closed by SPEC-1 in stash-conventions-code-spec.md (implemented: the same check now scans those keys too). Orphan and uncited-source checks for non-wiki assets are argued down (orphanhood is the sanctioned normal state under discretionary linking; uncited-source is undecidable without knowing whether an asset is derived). - Kill the tag footgun in code. Closed by SPEC-2 in
stash-conventions-code-spec.md
(implemented: directory tokens from the canonical ref subpath now always
merge into
tags; filename tokens still derive only whentagsis empty). Takes effect on reindex; operators with collapse-detector canary sets should re-mint baselines viaakm improve canary --refresh. - Vocabulary governance. How an agent proposes a
fact:conventions/domainsaddition mid-task without blocking on human review (interim: flat type-root fallback + a proposal note). - Scheduled consolidation. A curate/consolidate trigger to dedup near-duplicate atoms, promote recurring memories into domain knowledge, and fix danglers — since no reorg pass happens organically and utility ranking is cold-start-biased toward incumbents.
- Naming collision. The convention's "scope" wording vs the existing
entry.scope(user/agent/run/channel) /scopeKeyconcepts in code — the convention facts deliberately say "project/client slug" and "partition axis" to avoid cross-wiring; keep it that way. - Ref-prefix filter in code. Closed by SPEC-4 in
stash-conventions-code-spec.md
(implemented:
parseRefPrefixQuerydetects<type>:<prefix>/— and bare<type>:— queries on the untyped search path and translates them to a typed index enumeration with name-prefix narrowing; hits carry the fixed browse score 1, an explicit--typewins over the parsed type, and the in-memory prefix filter leaves anentry_key LIKESQL push-down as a later optimization). Whether the skeleton convention facts should re-adopt the idiom overakm search "<slug>" --type <type>stays deferred one release so older CLI versions aren't taught a query shape they don't support. - A tooled rename. Closed by SPEC-7, then re-opened in 0.9.0 and answered
"not as a contract":
akm mvships Experimental and a rename is delete plus create. The recommended forced-rename procedure is the manual one this document originally prescribed — move the file (a memory's.derived.mdtwin moves with it), grep and fix inbound refs in the same pass, thenakm indexandakm lint. The no-rename default is again the primary defence, which strengthens it. See 0.9.0-decisions.md D3. - Index self-situating body text. Closed by SPEC-8 in
stash-conventions-code-spec.md
(implemented: the native adapter normalizes bounded Markdown prose into the
lowest-weight
contentfield; secrets, env files, raw sessions, and session checkpoints are excluded at the adapter boundary). Thedescription:/when_to_use:orientation routing the conventions teach remains primary. - Typed provenance channel. Evaluate
sources:for all types, indexed outsidehints— it would separate provenance from associative links and resolve the xref-cap tension mechanically. Rejected for the shipped convention because non-wikisources:earns no retrieval benefit today and provenance-in-xrefs powers the "what did we build on this?" query. - Convention facts crowd domain-term queries. Their name/description/
when_to_use tokens (bodies are unindexed) match via prefix expansion and carry
the fact type boost (0.22); consider excluding
category: conventionfacts from default untyped search (their delivery channel is prompt injection, parallel to thesessiondefault exclusion) or demoting the category at rank time. Amendment (2026-07-12): closed by SPEC-6 in stash-conventions-code-spec.md, shipped capture-only. Thecategory:key is now captured into the index asentry.category(takes effect on reindex), making a category-keyed policy implementable — but the spec's prescribed measurement (full skeleton convention facts plus a realknowledge/authasset, untypedauthquery, semantic off) showed NO crowding: FTS is exact-first, so prefix expansion onto the facts' tokens fires only when nothing matches the query exactly, and a real domain asset always outranks the facts. The rank-time demotion contributor was therefore dropped;tests/integration/search-convention-fact-demotion.test.tspins the no-crowding invariant and is the regression guard if demotion is ever revisited. - Automate correction demotion. Closed by SPEC-5 in
stash-conventions-code-spec.md
(implemented as the deterministic CLI form:
--supersedes <old ref>onakm remember/akm importwrites the correction with the xref, setsbeliefState: superseded+supersededBy: [<new ref>]on the old asset, and reindexes it; demoting belief states now also cap the final search score so the stale incumbent cannot outrank its correction). An LLM-sidecurate/improvepass that detects citing corrections automatically remains open.