Commit 2d63470
Centralized embedding backend: semantic memory, session_search & skill matching (#28)
* Extract shared internal/embedding package from memory
Phase 1 of bringing sessions and skills to embedding parity with memory.
The text-embedding backends (RandomProjections + OpenAI-compatible HTTP),
their config shape, fingerprinting, and persistence were package-private
inside internal/memory, so sessions and skills could not reuse them.
Move them to a new internal/embedding package with an exported surface:
- Config (was EmbeddingConfig)
- TextEmbedder interface (exported methods)
- New / NewRP constructors (was newTextEmbedder / newRPTextEmbedder)
- Cosine similarity helper (was memory's cosineVector)
- the bigram featurization (featurize.go)
Memory keeps package-local aliases (EmbeddingConfig, textEmbedder) and thin
constructors so all its call sites and the on-disk .gob/fingerprint formats
are unchanged — no behavior change, no migration. cosineVector now delegates
to embedding.Cosine to avoid a divergent copy.
The embedder and featurize unit tests move with the code; memory keeps a
local mock embeddings server for its episode-index integration tests.
* Sessions: semantic session_search over shared embedding backend
Phase 2: route the session VectorIndex through internal/embedding so
session_search gains real semantic matching when an HTTP embeddings
backend (Ollama/OpenAI/etc.) is configured, instead of being hardwired to
go-vector RandomProjections.
- VectorIndex.emb is now embedding.TextEmbedder, selected by an
embedding.Config. Default (nil) stays RandomProjections — unchanged
for existing users.
- Add a vectors_meta.json fingerprint file next to vectors.gob. On init
the persisted vectors are only reused when the embedder fingerprint
(provider/model/dims) matches; switching backends forces a one-time
rebuild in the new space. A missing meta (legacy layout) also rebuilds,
since the old raw-text RP vectors live in a different space than the
shared featurized embedder.
- Rebuild batch-embeds the whole corpus via EmbedAll (one HTTP call) and
adopts the episode index's 30s failure cool-down so a down backend is
not hammered on every search or session save.
- Resilience: Add never fails a session save and Search never surfaces an
error when the backend is unavailable — both degrade silently to the
keyword fallback in session_search_tool.go (left untouched).
Wiring: Store.InitVectorIndex takes an *embedding.Config; the continue and
telegram entry points pass memory's embedding config so a single endpoint
powers both subsystems (a dedicated sessions block lands in Phase 4).
Tests mirror memory's: HTTP semantic match (kitten→feline), fingerprint
invalidation forcing a rebuild, and the rebuild backoff under a down backend.
* Skills semantic matching (opt-in) + shared embedding config
Phase 3 — skills:
Route the skill VectorMatcher through internal/embedding. The skill corpus
embeds once at reload (cheap); only the query embeds per turn, so the HTTP
backend is opt-in and time-bounded:
- NewVectorMatcherWithConfig selects the backend; default stays local RP.
- For an HTTP config that omits timeout_seconds, a short 2s query timeout is
applied so the per-turn embed fails fast.
- SkillManager.MatchLazySkills is the single entry point the agent loop wires:
when a remote backend is configured it tries semantic matching first and
falls back to the keyword ScoredMatcher on no-match/timeout/down-backend;
otherwise it uses the keyword matcher directly (unchanged default). The
local RP vector matcher stays a fallback, never the primary.
Phase 4 — shared config + docs:
- New top-level `embedding` block is the shared default. Memory inherits it
unless memory.embedding overrides (back-compat); sessions read it for
semantic session_search; skills do NOT inherit it — opt in via
skills.embedding because they run on every turn.
- Resolved into ResolvedConfig.Embedding and Skills.Embedding; merge + memory
inheritance wired in the loader.
- Session init and the skill managers across all agent entry points (run,
continue, repl, serve, subagent, mcp, telegram) now take the resolved
embedding configs.
- CONFIG.md documents the shared backend, the inherit/opt-in matrix, and the
per-subsystem resilience (session fingerprint rebuild, skill fail-fast
fallback, egress warning).
Tests: HTTP-backed semantic skill match, default-RP-not-semantic, keyword
fallback when the skills backend is down, and config resolution for top-level
inheritance + per-subsystem overrides.
* Add unit tests for embedding edge cases, session index, skill matching
Raises coverage on the new code: embedding 91.7%→98.3%, session 84.0%→85.5%,
skills 83.3%→85.0%.
- embedding: Cosine edge cases (mismatched/empty/zero-norm + NaN/Inf clamp),
NewRP dims default, HTTP apikey/timeout/dims fingerprint, stateless
SaveState/LoadState no-ops, RP save-to-bad-path safety.
- session: public Save + replace-on-Add + empty-text no-op, Remove/Save on a
not-ready index.
- skills: semantic-success path of MatchLazySkills, the non-semantic fallback
chain (scored→vector→trie→empty), HybridMatcher trie+vector merge, and the
buildEmbedText separate/with-body branches.
* docs: document shared embedding backend across sessions and skills
- SESSIONS.md: session_search now rides the shared embedding backend (RP
default or opt-in HTTP semantic), with the fingerprint rebuild + 30s
backoff and DeepSearch fallback. Corrects the stale "64-dim" note (256).
- LEARNING.md: new "How lazy skills are matched" section — keyword-first with
opt-in, time-bounded semantic matching and keyword fallback; config field.
- MEMORY.md: point the embedder mechanics at internal/embedding (the shared
package) instead of the now-thin memory/embedder.go aliases.
* Centralize embedding: shared default flows to every subsystem
Make the top-level `embedding` block the default for all three subsystems —
memory, sessions, AND skills — each with an optional override, instead of skills
being opt-in and sessions having no override slot.
- Skills now inherit the shared default (was opt-in). To keep the per-turn
hot path safe, an *inherited* skills config has its query timeout capped at
2s (maxSkillsInheritedTimeout); an explicit skills.embedding is respected
verbatim. Keyword fallback on miss/timeout/down-backend is unchanged.
- Add an optional `sessions.embedding` override (new SessionsConfig block) so
sessions are symmetric with memory/skills; resolved into
ResolvedConfig.SessionEmbedding. main/telegram wire it through.
- Memory inheritance unchanged (memory.embedding still wins for back-compat).
So a single top-level `embedding` block turns on semantic memory recall,
session_search, and skill matching at once; each subsystem can still point at a
different model/endpoint via its own block.
Docs: CONFIG.md inherit/override matrix rewritten (all inherit, all optional
override, skills timeout bound documented); SESSIONS.md and LEARNING.md updated
for inheritance + the new sessions override; anchors fixed. Tests updated to
assert every layer inherits, the skills timeout cap, and per-subsystem overrides
(including explicit skills timeout respected as-is).
* docker: point the shared embedding block at the local sidecar
The compose setup already ships a private llama.cpp embeddings sidecar
(`llama-embeddings`). Promote it from `memory.embedding` to the top-level
`embedding` block in both bundled configs so the single in-network endpoint now
powers all three consumers — memory, session_search, and skill matching — via
the centralized config (skills inherit with the bounded 2s per-turn timeout).
Updated docker-compose.yml, config.restricted.json, config.godmode.json,
Dockerfile.embeddings, .env.example, docker/README.md (heading + anchor), and
docs/DOCKER_COMPOSE_USER_GUIDE.md to describe the shared backend and the
fall-back-to-RandomProjections-everywhere disable path.
* docs: verification-protocol fixes — document new config surface
Axis 2.9 (documentation coverage) findings from the verification pass over the
embedding PR:
- CONFIG.md Skills table: add the `skills.embedding` field row (was only in the
shared-backend section and LEARNING.md).
- CONFIG.md memory `embedding` row: correct the "unset = RandomProjections"
description — memory now inherits the top-level shared default when unset
(RP only when neither is set). Added cross-link to the shared section.
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent f42f315 commit 2d63470
39 files changed
Lines changed: 1789 additions & 597 deletions
File tree
- cmd/odek
- docker
- docs
- internal
- config
- embedding
- memory
- session
- skills
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
828 | 828 | | |
829 | 829 | | |
830 | 830 | | |
831 | | - | |
| 831 | + | |
832 | 832 | | |
833 | 833 | | |
| 834 | + | |
834 | 835 | | |
835 | 836 | | |
836 | 837 | | |
| |||
1640 | 1641 | | |
1641 | 1642 | | |
1642 | 1643 | | |
1643 | | - | |
1644 | | - | |
1645 | 1644 | | |
1646 | 1645 | | |
1647 | 1646 | | |
| |||
1659 | 1658 | | |
1660 | 1659 | | |
1661 | 1660 | | |
| 1661 | + | |
| 1662 | + | |
| 1663 | + | |
| 1664 | + | |
1662 | 1665 | | |
1663 | 1666 | | |
1664 | 1667 | | |
| |||
1668 | 1671 | | |
1669 | 1672 | | |
1670 | 1673 | | |
1671 | | - | |
| 1674 | + | |
1672 | 1675 | | |
1673 | 1676 | | |
| 1677 | + | |
1674 | 1678 | | |
1675 | 1679 | | |
1676 | 1680 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
69 | | - | |
| 69 | + | |
70 | 70 | | |
71 | 71 | | |
| 72 | + | |
72 | 73 | | |
73 | 74 | | |
74 | 75 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
72 | 72 | | |
73 | 73 | | |
74 | 74 | | |
75 | | - | |
| 75 | + | |
76 | 76 | | |
77 | 77 | | |
| 78 | + | |
78 | 79 | | |
79 | 80 | | |
80 | 81 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
257 | 257 | | |
258 | 258 | | |
259 | 259 | | |
260 | | - | |
| 260 | + | |
261 | 261 | | |
262 | 262 | | |
| 263 | + | |
263 | 264 | | |
264 | 265 | | |
265 | 266 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
286 | 286 | | |
287 | 287 | | |
288 | 288 | | |
289 | | - | |
| 289 | + | |
290 | 290 | | |
291 | 291 | | |
| 292 | + | |
292 | 293 | | |
293 | 294 | | |
294 | 295 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
143 | 143 | | |
144 | 144 | | |
145 | 145 | | |
146 | | - | |
147 | | - | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
148 | 149 | | |
149 | 150 | | |
150 | 151 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
65 | 65 | | |
66 | 66 | | |
67 | 67 | | |
68 | | - | |
| 68 | + | |
69 | 69 | | |
70 | | - | |
71 | | - | |
72 | | - | |
73 | | - | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
74 | 75 | | |
75 | 76 | | |
76 | | - | |
| 77 | + | |
77 | 78 | | |
78 | 79 | | |
79 | 80 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
| 4 | + | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
188 | 188 | | |
189 | 189 | | |
190 | 190 | | |
191 | | - | |
| 191 | + | |
192 | 192 | | |
193 | 193 | | |
194 | | - | |
| 194 | + | |
195 | 195 | | |
196 | | - | |
197 | | - | |
198 | | - | |
199 | | - | |
200 | | - | |
201 | | - | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
202 | 210 | | |
203 | 211 | | |
204 | | - | |
205 | | - | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
206 | 215 | | |
207 | 216 | | |
208 | 217 | | |
209 | 218 | | |
210 | 219 | | |
211 | | - | |
212 | | - | |
213 | | - | |
214 | | - | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
215 | 225 | | |
216 | 226 | | |
217 | 227 | | |
218 | | - | |
| 228 | + | |
219 | 229 | | |
220 | | - | |
221 | | - | |
222 | | - | |
223 | | - | |
224 | | - | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
225 | 236 | | |
226 | 237 | | |
227 | 238 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
24 | 31 | | |
25 | 32 | | |
26 | 33 | | |
| |||
33 | 40 | | |
34 | 41 | | |
35 | 42 | | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
40 | | - | |
41 | | - | |
42 | | - | |
43 | | - | |
| 43 | + | |
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
| |||
0 commit comments