v0.34.0 — Grounded retrieval, bounded multi-hop, co-activation
Kindex can now tell you it doesn't know.
Added
Retrieval grounding. vector_search returned the top-k nearest neighbours for any query however nonsensical, and ask() could only reach its "no relevant knowledge" branch when the result list was empty — which vector search made unreachable. So a near-null question still pulled real nodes into an agent's context, and the graph had no way to say it knows nothing.
hybrid_search now reports a RetrievalVerdict — grounded / weak / ungrounded / uncalibrated — and format_context_block stamps it onto the block. That is the single place rows become context text, so the verdict is a gate rather than something every caller must remember to honour.
The similarity floor is an immutable versioned calibration record keyed by provider:model, carrying the corpus it was measured against. Config holds the percentile policy, never the number — a floor calibrated at low embedding coverage is detected as stale rather than quietly trusted.
kin embed calibrate # measure the null-query distribution
kin embed calibrate --show # read the current recordShadow mode is the default. Enforcing turns a visible, self-correcting problem (irrelevant results in context) into a silent one (an agent proceeding without knowledge that was there). Run it in shadow, see what it would have dropped, then opt in.
Bounded multi-hop expansion. hybrid_search ignored graph_hops and walked exactly one hop from five FTS hits. Store.expand_multihop honours the requested depth with per-hop decay and a mandatory beam whose ordering is total and stable — an unspecified beam makes traversal nondeterministic, which in a provenance-first graph is worse than a slow one. Measured on a 113k-edge graph: 3 hops in 0.7 ms, 4.8× the reach of 1 hop.
Learned pair co-activation (schema v11). A third ranking channel with its own table and auto-ramp, a bounded update that saturates instead of running away, and deposits gated on confirmed use rather than co-retrieval. Never folded into edges.weight, which is asserted topology — merging a learned correction there would destroy the told/inferred distinction.
Extraction engines and an eval gate. Engine output is staged into the capture_candidates quarantine and never writes nodes or edges. kin extract eval scores engines against your own corpus behind a two-part gate: grounding precision as a hallucination floor, title recall as the discriminator — either alone is gameable. New optional kindex[talon] extra, excluded from kindex[all].
kin doctor now reports column-level schema drift, oversized nodes, and silently-recovered failure counters.
Fixed
The stigmergic pheromone channel was dead on every upgraded install. The missed column was added to the v7 CREATE TABLE IF NOT EXISTS after v7 shipped, so any store that had already run v7 never received it and never would — while schema_version still read current. deposit_pheromone raised, the attention hook swallowed it with except Exception: pass, and session state recorded the deposit as successful anyway. Fixed by schema v10, a column-level drift check (a table-existence check is blind to this class), a logged and counted failure in place of the silent swallow, and a caller that records only what the store accepted.
Unbounded dream-cycle merge growth. merge_nodes appended with no cap, and content_overlap compares only the first 500 chars — where machine-generated files are identical. Minified symbols, one class defined in twenty files, a vendored LICENSE: all mutually similar by construction, so every merge was a false positive that grew the target. Caps now refuse rather than truncate.
LLM extraction was silently disabled on multi-key configs. extract.py did a bare os.environ.get on a field llm.py correctly parses as a comma-separated list, and hardcoded the Anthropic SDK while ignoring llm.provider. Both now delegate to llm.py.
similarity_from_distance uses the L2 identity cos = 1 - d²/2. sqlite-vec's vec0 returns L2 distance, not cosine; the naive 1 - d collapses the useful range to zero and calibrates a floor that can never fire.
Not adopted
The VSA/hypervector reservoir. Traversal is already free at this scale, and superposition loses the path — fatal for a graph whose value is auditable provenance. The reasoning is recorded in docs/prd-hillock-adoption-2026-08.md so it doesn't have to be re-derived.
Upgrading
SQLite schema v9 → v11, applied automatically on open. Both migrations are additive, atomic, and column-verified. Run kin doctor afterwards to confirm no drift.
1927 tests passing.