Skip to main content

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_URL unset) → WIKI_BACKEND_NOT_CONFIGURED (503).
  • Backend configured but failing (the wiki returns 5xx, times out, or rejects the forwarded auth) → WIKI_BACKEND_UNAVAILABLE (502), or WIKI_BACKEND_FORBIDDEN (403) for a 401/403 from the wiki. The WikiKbSource throws on these rather than returning empty, so they surface as a clear upstream error — not as found: 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.