ask_knowledge_base assembles a synthesis context that nothing bounds. SearchService.ask() → QueryService.queryDocuments({limit}) reads each hit's whole file and joins them into the prompt:
No truncation, no character budget, no token cap anywhere on that path — the only .slice(0, 500) in the file is on an error message. Request cost is therefore decided by whatever ranks top-limit.
Measured (local plane, deployedRevision 271bb132d8, nothing else running — the best case, not the bad one):
limit: 5 — the shipped default
-32001 at the agent seat's deadline
same query, long client deadline
42,973 ms
context for that query (3 of its 5 docs)
~9,400 tokens
five largest corpus documents
~73,900 tokens
context-engineering-done-right.md alone
18,800 tokens
limit is a proxy for cost, not a bound on it. Two large documents exceed a deadline that five small ones fit inside, so lowering the default relocates the cliff instead of removing it.
Corrected after @neo-gpt-emmy's Stage-2 challenge. This ticket originally specified a "caller-deadline-derived" budget. That authority does not exist:
No request-scoped deadline reaches the service. The public ask schema has no deadline coordinate, KB's buildRequestContext() carries identity only, and ai/services/knowledge-base/ contains zero occurrences of deadline.
The client deadline is not a citable constant either. It appears in ai/ only as prose in one OpenAPI description ("the canonical 60000 ms MCP request deadline") — no named constant, nothing a use site could read.
So a static AiConfig leaf can bound context; it cannot honestly be called derived from a caller coordinate. Naming an authority that does not reach the code is the exact error this correction is fixing, so this body will not do it a second time.
What is true, and it is sharper than the original claim. A server-side bound already exists: askSynthesis.timeoutMs defaults to 300,000 ms. The measured seat abandons below 43,000 ms. The ask path keeps working for roughly seven times as long as any caller waits, and the two numbers are set against each other by nothing. That is #16706's FIX-1 — never dispatch a request we cannot cancel and will not wait for — violated not by a missing bound but by a bound answerable to no caller.
Severity:AGENTS.md §edge_case_triggers mandates this tool as the FIRST step for Neo concepts, so a compliant seat reaches a timeout by following the rule. Field effect: five peers, zero calls since the provider switch.
Acceptance criteria
A context budget in characters or tokens bounds the assembled synthesis context, resolved from an AiConfig leaf at the use site (no module-level re-derivation, no env re-read, no hidden default, no defensive ?. — ADR 0019 §3).
The budget is documented as operator-configured and validated below the observed client abandonment point, with the measurement recorded. It is not described as derived from a caller deadline, because no caller deadline arrives.
A single oversized document cannot exhaust the budget on its own — per-document contribution is capped, not just the total.
When the budget truncates, the answer says so. A caller must never receive a confidently-scoped answer built from silently dropped context.
limit semantics and default are unchanged — the default is not the defect.
Spec: a body whose top hits exceed the budget yields a truncated-and-declared context; one within the budget is byte-identical to today.
Spec: the truncation notice is asserted on the returned answer text, not on an internal flag.
Spec: a single document larger than the whole budget still leaves room for at least one other document — the per-document cap is convicted, not assumed.
over-budget input truncates and declares it; never silently drops
spec arms above + a within-budget byte-identity control
Returned answer payload
this ticket
carries an explicit truncation notice when truncation occurred
no notice when no truncation
assertion on answer text
Out of scope
The limit default — not the defect.
A real request-deadline authority. Plumbing a caller deadline into RequestContext is a separate contract with its own justification; this leaf must not pretend to have one. Worth filing only if a second consumer needs it.
Endpoint isolation (#17000) and model selection (#17001).
Host memory allocation — @neo-gpt-sol's lane.
Post-Merge Validation
None deferred.
Authored by @neo-opus-vega
Deltas
ai/services/knowledge-base/SearchService.mjs — a bounded, per-document-capped context assembly with declared truncation.
ai/mcp/server/knowledge-base/configBase.mjs — the budget leaf, beside the existing askSynthesis block.
Evidence: L2 (spec arms over the assembly path, including a byte-identity control) → L2 sufficient: every AC here is decidable in-process. No residuals.
Test Evidence
To be recorded on the implementing PR.
tobiu referenced in commit b097ea6 - "feat(knowledge-base): bound the ask synthesis context and declare what it dropped (#16999) (#17002) on Aug 12, 2026, 9:14 AM
Refs #16998 Refs #16706
Problem
ask_knowledge_baseassembles a synthesis context that nothing bounds.SearchService.ask()→QueryService.queryDocuments({limit})reads each hit's whole file and joins them into the prompt:const contextDocs = (await Promise.all(contextPromises)).join('\n\n');No truncation, no character budget, no token cap anywhere on that path — the only
.slice(0, 500)in the file is on an error message. Request cost is therefore decided by whatever ranks top-limit.Measured (local plane,
deployedRevision 271bb132d8, nothing else running — the best case, not the bad one):limit: 5— the shipped default-32001at the agent seat's deadlinecontext-engineering-done-right.mdalonelimitis a proxy for cost, not a bound on it. Two large documents exceed a deadline that five small ones fit inside, so lowering the default relocates the cliff instead of removing it.Corrected after @neo-gpt-emmy's Stage-2 challenge. This ticket originally specified a "caller-deadline-derived" budget. That authority does not exist:
buildRequestContext()carries identity only, andai/services/knowledge-base/contains zero occurrences ofdeadline.ai/only as prose in one OpenAPI description ("the canonical 60000 ms MCP request deadline") — no named constant, nothing a use site could read.So a static
AiConfigleaf can bound context; it cannot honestly be called derived from a caller coordinate. Naming an authority that does not reach the code is the exact error this correction is fixing, so this body will not do it a second time.What is true, and it is sharper than the original claim. A server-side bound already exists:
askSynthesis.timeoutMsdefaults to 300,000 ms. The measured seat abandons below 43,000 ms. The ask path keeps working for roughly seven times as long as any caller waits, and the two numbers are set against each other by nothing. That is #16706's FIX-1 — never dispatch a request we cannot cancel and will not wait for — violated not by a missing bound but by a bound answerable to no caller.Severity:
AGENTS.md §edge_case_triggersmandates this tool as the FIRST step for Neo concepts, so a compliant seat reaches a timeout by following the rule. Field effect: five peers, zero calls since the provider switch.Acceptance criteria
AiConfigleaf at the use site (no module-level re-derivation, no env re-read, no hidden default, no defensive?.— ADR 0019 §3).limitsemantics and default are unchanged — the default is not the defect.Contract Ledger
SearchService.ask()context assemblyAiConfigleaf, read at the use siteOut of scope
limitdefault — not the defect.RequestContextis a separate contract with its own justification; this leaf must not pretend to have one. Worth filing only if a second consumer needs it.Post-Merge Validation
None deferred.
Authored by @neo-opus-vega
Deltas
ai/services/knowledge-base/SearchService.mjs— a bounded, per-document-capped context assembly with declared truncation.ai/mcp/server/knowledge-base/configBase.mjs— the budget leaf, beside the existingaskSynthesisblock.Evidence: L2 (spec arms over the assembly path, including a byte-identity control) → L2 sufficient: every AC here is decidable in-process. No residuals.
Test Evidence
To be recorded on the implementing PR.