{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://kb.local/wire/v1/search_response.schema.json", "title": "SearchResponse v1", "description": "Top-level wrapper for `kebab search --json` output. Replaces the bare `search_hit.v1[]` array — wraps it with pagination + truncation metadata. Token counts are approximate (chars/4 estimate, no tokenizer dep). On `truncated: true`, caller may either widen `--max-tokens` or follow `next_cursor` for the next page. Stale `next_cursor` (corpus_revision changed since issued) returns `error.v1.code = stale_cursor`.", "type": "object", "required": ["schema_version", "hits", "next_cursor", "truncated"], "properties": { "schema_version": { "const": "search_response.v1" }, "hits": { "type": "array", "description": "search_hit.v1[]" }, "next_cursor": { "type": ["string", "null"], "description": "Opaque base64 cursor for next page; null when no more hits." }, "truncated": { "type": "boolean", "description": "True when budget forced snippet shortening or k reduction. Independent of `next_cursor`: caller may widen `max_tokens` (re-issue same query) or follow `next_cursor` (advance through more hits) or both." }, "trace": { "type": "object", "description": "p9-fb-37: present iff caller passed --trace / SearchOpts.trace=true. Lex/vec pre-fusion lists + RRF union + per-stage timing.", "required": ["lexical", "vector", "rrf_inputs", "timing"], "properties": { "lexical": { "type": "array", "items": { "type": "object" } }, "vector": { "type": "array", "items": { "type": "object" } }, "rrf_inputs":{ "type": "array", "items": { "type": "object" } }, "timing": { "type": "object", "required": ["lexical_ms", "vector_ms", "fusion_ms", "total_ms"], "properties": { "lexical_ms": { "type": "integer", "minimum": 0 }, "vector_ms": { "type": "integer", "minimum": 0 }, "fusion_ms": { "type": "integer", "minimum": 0 }, "total_ms": { "type": "integer", "minimum": 0 } } } } }, "hint": { "type": "string", "description": "v0.17.0 A5 Step 4b: advisory string set when the empty hit list is likely due to a query shorter than the FTS5 trigram tokenizer's 3-char minimum. Field is omitted when no advisory applies. Raw FTS5 mode ('...') opts out. MCP / agent consumers should surface this so users understand the empty result rather than retrying the same short query." } } }