Commit 1102d43
feat(memory): pluggable semantic embeddings via go-vector v1.3.0 (#27)
* feat(memory): pluggable semantic embeddings via go-vector v1.3.0 HTTPEmbedder
Replace the hardwired RandomProjections usage across all memory similarity
paths with a textEmbedder seam. The default stays RP (local, zero-cost,
unchanged behavior); a new memory.embedding config section routes episode
recall, episode dedup, the non-LLM ranker, and fact merge-on-write through
any OpenAI-compatible embeddings API (Ollama, llama.cpp, OpenAI, ...) for
real semantic similarity instead of bag-of-words overlap.
- internal/memory/embedder.go: EmbeddingConfig + textEmbedder interface;
rpTextEmbedder (corpus-fitted, bigram-featurized) and httpTextEmbedder
(stateless, raw text, text->vector cache, batch misses-only refits).
- episode_index.go: index owns a textEmbedder; persisted vectors carry an
embedding-space fingerprint (episodes_index_meta.json) so switching
provider/model/dims rebuilds automatically while legacy RP gobs keep
loading; failed rebuilds back off 30s so a down backend is never re-hit
on every loop turn.
- episodes.go: embedder factory on EpisodeStore; findDuplicate and the
embedder ranker fail safe (no dedup / recency order) on embed errors.
- merge.go: MergeDetector classifies over the configured embedder; embed
failures degrade to "nobody" (add without merge) instead of misclassifying.
- config: memory.embedding overlay in resolveMemory + NewMemoryManager;
${ENV_VAR} expansion for base_url/api_key; 10s default timeout.
- docs: CONFIG.md field reference + MEMORY.md mechanics/privacy notes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(memory): address PR #27 verification findings (off-lock rebuild + hardening)
AI Verification Protocol pass (agents B/C/D) on PR #27 surfaced one HIGH
finding plus defensive gaps. Repairs:
- HIGH (2.1/2.5 network-I/O-under-lock): episodeVectorIndex.ensureFresh now
fits + embeds the corpus on a FRESH embedder instance OFF the write lock,
then swaps the store/embedder in atomically. A single-flight guard collapses
concurrent rebuilds and a dirty-sequence check folds in a write that lands
mid-rebuild. A slow/remote embedding backend can no longer serialize every
concurrent recall behind one rebuild under odek serve. New test
TestRebuildDoesNotSerializeRecall fails against the old lock-held-on-network
code; whole package stays race-clean.
- cosineVector now guards NaN/Inf (hostile/buggy embedding backend) so ranking
order stays well-defined, mirroring MergeDetector.Classify.
- MergeDetector.ReplaceEntry guards a desynced vecs/corpus (post-failed-Fit)
so it can never index-panic.
- Tests closing the verification coverage gaps: config-loader end-to-end parse
of memory.embedding (C2), api_key ${ENV_VAR} expansion (C10), and the
safety-critical failure-mode net — ranker recency fallback + merge add-without-
merge (C12) and dedup-never-deletes-on-embed-error (C13).
- Docs: corrected the rebuild-cache wording (one batch over the corpus, off
lock), added the base_url egress/SSRF caveat, and the dims:0 model-change note.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(docker): llama.cpp embeddings sidecar + local model for memory.embedding
Adds a private llama.cpp embeddings sidecar so the Docker setup uses real
semantic embeddings for memory (recall, dedup, ranking, fact merge-on-write)
out of the box — completing the consumer side of PR #27's pluggable embeddings.
- docker/Dockerfile.embeddings: builds llama-server from source pinned to the
same llama.cpp release as the main image (b9549, Bookworm base → no ABI
mismatch) and bundles a pinned nomic-embed-text-v1.5 GGUF (768-dim, ~84 MB
Q4_K_M) at a fixed HF revision. No first-run download, no volume — mirrors the
bundled whisper / MiniCPM-V models. Runs --embeddings --pooling mean, non-root.
- docker-compose.yml: llama-embeddings service on all 4 profiles, no host port
(only the odek containers reach http://llama-embeddings:8080), /health
healthcheck, wired into each odek service's depends_on.
- config.restricted.json / config.godmode.json: memory.embedding → the sidecar.
- README.md / .env.example / DOCKER_COMPOSE_USER_GUIDE.md: 'local memory
embeddings out of the box' docs, quant/model override build args, disable path,
and the egress-trust note.
Verified: image builds; container reaches /health in ~20s; /v1/embeddings
returns 768-dim vectors; cosine('fixed the auth bug','repaired the login
issue')=0.70 vs 0.35 for an unrelated phrase — the semantic match RP cannot do.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(docker): make embeddings + searxng integration settings explicit
Both bundled configs already wired memory.embedding -> the llama.cpp sidecar
and web_search -> the SearXNG sidecar; make those integration settings explicit
and more robust:
- memory.embedding: pin dims: 768 (nomic-embed-text-v1.5's output) so the
HTTPEmbedder validates every response length and the index fingerprint is
dimension-aware — a silent model dim change forces an explicit rebuild
instead of self-healing to no-context. Add explicit timeout_seconds: 10.
- web_search: add explicit timeout_seconds: 15 (the SearXNG query ceiling),
so it is visible and tunable rather than relying on the implicit default.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(memory): fix flaky TestRebuildDoesNotSerializeRecall cleanup race
The test launches the blocked-rebuild goroutine and returned right after
close(release), so the rebuild's index-gob writes (saveLocked) could land in
t.TempDir AFTER the test function returned, racing the TempDir RemoveAll
cleanup — CI failed with 'directory not empty'. (Product code unaffected;
purely a test-teardown ordering bug.)
Fix: a deferred releaseBackend()+<-g1 unblocks the backend and waits for the
rebuild goroutine to finish on every exit path (deferred funcs run before
t.Cleanup), so the index is fully written before TempDir is removed. Verified
50x under -race.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>1 parent 71c0609 commit 1102d43
23 files changed
Lines changed: 1650 additions & 143 deletions
File tree
- docker
- docs
- internal
- config
- memory
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
65 | 65 | | |
66 | 66 | | |
67 | 67 | | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
68 | 78 | | |
69 | 79 | | |
70 | 80 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
| 24 | + | |
| 25 | + | |
25 | 26 | | |
26 | 27 | | |
27 | 28 | | |
| |||
187 | 188 | | |
188 | 189 | | |
189 | 190 | | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
190 | 226 | | |
191 | 227 | | |
192 | 228 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | | - | |
| 21 | + | |
| 22 | + | |
22 | 23 | | |
23 | 24 | | |
24 | 25 | | |
| |||
32 | 33 | | |
33 | 34 | | |
34 | 35 | | |
35 | | - | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
36 | 44 | | |
37 | 45 | | |
38 | 46 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | | - | |
| 21 | + | |
| 22 | + | |
22 | 23 | | |
23 | 24 | | |
24 | 25 | | |
| |||
32 | 33 | | |
33 | 34 | | |
34 | 35 | | |
35 | | - | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
36 | 44 | | |
37 | 45 | | |
38 | 46 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
33 | | - | |
| 33 | + | |
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
53 | | - | |
| 53 | + | |
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
| |||
74 | 74 | | |
75 | 75 | | |
76 | 76 | | |
77 | | - | |
| 77 | + | |
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
| |||
93 | 93 | | |
94 | 94 | | |
95 | 95 | | |
96 | | - | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
97 | 125 | | |
98 | 126 | | |
99 | 127 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
206 | 206 | | |
207 | 207 | | |
208 | 208 | | |
209 | | - | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
210 | 218 | | |
211 | 219 | | |
212 | 220 | | |
| |||
231 | 239 | | |
232 | 240 | | |
233 | 241 | | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
234 | 284 | | |
235 | 285 | | |
236 | 286 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
138 | 138 | | |
139 | 139 | | |
140 | 140 | | |
141 | | - | |
| 141 | + | |
142 | 142 | | |
143 | 143 | | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
144 | 152 | | |
145 | 153 | | |
146 | 154 | | |
| |||
0 commit comments