D151 — A KB's capabilities and mount provenance are visible from a session¶
Status: implemented (2026-08-28). Closes #172.
Context. The server knows its own configuration and never volunteered it. An agent could
enumerate 33 tools and read 600 concepts and still not answer "what am I allowed to do in this
KB, and what is switched off?" In the field that was the single largest block of time in a whole
migration: 23 skills, 11 templates, 2 agents and instructions.md were written by editing files
in the KB clone and committing, and removing an agent was a git rm — because artifact_write
appeared not to exist. It existed, behind a per-KB flag defaulting to false.
Four client-visible surfaces could each have revealed it; none did. tools/list omits the tool.
tools/call fails as unknown — collapsing not registered, hidden by profile and absent from
this build into one message. kb_status returned 20 fields and not one was a capability:
exhaustive about git, silent about permissions. doctor reported missing hooks and unverifiable
managed files, never disabled capabilities. Meanwhile artifact_read's own description named
artifact_write/artifact_delete unconditionally — so when tools/list did not contain them,
the natural inference was that the docstring was stale, not that a hidden flag existed. The
signal pointed the wrong way.
Behind several of those gates being off at once sat one invisible cause: serve.go mounts every
subdirectory of data: it finds, and such a mount has no KBSpec, so AllowArtifactWrite
is false, there is no tool_prefix, no sops_age_key_file and no
machine_path_allow_prefixes — while kb_status reported a remote, an unpushed count and
concept totals identical in shape to a configured KB.
Decision.
- One capability surface, and everything else references it.
kb_statusgains acapabilitiesobject: each gate's state plus the configuration key that controls it, because a state without the name of the switch is only half an answer./healthserves the same map from the same derivation (KBCapabilitiesFor), so the MCP tool and the HTTP endpoint cannot drift. kb_statusstays read-only and offline: every input is in-process state, so its own promise still holds. No path and no secret material appears — the SOPS key is reported as configured or not, never as a path, so the host's layout does not leak into a transcript.- An unknown-tool failure for a name this build implements but did not register says which
setting enables it. The table lives next to
advancedToolNames, because the two mechanisms are indistinguishable from a client and are not the same thing: an advanced tool is registered and callable, merely unlisted; a gated one is not registered at all. Keeping them in one file is how that difference stays documented. artifact_read's description names the gate, so it stops reading as a stale docstring.- Mount provenance is reported, not corrected. A discovered KB keeps working exactly as
before; startup warns once per discovered KB,
kb_statussaysmount: discovered, anddoctorraises an infocapabilityfinding. Auto-promoting it to a syntheticKBSpecis deliberately out of scope: it would invent configuration the operator never wrote.Discoveredis carried explicitly rather than inferred from an emptySpec, becausekbs: [{path: ...}]with no options also has a zeroSpecand that KB is configured — it chose the defaults. - The default of
allow_artifact_writedoes not change. Writing a provisioning artifact injects instructions a client agent will execute: the gate is a security boundary, and D71's rationale is about the pair's visibility in the tools profile, not about the gate. This entry removes the invisibility, not the gate. Flipping the default is a separate decision. doctorstays silent when it cannot observe: an unreachable server, or one that advertises no capabilities, produces no finding. A missing signal must not become "all good" — the same rule the sync-timer check follows.
Consequences. Additive: one new kb_status field, one optional /health field, new info
findings in doctor, one new startup warning, one changed error message. No config migration and
no default changed.