Commit 6c25213
authored
feat: semantic search with run-grouped embeddings and conversation-unit citations (#999)
Adds opt-in semantic/vector search over conversation content alongside the existing substring/regex/FTS modes, and gives every content-search match — in every mode, on all three backends — a conversation-unit citation.
## Semantic search
- `session search --semantic` and `--hybrid` (reciprocal-rank fusion of the vector and FTS legs), plus `--scope top|all|subordinate` to control whether subordinate evidence (sidechain runs, subagent/fork sessions) is shown; subordinate hits are rank-penalized and annotated, never silently hidden.
- Embedding documents are run-grouped: each user message is one document, and each unbroken run of assistant/tool messages between user turns is concatenated into one document (~25x fewer assistant-side documents than per-message embedding). Semantic hits anchor on the message containing the best-matching chunk's center and carry the run's ordinal span.
- `embeddings build/list/activate/retire` manage the index: through the daemon when one is running, directly (flock-guarded) otherwise. `serve` wires a debounced after-sync scheduler when `[vector]` is enabled.
- Embeddings come from any OpenAI-compatible endpoint (`[vector]` in config.toml; Ollama quickstart in the docs). An optional `input_suffix` appends a client-side terminator to every embedded text for models that need one (e.g. Qwen3-Embedding's `<|endoftext|>` under llama.cpp); it joins the generation fingerprint.
- `session messages --around N --before/--after --role` retrieves context windows around any hit on all three backends; `session search --context N` inlines them.
## Conversation-unit citations
Every match now carries `ordinal_range: [start, end]` — the conversation unit enclosing the anchor — plus `subordinate`, `relationship`, `parent_session_id`, and `is_sidechain`. `ordinal` remains the exact matched message in every mode.
- Row cardinality is mode-specific by design: lexical modes stay grep-like (one row per matching source row), semantic returns one row per embedded unit, hybrid one row per unit with exact-match anchors.
- Lexical and hybrid unit-less rows derive their unit structurally from the messages/sessions tables — deterministic, identical on SQLite/PostgreSQL/DuckDB, and independent of whether a vector index exists. A property test pins derivation to exact equivalence with the embedding reducer's unit spans.
- Derivation is post-scan and O(page): batched correlated point lookups with run sharing, ~0.4-0.65ms added per 50-hit page on the gated benchmarks.
- Surfaces: CLI renders `#start-end @anchor` with a `sub` marker; MCP `search_content` carries the same fields; the OpenAPI schema and generated client are updated.
## SQLite DSN hardening
Read-only connections were silently read-write: mattn/go-sqlite3 ignores `mode=ro` without a `file:` URI scheme. Fixed for sessions.db, vectors.db, and every foreign-app parser DB (which were also being converted to WAL on read). The fix exposed and fixed a WAL close-ordering bug in the resync swap flow. Paths are percent-escaped; read-only enforcement is pinned by tests.
## Architecture
Vectors live in a separate `vectors.db` (SQLite + sqlite-vec via go.kenn.io/kit), a mirror keyed by resync-stable doc keys with generations fingerprinted by model/dimension/unit-scheme config; a mirror schema version gates cross-version reads (rebuild-required surfaces as 501 with remediation) and resets stale mirrors on writable opens. Staleness, first-build progress, and endpoint outages surface as distinct errors across CLI/HTTP/MCP. See `docs/semantic-search.md` (usage) and `docs/semantic-search-internals.md` (unit model, doc keys, derivation invariants, fusion, error taxonomy).
## Where to look
- `internal/db/messages.go`, `internal/db/unit_range.go` — run reducer and shared unit-range derivation (the correctness core; reducer-equivalence property test).
- `internal/vector/` — mirror, generations, chunk-anchor resolution, build orchestration, encoder, search.
- `internal/db/search_content*.go` — semantic/hybrid modes, unit-granularity fusion, scope filtering, lexical citation enrichment.
- `internal/postgres/unit_range.go`, `internal/duckdb/unit_range.go` — SQL-only backend seams over the shared resolvers.
- `cmd/agentsview/embeddings.go`, `embed_scheduler.go` — CLI group, serve wiring, scheduler.
- `internal/vector/encoder.go`, `internal/vector/build.go` — build throughput: requests use `encoding_format: "base64"` (~4x smaller responses, with transparent float fallback for servers that reject or ignore the field), and a `[vector.embeddings] concurrency` key (default 4) embeds documents in parallel via kit's new `FillOptions.Concurrency` (kenn-io/kit#27; go.mod pins that PR's commit and should move to a tagged kit release before merge). Saves stay serialized, preserving the single-writer model. Sequential float-JSON requests left builds round-trip-bound against remote endpoints; measured on a slow WireGuard link, these two changes took a full-archive build from ~46 to ~700 chunks/min.
- Frontend diff is regenerated API-client output only; the web UI remains FTS-only in this release.
## Limitations
- Semantic/hybrid search is SQLite-archive only; `pg serve`/DuckDB validate and report it unavailable (citations work on all three).
- Metadata filters post-filter the vector leg (over-fetch mitigates recall loss); narrow scopes can under-fill a page past the batched FTS-leg cap.
- Citation derivation adds ~19-21% to content-search page latency (sub-millisecond absolute); levers (covering index, statement cache) documented but not pulled.
- Draft: kept open for real-world exercise before merge.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Wes McKinney <wesm@users.noreply.github.com>1 parent 73bb116 commit 6c25213
187 files changed
Lines changed: 26852 additions & 291 deletions
File tree
- cmd/agentsview
- docs
- frontend/src/lib
- api/generated
- models
- services
- components/activity
- internal
- config
- db
- duckdb
- mcp
- parser
- postgres
- server
- service
- skills
- templates
- timeutil
- vector
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
120 | 120 | | |
121 | 121 | | |
122 | 122 | | |
| 123 | + | |
| 124 | + | |
123 | 125 | | |
124 | 126 | | |
125 | 127 | | |
| |||
128 | 130 | | |
129 | 131 | | |
130 | 132 | | |
131 | | - | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | | - | |
136 | | - | |
137 | | - | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
143 | | - | |
144 | | - | |
145 | | - | |
146 | | - | |
147 | | - | |
148 | | - | |
149 | | - | |
150 | | - | |
151 | | - | |
152 | | - | |
153 | | - | |
154 | | - | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
155 | 160 | | |
156 | 161 | | |
157 | 162 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
277 | 277 | | |
278 | 278 | | |
279 | 279 | | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
280 | 285 | | |
281 | 286 | | |
282 | 287 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
115 | 115 | | |
116 | 116 | | |
117 | 117 | | |
| 118 | + | |
118 | 119 | | |
119 | 120 | | |
120 | 121 | | |
121 | 122 | | |
122 | 123 | | |
123 | 124 | | |
| 125 | + | |
124 | 126 | | |
125 | 127 | | |
126 | 128 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
202 | 202 | | |
203 | 203 | | |
204 | 204 | | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
205 | 209 | | |
206 | 210 | | |
207 | 211 | | |
208 | 212 | | |
209 | 213 | | |
210 | | - | |
| 214 | + | |
| 215 | + | |
211 | 216 | | |
212 | 217 | | |
213 | 218 | | |
| |||
0 commit comments