Gå til hovedinnhold

AD-037: The context retrieval surface

Summary

A project's content context is retrieved through two primitives, offered identically on the CLI and over MCP:

ShapeQuestionCLIMCP
By locationwhat applies here?kapi context <path>context://<path> resource
By contentwhat do we know about this?kapi context search <query>context_search tool

Every asset-shaped lookup folds into one of the two. There is no separate retrieval command per store — not for voice, not for terms, not for content memory — because "which store holds the answer" is an implementation detail the caller should never have to know.

The two surfaces are held in parity by construction: the MCP tools are thin wrappers over the same host functions the CLI verbs call, and a conformance test asserts the retrieval surfaces agree.

Context

The surface was generated, not designed

host/mcp_tools.go exposed every CLI-visible framework tool over MCP. In ad-hoc mode that produced 51 tools, of which roughly 12 were deliberately authored. Adding a pipeline step silently added an agent-facing tool, so the surface accumulated whitespace-correct, encoding-detect, inline-codes-remove and xml-validation — flow steps no agent should call — alongside external-command ("Executes an external command on block text") and script ("Run a JavaScript processing script"), which were exposed to any MCP client by inheritance rather than by decision.

Retrieval was asset-shaped on both surfaces, and the two disagreed

Before this AD, retrieval was a command per store, and MCP exposed an arbitrary subset of what the CLI offered:

CLIMCP
kapi brand guidebrand_guideboth
kapi brand showCLI only (and a duplicate of brand guide)
kapi brand profilesCLI only
kapi terms lookupterm_lookupboth
kapi terms searchCLI only
kapi memory lookupCLI only
kapi memory searchtm_searchboth

Six CLI retrieval verbs, three MCP tools, no stated rule for which got exposed. The agent skill drives the CLI, so an assistant following the skill and an assistant calling MCP tools learned two different models of what kapi holds.

Why asset-shaped retrieval is the wrong shape

A caller does not have a store in mind. It has a question:

Communication is contextual — the assistant asking what applies here, the check asking does this rule fire, and the engine asking may this approved wording be reused are one question asked by three callers.

Asset-shaped retrieval forces the caller to already know the answer's location before asking, which inverts that. Worse, it returns partial answers that read as whole ones: brand_guide renders a profile's own PreferredTerms / ForbiddenTerms, so its output visibly contains terminology — while the project's terms store, a separate source reaching the engine by a separate path, went unread. A caller that writes against that output has terminology it did not get. Silence would have been safer.

Decision

One question, two shapes

Retrieval is addressed by location or by content, never by store.

By location answers what applies here: the profile in force, its rendered guidance, the terms that apply, and any scoped rules. It resolves through core/brand.ResolveContext (workspace → project → stream → collection) and, as the coordinate work lands, file and passage overrides beneath it.

By content answers what do we know about this: one query, every store the scope holds — terms and concepts, content memory, profile examples.

Two address forms, one thing

The by-location primitive is addressable two ways, because an ad-hoc run has no path to resolve:

context://<path> what applies at this location
context://profile/<name> what a named profile holds (packs, ad-hoc, "as if")

CLI mirrors this as kapi context <path> and kapi context --profile <name>.

Rendering is a property, not a command

The by-location primitive renders as prose for a model (text/markdown) or structured for a program (application/json). MCP resources carry a mime type, so this is a property of the resource rather than a second entry point; the CLI expresses it as --json.

This is what dissolves brand_guide. It conflated three concerns — voice only, by name only, markdown only — into one narrow point. Once content is the whole context, addressing covers by-name, and rendering is a property, nothing is left over.

What folds in

Retired from the retrieval surfaceFolds into
brand_guide, brand showkapi context / context://
term_lookup, terms lookup, terms searchkapi context search / context_search
tm_search, memory lookup, memory searchkapi context search / context_search

Management verbs are not retrieval and do not fold. terms import/export/ stats, memory import/export/audit/sessions, brand new/validate/import/pack stay exactly as they are — they operate on a store deliberately, which is a different act from asking a question.

Content memory is recycled, not searched

Recycling is the loop's job and is invisible by design: up pre-fills from content memory as the structural cost control. So the retrieval surface does not offer "search memory for prior translations" — that duplicates work already done, and inviting a caller to do it by hand is inviting it to hand-crank the loop. recycle and diff-leverage leave the agent surface for the same reason.

What is served is precedent: how has this project said this before, when authoring source content. That is a different question from recycling, it is not answered anywhere else, and it is what makes a caller's own writing sound like the project. It reaches callers through context search, not a memory-specific verb.

Reach, not capability

The same call returns less locally, and says so. kapi resolves a terms store, a content memory and a profile; bowrain resolves the full concept graph with relations, revisions and market scoping. A local result set is explicitly scoped in its response rather than silently thinner — a caller must be able to tell "this project holds no answer" from "this scope cannot hold one".

Results are grouped, never merged into one ranking

A term match and a memory match are not comparable scores. Results are grouped by kind, each group ranked within itself. A single blended list would impose an order that means nothing.

The generated surface becomes opt-in

MCP exposes a curated set by default. KAPI_MCP_ALL_TOOLS=1 restores the full generated surface for debugging and power use.

external-command and script are not part of that flag. "Show me every tool" and "let a caller execute arbitrary commands and JavaScript" are different classes of decision, and bundling them means enabling the first silently grants the second. They are not exposed over MCP; kapi exec still runs them.

Cutting MCP exposure removes nothing from the CLI. kapi exec <tool> runs every registry tool regardless.

Consequences

  • The skill and the MCP client learn the same model. The agent skill drives the CLI, so parity is what stops the highest-leverage prose in the repo from teaching a surface that MCP does not have.
  • Callers stop needing a map of the stores. The question is the interface; where the answer lives is ours to change.
  • Partial answers stop reading as whole ones — the failure that made brand_guide actively misleading rather than merely narrow.
  • A new registry tool no longer becomes an agent tool by accident. Exposure is a decision with a name attached.
  • The by-location primitive ships shallower than it will end. Until file and passage overrides land, it resolves to collection level; the migration-guide case — the old name is permitted in these two paragraphs — needs the coordinate work. The by-content primitive has no such dependency.
  • Parity is a test, not a convention. MCP tools wrap the same host functions the CLI verbs call, and a conformance test asserts the surfaces agree — the drift that produced the table above cannot recur silently.