D153 — A tool prefix is the default for every mounted KB¶
Status: implemented (2026-08-28). Supersedes D102's default; requires D152. Closes #174.
Context. Prefixing was opt-in and its absence was silent. mcp.tool_prefix_mode defaulted to
off, so with two or more KBs mounted every KB registered the same tool names. On a client with a
flat tool namespace one of them wins and the others become unreachable — a question about KB A
answered from KB B, with no error at call time and nothing in the logs, only a startup warning
the operator may never read. Silent wrong answers from a plausible-looking source are the worst
failure class a knowledge base can have, and that was the default configuration's behaviour.
The portability argument is the one that bit in daily use. Because the prefix was an optional,
server-side choice, a tool name was not a stable identifier for a KB's capability: the same KB
is concept_read on one deployment and <x>__concept_read on another. That contradicts the rest of
the model, where a concept ID is stable and portable by construction. The product already knew it —
generateKBInstructions takes a toolPrefix and qualifies every tool name it imprints, precisely
"so a prefixed KB does not imprint tools the agent cannot call" — but skill bodies get no such
treatment: turning on a prefix for one KB forced rewriting 25 tool citations inside skill bodies
across two KBs, because every naked concept_write in a skill had silently become wrong.
D102's objection was that keeping the mode off lets deployments on per-server-namespacing clients work untouched. That optimises for not renaming existing tools, at the price of making correctness depend on which client happens to connect. A KB's identity in the tool namespace is a property of the KB, not of the consumer — and the consumer that gets it wrong fails silently.
Decision.
Default()returnsToolPrefixMode: "kb-name". An explicitkbs[].tool_prefixstill wins, unchanged: nothing an operator wrote down is overridden.offis retained, explicitly and documented, as the opt-out for single-KB deployments and for anyone who cannot absorb the rename in this release. It is not deprecated here.- The single-KB case is reserved. With exactly one KB mounted a prefix is pure noise, so no
prefix is derived and tools keep bare names. Implemented by overriding the mode for that case
rather than by changing
ResolveToolPrefix, which stays a pure function of (spec, mode, name) — the reservation is a deployment-shape decision, not a resolution rule, and routing it through the mode is what keeps an explicittool_prefixworking on a single-KB mount for free. - A derived prefix is announced at startup, once per KB, only when it was derived rather than configured: an operator who wrote the prefix does not need telling, but adding a second KB to a previously-single-KB deployment renames the first one's tools and that must be loud.
- Not in scope: making skill bodies prefix-independent. A placeholder expanded at
materialization time (the way
{{repo:...}}already is) would be the fix, and coupling a config default to the provisioning expander in one change is how both get broken. Recorded here as follow-up work rather than attempted.
Consequences. Breaking for multi-KB deployments: tool names change on first start, so the PR
title is a feat: and release-please computes a minor bump. The generated steering block
follows automatically; hand-written tool citations in skill bodies do not, which the release note
must say.
A name that sanitises to empty or starts with a digit now fails fast for a KB that previously
mounted fine, so the message names the derivation and mcp.tool_prefix_mode: off as the escape.
Three e2e scenarios asserted on bare tool names with two KBs mounted: 10_scoped_tokens and
14_rbac_visibility pin off (they assert on RBAC, not on naming) and 16_prefixed_multikb pins it
for its mixed prefixed/unprefixed phases, then gains a fourth phase that exercises the new default
end to end — derived prefixes on both KBs, the announcement in the log, /health advertising them,
and the bare name no longer resolving. Pinning the old mode in a test is also exactly the migration
path a real deployment takes, so the suite documents it.