activeloopai / activeloopai/hivemind
openclaw: auto-capture INSERT misses message_embedding column (parity gap with claude-code/codex)
- Dominant language
- TypeScript
- Stars
- 1.6k
- Forks
- 107
- Avg merge
- 17h 30m
- Merged PRs (30d)
- 6
Description
## Summary
OpenClaw's auto-capture path writes rows to the `sessions` table **without populating the `message_embedding` column**. Recall against OpenClaw-captured sessions is therefore permanently lexical-only, even when `@huggingface/transformers` is installed and the embedding daemon would otherwise be reachable. This is the same oversight that affected the codex hooks until [`4dd1c32`](https://github.com/activeloopai/hivemind/commit/4dd1c32), and that affected the claude-code capture path until [`bfff7be`](https://github.com/activeloopai/hivemind/commit/bfff7be).
## Where the gap is
`openclaw/src/index.ts:893` — auto-capture INSERT:
```ts
`INSERT INTO "${sessionsTable}" (id, path, filename, message, author, size_bytes, project, description, agent, creation_date, last_update_date) `
```
The column tuple does **not** include `message_embedding` and there is no `EmbedClient` call site anywhere in `openclaw/src/index.ts`. Confirmed by grep: `EmbedClient | embed-daemon | embeddingsDisabled | embeddingSqlLiteral` → 0 hits in the openclaw source. `openclaw/dist/` does not ship `embeddings/embed-daemon.js` either.
The comment one line above (`// Auto-capture: store new messages in sessions table (same format as CC capture.ts)`) makes the intent clear — feature parity with claude-code's capture — but the embedding step never made it across.
## Impact
- OpenClaw users who install `@huggingface/transformers` get **no benefit** from it for capture. Every captured row lands with `message_embedding = NULL`.
- The `sessions` schema migration (ALTER ADD COLUMN) is still triggered on every SessionStart by the shared `DeeplakeApi.ensureSessionsTable()`, so the column exists — it's just never written.
- Hybrid semantic + lexical recall via `searchDeeplakeTables` falls back to lexical-only on OpenClaw-originated rows.
## Plugin matrix today
| Capability | claude-code | codex | openclaw |
|---|---|---|---|
| Embed in capture-path | ✅ | ✅ (4dd1c32) | ❌ this issue |
| `@huggingface/transformers` detection / graceful fallback | ✅ | ✅ | n/a (no embed call) |
| Schema migration (ALTER ADD COLUMN) | ✅ | ✅ | ✅ |
| Fail-fast on 500 "already exists" | ✅ | ✅ | ✅ (shared `DeeplakeApi`) |
## What needs to be done
Mirror of `4dd1c32` against `openclaw/src/index.ts`:
1. **Imports** in `openclaw/src/index.ts` (top of file):
```ts
import { EmbedClient } from "../../src/embeddings/client.js";
import { embeddingSqlLiteral } from "../../src/embeddings/sql.js";
import { embeddingsDisabled } from "../../src/embeddings/disable.js";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
```
2. **Helper** alongside the existing helpers:
```ts
function resolveEmbedDaemonPath(): string {
return join(dirname(fileURLToPath(import.meta.url)), "embeddings", "embed-daemon.js");
}
```
3. **Embed before INSERT**, immediately above `openclaw/src/index.ts:893`:
```ts
const embedding = embeddingsDisabled()
? null
: await new EmbedClient({ daemonEntry: resolveEmbedDaemonPath() }).embed(line, "document");
const embeddingSql = embeddingSqlLiteral(embedding);
```
4. **Update the INSERT** to list the column and value:
```ts
`INSERT INTO "${sessionsTable}" (id, path, filename, message, message_embedding, author, size_bytes, project, description, agent, creation_date, last_update_date) `
```
…and put `${embeddingSql}` in the corresponding VALUES slot (right after the JSONB message literal, before the author).
5. **`esbuild.config.mjs`**: add the embed-daemon entry to the openclaw build target so the shipped bundle has `embeddings/embed-daemon.js` to spawn (today only claude-code and codex ship it).
6. **Tests**: update `openclaw/tests/auto-recall.test.ts` (or the auto-capture-covering test in that suite) to mock `EmbedClient` the same way `claude-code/tests/codex-capture-hook.test.ts` does after `4dd1c32`:
```ts
vi.mock("../../src/embeddings/client.js", () => ({
EmbedClient: class {
embed(_t: string, _k?: string) { return Promise.resolve(null); }
warmup() { return Promise.resolve(false); }
},
}));
```
## How to verify
After the fix, run the existing real-table scenario harness against the openclaw bundle (the `scenario-matrix.sh` and `scenario-matrix-codex.sh` scripts in the `embedding_generation` worktree are reusable templates — point them at the openclaw bundle's `auto-capture` entry point). Expected behaviour, mirroring claude-code and codex:
- 4 of 7 scenarios (greenfield, half-legacy-memory, fully-migrated, mixed-sess-emb) → `rows=1`, `emb_len=768` with `@huggingface/transformers` reachable, `emb_len=null` without it.
- 3 of 7 scenarios (full-legacy, half-legacy-sessions, mixed-mem-emb) → first capture lost to the Deeplake post-ALTER `vector::at` window (~30s), recovers from the second capture onward. This is a backend bug, not a plugin one (see `repro-vector-at-bug.sh` and `recovery-window-probe.sh` in the worktree).
## Acceptance criteria
- [ ] `grep -c message_embedding openclaw/src/index.ts` returns ≥ 1.
- [ ] `openclaw/dist/embeddings/embed-daemon.js` exists after `npm run build`.
- [ ] OpenClaw-captured rows on a workspace with `@huggingface/transformers` installed land with `ARRAY_LENGTH(message_embedding, 1) = 768`.
- [ ] OpenClaw-captured rows on a workspace **without** `@huggingface/transformers` land with `message_embedding = NULL` and the daemon is never spawned (graceful degradation parity with codex/claude-code).
- [ ] Existing openclaw test suite passes; auto-capture test asserts `message_embedding` is in the INSERT column tuple.
## References
- `bfff7be` — feat(capture): embed message inline before sessions INSERT (claude-code)
- `4dd1c32` — feat(codex): embed message inline before sessions INSERT (codex)
- `6b77d6d` — feat(embeddings): degrade to lexical-only when transformers is missing
- `0aef2df` — refactor(session-start): log specific reason embeddings are off
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.