activeloopai / activeloopai/hivemind

openclaw: auto-capture INSERT misses message_embedding column (parity gap with claude-code/codex)

Offen
#80 1 Kommentar 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
bug
Vorherrschende Sprache
TypeScript
Sterne
1.6k
Forks
107
Ø Merge
17 Std. 30 Min.
Gemergte PRs (30 T.)
6

Beschreibung

## 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

Beitragsleitfaden

Für dieses Repository ist kein Beitragsleitfaden indexiert

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.