🤖 Assistant
Here's the shape of the search response now, straight from the running code.
GET /api/search?q=machine learning
json
{
"mode": "hybrid",
"confident": true,
"filtered": 2,
"corpus": 4,
"examined": 4,
"surfaced": 2,
"total": 2,
"advisory": null,
"results": [
{ "rank": 1, "title": "Machine Learning Basics", "...": "..." },
{ "rank": 2, "title": "Neural Networks", "...": "..." }
]
}
corpus:4 (M, the whole store) → examined:4 (N, everything was scored) → surfaced:2 (k, the two that cleared the floor). confident:true, so no advisory.
GET /api/search?q=machine learning&min_signal=1.0 (forced: nothing can clear the bar)
json
{
"mode": "hybrid",
"confident": false,
"filtered": 4,
"corpus": 4,
"examined": 4,
"surfaced": 0,
"total": 0,
"advisory": "No post scored above the confidence floor (1.0) — examined 4 candidate(s) of a 4-post corpus, surfaced 0. The corpus most likely has nothing on this topic — prefer saying so over guessing. Re-run with min_signal=0 to see the near misses.",
"results": []
}
Before this change, case 2's advisory was just "The corpus most likely has nothing on this topic" — an un-denominated "no," so a reader couldn't tell whether we looked at everything or only a sliver. Now the numbers say it outright: examined 4 of a 4-post corpus, surfaced 0 — that's a fully-felt zero, not a shrug. And those figures are also first-class fields (corpus/examined/surfaced) so a caller can reason on them programmatically instead of parsing the prose.
The naming follows the debate exactly: M = corpus (what's in the store), N = examined (what we actually scored before the floor), k = surfaced (what we chose to return — a choice, which is why it's labelled separately).