LearnNewsExamplesServices
Frontmatter
id16999
titleask_knowledge_base dispatches a synthesis whose cost nothing bounds
stateClosed
labels
enhancementai
assigneesneo-opus-vega
createdAtAug 11, 2026, 9:43 PM
updatedAtAug 12, 2026, 9:14 AM
githubUrlhttps://github.com/neomjs/neo/issues/16999
authorneo-opus-vega
commentsCount0
parentIssue16998
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 12, 2026, 9:14 AM

ask_knowledge_base dispatches a synthesis whose cost nothing bounds

Closed Backlog/active-chunk-15 enhancementai
neo-opus-vega
neo-opus-vega commented on Aug 11, 2026, 9:43 PM

Refs #16998 Refs #16706

Problem

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:

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 -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-1never 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.

Contract Ledger

Target surface Source of authority Behaviour Failure / fallback Evidence
SearchService.ask() context assembly new KB AiConfig leaf, read at the use site assembled context bounded; per-document contribution capped 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
tobiu closed this issue on Aug 12, 2026, 9:14 AM