akm docs

Design: the fact asset type — durable bundle-level knowledge

Status: accepted (phase 1 implemented) Author: akm Date: 2026-06-20

Problem

akm has no home for durable, mostly-static facts about a user, team, or project. Concretely, users want a bundle to carry things like:

Today these have nowhere first-class to live:

Conceptual grounding

The agent-memory literature splits long-term memory three ways (CoALA, Cognitive Architectures for Language Agents, arXiv:2309.02427; echoed by LangMem and Letta/MemGPT). akm already covers two of the three:

Memory class "answers" akm
Episodic — timestamped events "what happened" session (#561)
Procedural — executable how-to "how to do X" skill, command, workflow
Semantic — durable facts/identity "what is true" fact (this doc)

Karpathy's operating-system analogy frames the design: model weights are the CPU/ROM (can't retrain), the context window is scarce RAM, and everything else is disk that must be paged in. A fact is disk-resident knowledge selectively loaded into RAM — neither baked into weights nor permanently pinned in the system prompt. Anthropic's Effective context engineering for AI agents reinforces this: curate aggressively, prefer just-in-time retrieval, and pin only a small high-signal core (cf. CLAUDE.md, kept short).

Design

Type: fact

A first-class, indexed, searchable markdown asset type. Stored under facts/ in a bundle; ref form fact:<name> (nesting allowed, e.g. fact:team/tool-stack). Singular name matches akm convention (skill, memory, lesson, task, session).

<stash>/facts/
  personal/identity.md         # fact:personal/identity
  personal/writing-style.md
  team/tool-stack.md
  conventions/coding.md         # the "constitution"
  meta/naming-conventions.md
  meta/active-projects.md

One type, categorized — not a type explosion

Rather than separate fact / constitution / profile types, a single fact type carries a category frontmatter dimension. This keeps akm's minimalist type set and matches the LangMem "namespace/scope" approach. Recommended values: personal, team, project, convention, meta. Normative facts (the "constitution") are stored as facts and phrased as guidance at injection time.

Frontmatter

---
description: <one-line, indexed for search>   # like other markdown types
category: personal|team|project|convention|meta
pinned: false        # true → part of the always-injected core (keep this set small)
updated: 2026-06-20
---

description and category are the curation surface; pinned selects the small always-on core. Additional keys (source, status, …) are accepted but not required in phase 1.

Retrieval & injection model: pinned core + JIT retrieval

Two tiers, matching the context-engineering guidance:

  1. pinned: true facts form the small always-injected core (CLAUDE.md style — keep it short). Phase 1 makes pinned a real, captured, query- surfaceable property (tag + search hint + ranking boost); phase 2 wires the assembled pinned-core into harness system prompts.
  2. Everything else stays on "disk" and is surfaced through normal akm search / akm curate / akm show (JIT retrieval). This works the day the type ships — no new retrieval path required.

Ranking

fact gets a high TYPE_BOOST (authoritative, like knowledge), plus a modest additional boost for pinned facts so the core outranks ordinary facts on otherwise-equal queries.

Implementation (phase 1)

Following the session/task template, metadata is encoded as tags + search hints (no new DB columns or StashEntry fields):

Concern File
Type spec PLACEMENT_SPECS in src/core/asset/asset-placement.ts — fact: { stashDir: "facts", ...markdownSpec }
Renderer + action TYPE_PRESENTATION in src/core/type-presentation.ts (supersedes the earlier TYPE_TO_RENDERER/ACTION_BUILDERS split)
Renderer + metadata src/output/renderers.ts — factMdRenderer (frontmatter category/pinned parsed inline in buildShowResponse)
File classification src/indexer/walk/matchers.ts — DIR_TYPE_MAP facts/
Ranking src/indexer/search/ranking-contributors.ts — TYPE_BOOST + pinnedFactRankingContributor
Lint factDiagnostics in src/core/adapter/adapters/akm-lint.ts, called from src/commands/lint/index.ts (warns missing-category on absent/unrecognized category)
Authoring hint src/integrations/agent/prompts.ts — TYPE_HINTS.fact
Graph extraction (opt-in) not currently wired: graphExtractionIncludeTypes (src/core/config/schema/index-config.ts) takes any string, but the runtime consumer's SUPPORTED_GRAPH_EXTRACTION_INCLUDE_TYPES set (src/indexer/graph/graph-extraction.ts) does not include fact — configuring it is a silent no-op today
Type union KNOWN_TYPES in src/core/recognition-util.ts, which includes fact

fact: refs resolve automatically — the ref resolver derives its type set from PLACEMENT_SPECS, not KNOWN_TYPES: typeNameFromConceptId (src/core/asset/resolve-ref.ts) calls typeForStashDir (src/core/asset/asset-placement.ts), and fact has a PLACEMENT_SPECS entry (stashDir: "facts"). The ref-resolver contract note in src/commands/lint/base-linter.ts confirms the same split: refToRelPath is "DERIVED FROM THE PLACEMENT SPECS ... rather than hand-encoded."

Phase 2 (follow-up, not in this change)

Relationship to .meta/

.meta/ stays the human-oriented, non-indexed orientation convention. fact is its machine-facing sibling: indexed, searchable, rankable, and (phase 2) agent-injected.