Decision records¶
This is where the why behind non-obvious choices lives. It is historical context, not the product contract and not a backlog:
- current behaviour lives in the topic pages linked from the documentation index;
- planned work, bugs and delivery status live in GitHub issues;
- completed user-visible work is summarised by releases and
CHANGELOG.md.
One decision is one file, docs/decisions/D<n>-<slug>.md, and the list below
is generated from those files. Reading one costs the one file, not the register it
used to live in — the ten thematic registers this replaced held 197 records in
483 KB, so answering "why is it like this?" meant loading up to 101 KB to reach a
single paragraph.
Referring to a decision¶
In code comments and in prose the reference is the bare D<n>, with no path:
ls docs/decisions/D47-* # resolve it
rg -l 'D47' --type go # who mentions it
It costs fewer tokens than a path, and it does not break when a title is reworded. In markdown, use a real link — the test suite checks that every one of them resolves.
Adding a decision¶
- Reserve the number. Survey open
planissues as well as the files: a plan title reserves itsD<n>until it is implemented or abandoned.
make decisions-next # highest on disk + 1
gh issue list --label plan --state all --limit 1000 # and reserved by a plan
- Write it at the end of implementation, not before. The plan issue is the draft; the decision file records what was actually built, including any deviation from the plan.
make decisions-new N=202 SLUG=my-choice TOPIC=control-plane
-
Record the decision, the rationale, and the consequences — including the uncomfortable ones. Alternatives that were considered and rejected are the part a reader six months from now actually needs. Current behaviour goes in the corresponding topic page, future work in a GitHub issue.
-
Regenerate the index and commit it:
make decisions-index. CI fails if it is stale, so this is not optional. It also fails if the file still carries a placeholder from the template — a half-written record indexes as a blank entry, because<title>renders as an HTML tag.
Do not add status tables, milestone lists or duplicate entries. A decision has exactly one file; other pages link to it.
Numbers that have no file¶
Every D<n> written anywhere in the repository has to resolve, and make test
checks it (D208). Three states are legitimate, and only the first is a file:
| State | Where it is declared | Meaning |
|---|---|---|
| implemented | docs/decisions/D<n>-<slug>.md |
the record |
| gap | repodocs.GapDecisions, with a reason |
deliberately never written; the number must not be reused, and a file claiming it fails the build |
| reserved | repodocs.ReservedDecisions, with the plan issue |
an open plan holds it; the record arrives with the implementation, and the entry is then removed |
A plan abandoned without implementing does not stay reserved: either it gets a record saying it was not done — D130 is the worked example — or, if nothing depends on the number, it becomes a gap.
All decisions¶
208 records, one file each, grouped by the topic they belong to.
In code and in prose the reference is the bare D<n>, with no path: resolve it
with ls docs/decisions/D<n>-*. Add one with make decisions-new, then
regenerate this list with make decisions-index.
Cross-cutting architecture¶
KB model and imports¶
- D5 — Normalized content-hash
- D8 — Frontmatter: stdlib-only YAML parser
- D9 — Canonical ordering + SectionHashes
- D13 — Graph neighbors: standard markdown links
- D15 — Secret scrubbing: blocking regex, 6 patterns ✅ Removed
- D19 — Agent contract: AGENTS.md generated by Init
- D22 — Event-driven exporter: HTTP webhook + Go worker pool ✅ Removed
- D25 — KB layout:
data/conceptual root,services/carve-out inResolvePath - D28 — Removal of
raw/,mcp/,source_ingest,scrub,exporter(June 2026) - D62 — KB repo cleanup: no generated AGENTS.md/.gitignore, exclude via .git/info/exclude (WP6)
- D72 — KB refactoring ergonomics: backlink rewrite and batch in
concept_move, consistent index, inventory, explicit dossiers - D74 — Import of external non-OKF wikis/KBs:
kb-importskill + CLI scaffold + lint-driven curation - D77 — Atlas/Map/Journal hierarchy: the dossier becomes a state of the concept
- D87 — Fence-aware heading detection: shared line iterator for
ListHeadings/ExtractSection/SectionHashes - D91 — Import convenience without widening the default write surface
- D106 — Concept-owned non-Markdown assets
- D107 — Declarative lint contracts in map descriptors
- D109 — KB-owned concept templates and one-shot scaffolding
- D124 —
machine_pathdistinguishes client-local paths from a Map's operational target paths - D125 — Atomic multi-concept mutation batches:
concept_batch - D150 — What counts as a link: code fences, labelled wiki-links, extensionless assets
- D159 — Per-concept lint opt-out, home-anchored allow prefixes, related size thresholds
- D160 — Complete the concept refactor primitives: move, merge, collapse
- D162 — Authoring and ingestion papercuts: frontmatter, placeholders, repo scan, import mapping
MCP tools, search and lint¶
- D3 — SQLite:
modernc.org/sqliteplanned, not yet introduced - D10 —
concept_write: frontmatter from JSON map - D12 — Keyword search: pure-Go inverted index
- D14 — Lint: deterministic checks only in the Core
- D20 — Embedding: interface + Ollama adapter, wired
- D24 — Automatic keyword index update after
concept_write - D32 — Search index persisted on SQLite (
internal/sqlindex) - D36 — mcpserver: unified
Depsregistration, split per domain, shared index - D43 — Automatic SQLite index rebuild at startup if empty, without embedding
- D65 — "agent" tool profile and compact instructions: less fixed context per session
- D66 —
kb_overviewcounts concepts, not subdirectories - D67 —
concept_deleteMCP tool - D70 — Incremental update ergonomics:
concept_patch+ snippets insearchresults - D71 — MCP tools for provisioning artifacts:
artifact_read/artifact_write/artifact_list/artifact_delete - D78 — Readable per-path log,
concept_readwith size guard and outline,concept_oversizelint - D88 — Frontmatter unset in
concept_patch,map_deletetool - D89 — Multi-term search fallback and coherent search modes
- D90 — Content-hash reconciliation for derived search indexes
- D108 — On-demand backlinks and frontmatter facets
- D122 — Bounded curated-index read/patch:
index_get(with_hash)+index_patch - D123 — Expose read-only governance tools to descriptor-bound agents
- D131 —
kb_status:by_statusroll-up excludes unset status - D135 — Remove semantic search and the Ollama embedding backend
- D136 —
index_rebuildis consolidated intoreindex - D149 — One link base and one concept resolver for lint
- D151 — A KB's capabilities and mount provenance are visible from a session
- D161 — Every enforced limit and contract stated in its own tool description
- D165 —
searchscope is a literal prefix on both backends - D185 — The read path stops sending the same bytes twice
- D186 — A gate reports what can act on the verdict, not the whole archive
HTTP, stdio and authorization¶
- D2 — Hand-rolled MCP stdio transport (no SDK)
- D16 — HTTP transport: hand-rolled Streamable HTTP
- D17 — Multi-KB: routing via query parameter
- D18 — Audit log: JSONL hash-chain with opt-in Ed25519 signature (compliance-grade)
- D44 — Structured tokens with per-KB scopes + per-KB identity/SOPS fields
- D45 — Per-KB r/rw scope enforcement: scoped
TokenStore+ body-peek HTTP guard + fail-closed read-only classification - D102 — Opt-in per-KB MCP tool-name prefix
- D118 — Fine-grained RBAC and permission-aware retrieval
- D119 — Operational audit: attempt/completion pairs, checkpointed retention, offline verification
- D120 — Tool-prefix discovery for client-owned multi-KB operations
- D128 — Serve the 2026-07-28 revision alongside the handshake era
- D129 — Report the protocol era and client identity of connected clients
- D130 — The handshake era is not retired: the plan was overtaken by D168
- D132 — Audit authorization denials; serve RFC 9728 metadata; drop the unwired HTTP handler layer
- D133 — The version header selects the era by value, not by presence
- D144 — The D102 tool-prefix mitigation reaches the agent
- D152 — Tool-prefix uniqueness is enforced, and the client warning reads the prefixes
- D153 — A tool prefix is the default for every mounted KB
- D166 — HTTP connection timeouts, and deleting three unreachable entry points
- D168 — The MCP wire format comes from the official SDK
- D179 — Invalid auth configuration fails startup instead of widening access
- D187 — One tool surface for a multi-KB server: a routed mount with the KB as an argument
- D200 — A POST's
Acceptheader is supplied, not enforced - D210 —
Mcp-Protocol-Versionis stripped on notifications
Commits, synchronization and conflicts¶
- D30 — Git commit per logical operation (Step 1: local commit)
- D31 — Rebase conflict handling: unversioned side registry +
degradedmarker + guided skill - D33 — Git Step 4: record→finalize conflict resolution, per-content merge
- D46 — Per-KB git identity (author/committer + SSH): per-KB env wins over the process, default committer = author
- D76 — Write-path latency: batch patch, sync coalescing, asynchronous push
- D93 — Read-path Git freshness
- D94 — Git-history changes digest
- D103 — Observable replication failures and native Git identity fallback
- D117 — Server Git profile uses a dedicated working branch and GitHub PR boundary
- D145 —
kb_statusstates the replication facts; the workflow field is named for what it means - D155 — Surviving an external process writing into a synced KB
- D164 — The search read lock spans the query, not the pointer load
- D167 — The push force flag is cleared when a push ends, not only when one starts
Skills, services and secrets¶
- D26 — Bundled skills embedded in the binary
- D47 — Per-KB SOPS:
AgeKeyEnv(env wins), flatservice_get(resolve_secrets), resolve requires rw - D96 — Operations knowledge ships as a bundled skill
- D104 — Structured SOPS pointers, scoped refs and encrypted-only writes
- D158 —
Serviceis matched case-insensitively, andsecret_resolveredacts by default - D191 — One skill validator, for the MCP channel and the git one
- D196 — The onboarding skills route to references for artifact authoring and the encryption flow
- D212 — A skill's frontmatter is validated with the parser a client uses, not with ours
Provisioned artifacts¶
- D27 — Client synchronization: manifest+revision, lockfile, layered triggers
- D34 — Synchronization: Layer 3 push over stdio + codex/kiro materialization
- D40 —
sync_pulland client-side trust; anti path-traversal guard inprovisioning.Apply - D48 — Provisioning extended to
kind: agent/hook; hooks without auto-merge intosettings.json; per-kind counts - D50 — Honest per-provider sync state:
unsupported≠needs_approval,InSyncrequires zero differences - D54 — Per-server trust persisted at
connect(replaces the recurring--auto-trustgate) - D55 — Agents also materialized on OpenCode, via frontmatter translation
- D56 —
instructionskind: KB imprinting via managed block in the global instruction files - D57 — Claude Code hooks: automatic registration in
settings.json - D58 — Real Codex CLI integration: managed-block
config.toml, TOML agents, hook engine - D59 — OpenCode hooks: generated JS plugin
- D60 — Client-side bootstrap hook: auto-sync at session start (WP4)
- D61 — Instructions: auto-generated agents section + curated
instructions.md(WP5) - D69 —
kind: mcpprovisioning: third-party MCP servers distributed by the KBs - D75 — Path portability across machines: placeholders auto-resolved via git remote
- D105 — Binary-safe provisioning artifacts and executable KB scripts
- D114 — Verify provisioning artifacts cryptographically
- D115 — MCP allow-list and hash-bound local approval
- D116 — Trusted stdio MCP descriptors with environment references
- D138 — Provenance stamp on materialized skills and agents, and two hashes per managed file
- D139 — On-disk verification: sync restores what diverged locally
- D140 — A scheduled sync trigger for clients with no session hook; Kiro has no registrable one
- D148 — Provisioning refuses a symlinked destination
- D154 — The generated steering block describes what this client received
- D170 — Selection before the merge: each provider is projected only its bound KBs
- D171 — A cross-KB collision is an error, not an alphabetical tie-break
- D172 — Sync ordering, per-provider checkpoints, and a client lock
- D178 — A file dropped from an artifact is removed with it, not stranded
- D181 — A cached repo path is validated before use, and a change of roots invalidates the cache
- D182 — Attribute and order the per-KB instruction sections
- D183 — Keyed session-global directives make a cross-KB prose conflict detectable
- D184 —
syncreports the revision each provider recorded, not the one it fetched - D193 — Workspace-scoped artifact projection: one provider, different KBs per repository
- D195 — Kiro receives subagents; its hooks are documented but not shipped
CLI, TUI and providers¶
- D23 — Multi-provider configurator: CLI flags + per-provider JSON adapters, non-destructive merge
- D29 — OpenCode format aligned to the official schema +
kb.Initcreates the git repo - D35 — Configurator: opt-in interactive TUI (
--tui) - D37 — Single binary with subcommands; client always HTTP (no stdio/
--check/--base-dir) - D42 —
cartographer disconnect: inverse ofconnect, inverse JSON merge, full provider prune - D49 — Interactive
cartographer connect: bubbletea form shared TUI/CLI - D51 — MCP server name fixed to "cartographer"; auto-trust hint with the exact command
- D52 —
.cartographer.yamlalways machine-wide (home): project/global scope removed - D63 — Complete prune: empty directories with boundaries, MCP configs reduced to empty (WP7)
- D64 — Connect UX: per-field hints, retry with populated form, persistent default, pre-connect probe (WP8)
- D86 — Connect UX: agent subsets, 0-KB diagnostics, absolute paths
- D92 — Per-KB MCP entries for multi-KB servers
- D99 — Codex's
config.toml: comment markers are not enough, orphaned tables are adopted - D113 — One client status snapshot across CLI and dashboard
- D126 — Codex's
config.toml: foreign tables written inside a managed block are relocated, not lost - D127 — Codex hook adoption also matches on the decoded command value
- D137 — Declarative provider registry: two tables, owned by the packages that own the concepts
- D141 — Hermes is a supported provider that receives deliveries, not installations
- D142 —
reconnect: rebuild a client configuration, never automatically - D143 —
doctor: a separate command that diagnoses and never repairs - D146 — Existence and content are verified with different evidence
- D147 — Every reported write is observed, never intended
- D157 — Backfill managed-file hashes in place instead of rebuilding every client
- D169 — Per-provider KB binding: a user-owned preference beside a server-owned cache
- D175 — The dashboard is binding-aware, and says when it does not know
- D180 — Every typed client-config field is written by
Save - D189 — Instructions written correctly are not reported as installed until the provider reads them
- D190 — The KBs are chosen before the first write, not narrowed after it
- D194 — Support Google Antigravity in multi-provider client and provisioning
- D201 — A provider's tool identifier limit is checked at connect and sync
Configuration, deployment and releases¶
- D21 — Configuration via environment variables
- D38 — Server configuration via YAML (
internal/config,gopkg.in/yaml.v3) - D39 — Server-side KB bootstrap from git remote (replaces the k8s init container)
- D41 — Direct release+deploy pipeline (least-privilege SA) +
install.shwithout Homebrew - D53 — Explicit KB name (
kbs[].name) + per-KB conventions for the git token (git.token_dir) and SOPS key (sops.age_key_dir) - D68 — Deploy via Flux GitOps,
kubectl applyremoved from CI - D73 — Local mode as a native service (
cartographer service), Docker out of the local deploy - D83 — Service install robustness: create the data dir, tolerate its absence, stable plist binary path
- D84 — Readiness signal and per-KB path routing
- D85 —
kb createand first-KB onboarding: a CLI command, not only the agentic skill - D95 — Upgrade transparency through version-skew hints
- D97 — Agent-driven onboarding mounts remotes through
kb clone - D112 — Reserved local endpoint defaults
- D121 — Native local upgrades repair themselves
- D134 —
kb createrequires a git remote - D156 — Service
PATH,restartafterstop, and akb createthat keeps its scaffold - D173 —
kbcommands: bounded clone, correct local target, read-only list - D174 —
service statusreports observable state, not a verdict on nothing - D176 — The multi-KB readiness path gates on the audit sink too
- D177 —
kb renameis offline, bounded, and says what it does not migrate - D192 — Onboarding and release hygiene: text that lied, hints that could not be run, paths that ended half-done
- D197 — The evaluate-then-install path: steer the agent that is actually reading
- D199 — The Cask's install steps are
postflight_steps; the next sync repairs the service - D211 — The pre-release flag is set by the release job and verified there
Repository and documentation policy¶
- D1 — Stdlib-only constraint ✅ removed
- D79 — Open source release: Apache-2.0, GitHub, release-please, GoReleaser, ghcr, Homebrew tap
- D80 — Versioning reset to 0.x: the public line starts at v0.1.0
- D81 — Agent-neutral workflow: AGENTS.md canonical, plans as GitHub issues
- D82 — Beta marking via GitHub pre-release flag, not
-betaversion suffix - D98 — Planned work lives in GitHub issues, not in the docs
- D100 — Community surfaces: Pages from
docs/, Discussions on, Wiki off - D101 — Tool names in the docs are CI-enforced
- D110 — Topic-owned decision records and GitHub-owned project state
- D111 — Repository E2E tests are deterministic and model-free
- D188 — Retired without ever being written
- D198 — No recorded demo in the README
- D202 — One file per decision, with a generated flat index
- D203 — AGENTS.md is the canonical instruction file, and a skill exists once
- D204 — The documentation gates are Go tests inside
make test - D205 — The code map is generated from the package doc comments
- D206 — D163 was a copied off-by-one, not a missing record
- D207 — A claim about a client comes from the provider matrix, and a skill is parsed the way a client parses it
- D208 — A reference is checked wherever it can live, and a decision number has three states
- D209 — The gate is one command, CI runs that command, and a budget measures what the author controls