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

143 lines (110 loc) • 7.18 kB
# 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: - **Personal** — name, email, address, timezone, favorite blogs, writing/working style. - **Team** — tool stacks, primary languages, CI/CD platform, deployment targets, web addresses. - **Conventions / constitution** — coding conventions, architecture principles, lint rules. - **Bundle meta** — folder organization, naming conventions, expected frontmatter, the active-projects list. Today these have nowhere first-class to live: - `memory` is **episodic** — recency-decayed, belief-state ranked, captured ad-hoc via `akm remember`. It models "what I observed in a session," not "what is durably true about this bundle." - `knowledge` is curated **reference material** — documents an agent reads on demand, not bundle-level identity/conventions injected as context. - `lesson` is a **`when_to_use` trigger** distilled from feedback. - The `.meta/` convention (`src/core/asset/stash-meta.ts`, see also `docs/guides/concepts.md`) is **documentary only**: not indexed, not searchable, not ranked, and not surfaced to agents automatically. ## 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 ```yaml --- 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) - Assemble the pinned-fact core and inject it into harness system prompts (`src/integrations/agent/**`, per-harness builders). - Optional `akm fact` CLI surface and a hot-capture path (à la `akm remember`). - Staleness/conflict handling (`status: active|stale|superseded`), since the documented failure mode of fact stores is update, not storage. ## 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.