activeloopai / activeloopai/hivemind

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

Đang mở
#80 1 bình luận 0 reaction 0 người được giao Xem trên GitHub
bug
Ngôn ngữ chính
TypeScript
Star
1.6k
Fork
107
Merge trung bình
17 giờ 30 phút
Pull request đã merge (30 ngày)
6

Mô tả

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

Hướng dẫn đóng góp

Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.