Knowledge-base retrieval
The agent-native knowledge base (the knowledge-base wiki space) is the team's
compounding memory — decisions, conventions, and lessons distilled from prior
work. query_knowledge_base is the first-class, index-first entry point
that lets any agent read it in one call, not just the coordinator brain
(PLT-283).
For agents — query before you implement
Before writing code on a non-trivial task, ask the KB what's already known and cite what you used.
# MCP tool (Claude / Codex / Cursor — any family on one PT_AUTH_TOKEN)
query_knowledge_base(question: "How do we cut a release?")
query_knowledge_base(question: "Tenancy / RLS conventions", spaceSlug: "knowledge-base")
# CLI mirror
npx pt query-kb "How do we cut a release?"
npx pt query-kb --space knowledge-base "What is the tenancy model?"
One call returns the space index (the map of every topic) plus the most
recent synthesis pages, each tagged with its slug. This is index-first
single-pass (PLT-239) — there is no embedding search (PLT-201 closed
embeddings NO-GO); the index is the map. Read it, then drill into a named page
with get_page(page: "<slug>", spaceSlug: "<the returned spaceSlug>") if you
need the full text — pass the spaceSlug the result returned, since slug paths
resolve in the default space otherwise. Cite the slugs in your PR / commit /
reasoning. The consult-the-kb skill is the per-session playbook.
The index itself is hub-and-spoke (PLT-281): the root index is a
topics-only hub, and each topic has its own index_spoke page listing the
"read first" synthesis/summary pages for that topic. The retrieval reads the hub,
selects the relevant spokes (by token overlap — still no embeddings), and
resolves only those, so the read stays bounded as the space grows instead of
embedding one ever-larger flat index. You don't orchestrate this — a single
query_knowledge_base call performs the hub → spoke → page navigation for you.
What the result guarantees
- Caller-scoped. The read runs under your own identity: the wiki's RLS and an UNCLASSIFIED classification cap mean you only ever see pages you are allowed to see — no cross-tenant or over-clearance leakage.
- Capped. Output is bounded to the same context budget the coordinator brain uses (the PLT-279 caps), so a large KB never blows your window.
- Cited. Each synthesis page carries its slug + title + summary.
Robust availability — never a silent absence
query_knowledge_base is registered against the always-present Project Tracker
client, so it never silently disappears. The route distinguishes three states so
a failure is never mistaken for "no knowledge base":
- Backend not configured (
WIKI_ZONE_URLunset) →WIKI_BACKEND_NOT_CONFIGURED(503). - Backend configured but failing (the wiki returns 5xx, times out, or rejects
the forwarded auth) →
WIKI_BACKEND_UNAVAILABLE(502), orWIKI_BACKEND_FORBIDDEN(403) for a 401/403 from the wiki. TheWikiKbSourcethrows on these rather than returning empty, so they surface as a clear upstream error — not asfound: false. - No KB space / nothing groundable (the lookup succeeded, but the tenant has
no such space or no UNCLASSIFIED pages) → an explicit
found: false(200, empty — genuinely "no KB", not a failure).
Architecture — one retrieval path
Retrieval lives once, in @constellation-platform/coordinator's
queryKnowledgeBase. Both the coordinator brain's KB reader and the
agent-facing POST /api/kb/query route call it, so there is a single source of
retrieval truth and a single set of caps.
coordinator brain ─┐
agent MCP tool ────┼─→ queryKnowledgeBase(question, source, …) ─→ WikiKbSource (the seam) ─→ wiki REST API
INF-163 CI reviewer ┘ (selection: index + recency top-k, resolveSpaceId / listPages
UNCLASSIFIED cap, fail closed)
renderKnowledgeBaseSection (also exported from the package) applies the
PLT-279 character caps — it is the single capping path the coordinator prompt
and the agent surface both render through.
The WikiKbSource seam — consumable outside apps/wiki
The only wiki-touching part of retrieval is a small port:
interface WikiKbSource {
// `null` = no such visible space; THROW `KbBackendUnavailableError` on a
// reachable-but-failing backend (so a failure is never read as "no space").
resolveSpaceId(slug: string): Promise<string | null>;
// `[]` = readable-but-empty; THROW `KbBackendUnavailableError` on failure.
listPages(args: {
spaceId: string;
pageType: 'index' | 'synthesis';
limit: number;
}): Promise<KbSourcePage[]>;
}
Everything above the port is pure — no fetch, no Zod, no apps/wiki import —
so the retrieval interface is consumable from anywhere that can supply a
source. This is the GroundingSource seam the INF-163 CI PR reviewer uses to
ground a review against the KB from a CI script, without depending on the wiki
module:
import {
queryKnowledgeBase,
renderKnowledgeBaseSection,
KbBackendUnavailableError,
type WikiKbSource,
} from '@constellation-platform/coordinator';
/** A fetch-backed source for a CI job (its own service/user token). */
function createCiWikiKbSource(wikiBaseUrl: string, token: string): WikiKbSource {
const headers = { authorization: `Bearer ${token}`, 'content-type': 'application/json' };
return {
async resolveSpaceId(slug) {
const res = await fetch(`${wikiBaseUrl}/api/spaces`, { headers });
// Throw on a failing backend so it is never mistaken for "no such space".
if (!res.ok) throw new KbBackendUnavailableError('spaces read failed', res.status);
const { data } = (await res.json()) as { data: Array<{ id: string; slug: string }> };
return data.find((s) => s.slug === slug)?.id ?? null; // null = genuinely absent
},
async listPages({ spaceId, pageType, limit }) {
const url = `${wikiBaseUrl}/api/pages?spaceIds=${spaceId}&pageType=${pageType}&limit=${limit}`;
const res = await fetch(url, { headers });
if (!res.ok) throw new KbBackendUnavailableError(`${pageType} read failed`, res.status);
const { data } = (await res.json()) as { data: Array<Record<string, unknown>> };
return data.map((p) => ({
slug: String(p.slug),
title: String(p.title),
summary: (p.summary as string | null) ?? null,
bodyMd: String(p.bodyMd),
classification: p.classification as string | undefined,
updatedAt: p.updatedAt ? new Date(String(p.updatedAt)) : null,
}));
},
};
}
// In the reviewer: ground the prompt with the same KB the agents read.
const kb = await queryKnowledgeBase({
question: prTitleAndDescription,
source: createCiWikiKbSource(process.env.WIKI_URL!, process.env.WIKI_TOKEN!),
});
const grounding = kb ? renderKnowledgeBaseSection(kb) : '';
The CI source supplies whatever credentials the job holds; the retrieval core applies the UNCLASSIFIED cap regardless, so a CI reviewer can never launder a higher-classified page into an advisory comment. See the multi-LLM PR review page for how that reviewer is wired.