One search command, three response shapes - #678
Merged
Conversation
Finishing what #670 started and I then left named-but-undone in its own PR body. progressive_index/3 and get_context/3 are not different searches. The first calls search_keyword/3 and then caps-and-stubs; the second is the combined search returning full bodies. Exposed as separate TOOLS they asked an agent to decide, per query, which door to knock on. That decision is the problem, and it is not an ergonomic one. An agent's choice of entrypoint is unobservable, so it confounds every measurement of the ranking behind it - you cannot separate an algorithm's effect from a choice you cannot see. A parameter is a variable we control; a tool choice is a confounder we do not. The single command is therefore the precondition for judging any ranking change from observed behaviour, not a tidying-up before it. knowledge_search now takes format: results (default) - ranked results plus snippets, unchanged. The only shape that supports keyset pagination, which is why the dispatch sits on the :none cursor branch. stubs - capped stubs with hub enrichment, for surveying a broad topic without pulling bodies into context. bodies - full bodies plus linked references, for one deep read. Nothing is retired. The sibling tools and endpoints stay and now share this path. Two deliberate refusals. An unknown format is a 400 rather than a silent fallback to the ranked shape, because falling back answers a different question than the one asked. A shaped format with no query is also a 400: these are relevance shapes, there is no stub rendering of an enumeration page, and quietly returning ranked results would do the same thing. Telemetry attributes every shape to knowledge_search, with the shape in mode_used. Recording a shaped call under the sibling tool's name would split one command's traffic across three names and undo the comparability the parameter exists to create. Mutation-verified in four directions: an unknown format falling through, the shaped call recorded under the sibling name, the shaped telemetry removed entirely, and a query-less shaped request allowed - each turns tests red. format is documented in the endpoint's operation/2 spec.
mkreyman
enabled auto-merge (squash)
August 12, 2026 18:13
mkreyman
added a commit
that referenced
this pull request
Aug 12, 2026
Server-side landed in #678; this is the half agents can actually reach. knowledge_search takes format: results (default), stubs, or bodies. These are response SHAPES of one search, not different searches - the server dispatches stubs to the same progressive_index/3 and bodies to the same get_context/3 that knowledge_progressive_index and knowledge_context call. Nothing is retired. Both sibling tools stay registered and behave exactly as before. The schema says so in words and a test asserts it, so a later reader does not mistake this for a deprecation. The description states both refusals rather than leaving an agent to discover them: stubs and bodies REQUIRE a query, and an unknown value is a 400 rather than a quiet downgrade to results. An agent expecting a silent downgrade would read the error as an empty corpus. ON THE TEST, which is deliberately not written like its neighbours. The suites here re-implement handler bodies by design, since index.js has top-level await and exports nothing. That strategy structurally cannot catch the one failure that matters for a pass-through parameter: the schema advertising a field the request builder drops. A re-implementation forwards it correctly and passes while the real code does not. So this asserts against the index.js source, and mutation-verified it catches exactly that - deleting the params.set line with the schema left intact turns it red, as does removing a shape from the enum. That is the third instance today of the same shape - a value computed or declared in one layer and silently lost in the next, with a test green because it checked a thing existed rather than what it contained (#669 read the wrong keys off a result map, #672 never rendered keys onto one). Worth naming as a pattern rather than three coincidences. Version 2.74.0 with a CHANGELOG entry. Publishing to npm is a separate step and is NOT done here: until it is published and sessions restart, agents keep the 2.73.0 schema - the same staleness that cost the first day of search telemetry.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Finishing what #670 started, and what I named in its own PR body as remaining and then
didn't come back to.
Not an ergonomics change
progressive_index/3andget_context/3are not different searches. The first callssearch_keyword/3and then caps-and-stubs; the second is the combined search returning fullbodies. Exposed as separate tools, they asked an agent to decide, per query, which door to
knock on.
That decision is the problem. An agent's choice of entrypoint is unobservable, so it
confounds every measurement of the ranking behind it — you cannot separate an algorithm's
effect from a choice you cannot see. A parameter is a variable we control; a tool choice is a
confounder we do not.
So the single command is the precondition for judging any ranking change from observed
behaviour, not tidying-up to do before it.
knowledge_search?format=results(default):nonecursor branch.stubsbodiesNothing is retired. The sibling tools and endpoints stay and now share this path.
Two deliberate refusals
answers a different question than the one asked.
rendering of an enumeration page, and quietly returning ranked results would do the same
thing.
Telemetry
Every shape is attributed to
knowledge_search, with the shape inmode_used. Recording ashaped call under the sibling tool's name would split one command's traffic across three
names and undo the comparability the parameter exists to create.
Verification
mix precommitgreen (7,514 tests). Mutation-verified in four directions — each turns testsred:
formatis documented in the endpoint'soperation/2spec per the doc-hygiene rule.Next in the agreed order
MCP
knowledge_searchgainsformat(server-side is done and usable via REST today), thengrowing the golden set from logged queries via cross-search consensus, then offline replay.
Variant assignment last, if ever — at ~12 agent searches/day an online experiment would
produce confident noise.
Reviewed inline (this session's system prompt forbids dispatching review agents; per CLAUDE.md
the gate is satisfied by the best available reviewer with that stated).