Per-stage LLM routing so Hindsight's high-volume background jobs stop burning the rate-limited Kimi subscription: - CONSOLIDATION + REFLECT -> ollama/gemma3:4b via LiteLLM :4000 (local GPU, free) - RETAIN (fact extraction) stays on Kimi (hindsight-llm) for quality Fixes the Kimi 5h window maxing at 100% from ~1100 background calls/3h (dropped to ~38%). Documents the reasoning in HINDSIGHT-MIGRATION.md section 10: frequent/mechanical/background stages -> cheap local model; user-facing, quality-critical, low-volume -> Kimi. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014t8Qg9gi7H7HtT8MncoXAB
254 lines
15 KiB
Markdown
254 lines
15 KiB
Markdown
# Adolf memory migration — Cognee → Hindsight
|
||
|
||
**Status:** Planned · **Date:** 2026-07-13 · **Owner:** alvis
|
||
|
||
This is the authoritative design + plan for replacing Adolf's long-term memory
|
||
subsystem (**Cognee**) with **Hindsight** (Vectorize, MIT, self-hosted). It
|
||
supersedes the Cognee-specific parts of `docs/ARCHITECTURE.md` and gates 4–5 of
|
||
`docs/SPIKE-FINDINGS.md` in the OpenClaw fork (`/home/alvis/adolf`).
|
||
|
||
Memory stays integrated into Adolf **exactly the two ways Cognee was** — as a
|
||
**tool** (MCP) and as **forced hooks** (an OpenClaw memory plugin) — so no
|
||
behaviour the user sees is lost; only the backend changes.
|
||
|
||
> Scope note: this document + the kanboard **Ready** tasks (H1–H5) are the
|
||
> migration. No live service, compose file, `openclaw.json`, or plugin code has
|
||
> been changed yet — those edits are the H-tasks.
|
||
|
||
---
|
||
|
||
## 1. Why migrate
|
||
|
||
Cognee works, but its Agap deployment carries three structural costs, all
|
||
documented in the (now-retired) kanboard cognee tasks:
|
||
|
||
- **Three bespoke services** to keep the memory stack alive: `cognee` (FastAPI +
|
||
embedded Kuzu graph + Qdrant vectors), `cognee-mcp` (a patched MCP→HTTP proxy,
|
||
local build overlay from kb#70), and `cognee-llm` (a stateless Kimi-CLI wrapper
|
||
that exists *only* to give Cognee an LLM on the flat subscription).
|
||
- **The cognify pipeline is fragile and expensive.** "Cognify" (turning raw
|
||
turns into a graph) is an LLM step. On the Kimi CLI it runs ~5–24 s per call
|
||
and drains the single-seat subscription quota (SPIKE gate 5 recommended
|
||
LiteLLM instead). The plugin's async "cognify sweep" also silently stalled
|
||
twice (kb#69) because of OpenClaw plugin-lifecycle edge cases, so freshly told
|
||
facts weren't retrievable cross-session until the sweep was re-armed.
|
||
- **Scoping is best-effort.** Under `ENABLE_BACKEND_ACCESS_CONTROL=False` all
|
||
datasets share one graph/vector backend, so per-chat isolation leaks (kb#59).
|
||
|
||
## 2. What Hindsight gives us
|
||
|
||
- **One container.** `ghcr.io/vectorize-io/hindsight:latest` — REST API on
|
||
**:8888**, web UI on **:9999**, built-in PostgreSQL (`pg0`, persisted under
|
||
`/home/hindsight/.pg0`). It owns its own vector + graph + temporal
|
||
representation internally (the "four-network" model), so **no external Qdrant
|
||
or Kuzu** is needed for memory.
|
||
- **Built-in MCP server** mounted at `/mcp` on the same port — ~30 tools
|
||
including `retain` / `recall` / `reflect`. This **removes the need for a
|
||
separate `cognee-mcp` proxy container entirely**.
|
||
- **Retain learns on its own.** `retain` runs Hindsight's extraction/reflection
|
||
pipeline internally (optionally `async`), so there is **no separate "cognify
|
||
sweep" to arm, throttle, or watch** — the whole class of kb#69 bugs disappears.
|
||
- **Memory banks** are first-class isolation units, scoped by URL path
|
||
(`/v1/{tenant}/banks/{bank_id}/…`), so per-chat / per-user scoping is real, not
|
||
best-effort.
|
||
- **Bring-your-own LLM/embeddings** — OpenAI-compatible, Anthropic, Ollama,
|
||
LMStudio, etc. We point it at the infra we already run (LiteLLM `:4000` and/or
|
||
Ollama), so we can **delete `cognee-llm`** rather than port it.
|
||
|
||
Net: **3 services → 1**, plus we drop the cognify-sweep machinery.
|
||
|
||
## 3. Target architecture
|
||
|
||
```
|
||
Matrix ⇄ OpenClaw ("Adolf" gateway)
|
||
│ provider adolf-llm (Kimi wrapper, :8010) — unchanged
|
||
▼
|
||
adolf-llm ── kimi CLI (agent)
|
||
│
|
||
┌─────────┴───────────────── memory is a boundary concern ─────────────┐
|
||
│ │
|
||
│ (a) FORCED HOOKS — hindsight-memory OpenClaw plugin │
|
||
│ before_prompt_build → recall → inject as prependContext │
|
||
│ agent_end → retain (async:true) the turn │
|
||
│ │
|
||
│ (b) TOOL — Hindsight built-in MCP in openclaw.json mcp.servers │
|
||
│ http://hindsight:8888/mcp/adolf/ → retain/recall/reflect/… │
|
||
└───────────────────────────────┬───────────────────────────────────────┘
|
||
▼
|
||
hindsight (ONE container)
|
||
:8888 REST + /mcp · :9999 UI
|
||
built-in Postgres (pg0)
|
||
LLM → per-stage routing [resolved, §10]
|
||
embeddings → ollama bge-m3 (GPU) [resolved, §10]
|
||
```
|
||
|
||
Reused infra: **LiteLLM `:4000`** and/or **Ollama** for Hindsight's model calls.
|
||
**Retired:** `cognee`, `cognee-mcp`, `cognee-llm`, Qdrant-for-cognee, Kuzu, the
|
||
`cognee-openclaw-plugin`, and all `openclaw.json` cognee references.
|
||
|
||
### 3.1 Surface (a) — forced hooks (the `hindsight-memory` plugin)
|
||
|
||
A new OpenClaw memory plugin replacing `cognee-openclaw-plugin`, modelled on the
|
||
same Honcho touchpoints Cognee used, so the plugin shape is familiar:
|
||
|
||
| Cognee plugin (`cognee-memory`) | Hindsight plugin (`hindsight-memory`) |
|
||
|-----------------------------------------------------|---------------------------------------------------------------|
|
||
| `before_prompt_build` → LLM-free graph recall inject | `before_prompt_build` → `recall` → inject `prependContext` |
|
||
| `agent_end` → raw `/add` (no inline cognify) | `agent_end` → `retain` (`async:true`) the user+assistant turn |
|
||
| throttled **cognify sweep** (dirty-tracker, timers) | **removed** — retain does extraction/learning internally |
|
||
| `cognee_recall` registered tool | `hindsight_recall` (+ optional `hindsight_reflect`) tool |
|
||
|
||
- Activated via `plugins.entries.hindsight-memory` in `openclaw.json` with the
|
||
same hook grants Cognee needed: `hooks.allowConversationAccess: true` and
|
||
`allowPromptInjection: true` (external plugins must opt in).
|
||
- **Recall stays off the hot LLM path.** Hindsight `recall` is retrieval
|
||
(semantic + BM25 + graph + temporal) with evidence scoring — no generative
|
||
synthesis — so it's the direct analogue of Cognee's LLM-free `onlyContext`
|
||
recall. The LLM-backed synthesis path is `reflect`, exposed as a deliberate
|
||
tool, not run per-turn.
|
||
- **No freshness dial / sweep.** Retain with `async:true` returns fast and lets
|
||
Hindsight extract/consolidate in the background; there is no plugin-owned timer
|
||
to stall.
|
||
- Same "untrusted metadata" framing on the injected block; same cleaning of
|
||
OpenClaw's `Conversation info (untrusted metadata):` and the memory block out
|
||
of stored/queried text.
|
||
|
||
### 3.2 Surface (b) — tool (built-in MCP)
|
||
|
||
Replace the `cognee` entry in `openclaw.json` `mcp.servers` with:
|
||
|
||
```jsonc
|
||
mcp: {
|
||
servers: {
|
||
hindsight: {
|
||
type: "http",
|
||
url: "http://hindsight:8888/mcp/adolf/", // bank-scoped by URL path
|
||
},
|
||
// openclaw-tools, kanboard, marketplace — unchanged
|
||
},
|
||
}
|
||
```
|
||
|
||
The single bank in the path (`adolf`, or a per-chat bank) selects isolation; the
|
||
built-in MCP then exposes `retain`, `recall`, `reflect`, plus mental-model /
|
||
directive / memory-browse tools. This replaces cognee-mcp's `remember` / `recall`
|
||
/ `forget` — and gives deliberate delete via the memory-management tools instead
|
||
of the hand-patched `forget(data_id)` from kb#70.
|
||
|
||
## 4. REST / MCP API mapping
|
||
|
||
Base path: `http://hindsight:8888/v1/default` (tenant `default`). Bank id is a
|
||
**path** parameter.
|
||
|
||
| Operation | Cognee (old) | Hindsight (new) |
|
||
|------------------|------------------------------------------------|-----------------------------------------------------------------------|
|
||
| store a turn | `POST /api/v1/add` (+ later `/cognify`) | `POST /banks/{bank}/memories` body `{items:[{content,context,tags,timestamp}], async:true}` |
|
||
| recall (no LLM) | `POST /api/v1/search` `GRAPH_COMPLETION` `onlyContext:true` | `POST /banks/{bank}/memories/recall` body `{query, budget, max_tokens, tags}` |
|
||
| deep answer (LLM)| cognee-mcp `recall` (GRAPH_COMPLETION) | `POST /banks/{bank}/reflect` body `{query, budget, max_tokens, response_schema?}` |
|
||
| delete an entry | patched `forget(dataset, data_id)` (kb#70) | memory-management endpoints / MCP (`delete`, `clear_memories`) |
|
||
| list / inspect | dataset status polling | `GET /banks/{bank}/memories/list`, `GET /banks` |
|
||
| create bank | dataset created implicitly on add | `PUT /banks/{bank}` |
|
||
|
||
Built-in MCP tools live at `http://hindsight:8888/mcp/{bank}/` (HTTP transport;
|
||
bank via URL path, `X-Bank-Id` header, or `HINDSIGHT_MCP_BANK_ID` default).
|
||
|
||
> Exact request-body field names and any auth headers must be confirmed against
|
||
> the running instance's OpenAPI (`http://localhost:8888/docs`) and the Hindsight
|
||
> configuration docs during H1 — treat the bodies above as the shape, not gospel.
|
||
|
||
## 5. Bank scoping
|
||
|
||
Mirror Cognee's per-conversation `chat_<chatId>` dataset with a per-conversation
|
||
**bank**: derive `bank_id` from OpenClaw's `chat_id` (the
|
||
`Conversation info (untrusted metadata):` block; see SPIKE gate 2), sanitized to
|
||
`chat_<slug>`. A single shared `adolf` bank is the simpler alternative if
|
||
cross-chat recall is actually wanted — decide in H3. Banks are hard isolation in
|
||
Hindsight, so per-chat is now safe (unlike Cognee's leaky datasets).
|
||
|
||
## 6. Open decisions (resolve in H1)
|
||
|
||
1. **LLM backend for retain/reflect.** SPIKE gate 5 already concluded the
|
||
extraction workload should *not* sit on the Kimi CLI (latency + single-seat
|
||
quota). Recommendation: point Hindsight's LLM at **LiteLLM `:4000`** (or a
|
||
local **Ollama** model for zero marginal cost). This is why `cognee-llm` is
|
||
deleted, not ported. Confirm Hindsight's provider env-var names on the image.
|
||
2. **Embeddings.** Prefer the local **Ollama** embedder already available
|
||
(`nomic-embed` / `bge-m3` at `host.docker.internal:11436`) or Hindsight's
|
||
built-in, to keep embeddings off any paid path.
|
||
3. **Storage path.** Persist `pg0` under `/mnt/ssd/dbs/hindsight/` to match the
|
||
Agap storage layout (replaces `/mnt/ssd/dbs/cognee/`).
|
||
4. **UI exposure.** Whether to reverse-proxy the `:9999` UI (Caddy) or keep it
|
||
internal-only.
|
||
5. **Auth.** Open by default; enable the tenant API-key extension
|
||
(`HINDSIGHT_API_TENANT_API_KEY`, `Authorization: Bearer`) if the service is
|
||
reachable beyond the compose network.
|
||
|
||
## 7. Data migration
|
||
|
||
Cognee's Kuzu graph is **not** portable into Hindsight's store. The memory corpus
|
||
is low-value conversational history, so **start Hindsight empty** rather than
|
||
building an exporter. Optionally replay a handful of durable facts by calling
|
||
`retain` once at cutover. The two throwaway datasets left in Cognee
|
||
(`chat_verify`, `chat_webchat`) are discarded with the stack.
|
||
|
||
## 8. Migration phases (kanboard **Ready**, project *Adolf*)
|
||
|
||
- **H1 · Deploy Hindsight service** — add the `hindsight` container to
|
||
`openai/docker-compose.yml` (image, ports 8888/9999, `pg0` volume, LLM +
|
||
embedding provider env → LiteLLM/Ollama), bring it up, confirm `/docs` + a
|
||
round-trip `retain`→`recall`. Resolves §6 decisions.
|
||
- **H2 · Wire built-in MCP as an Adolf tool** — swap `mcp.servers.cognee` →
|
||
`mcp.servers.hindsight` (`/mcp/{bank}/`) in `openclaw.json`; verify Adolf can
|
||
call `retain`/`recall`/`reflect` as tools.
|
||
- **H3 · `hindsight-memory` OpenClaw plugin (forced hooks)** — build the plugin
|
||
replacing `cognee-openclaw-plugin`: `before_prompt_build`→recall inject,
|
||
`agent_end`→`retain(async)`, `hindsight_recall`/`hindsight_reflect` tools, bank
|
||
scoping; activate in `openclaw.json`. Delete the cognify-sweep machinery.
|
||
- **H4 · Decommission Cognee** — remove `cognee`, `cognee-mcp`, `cognee-llm`
|
||
services + volumes, the `cognee-openclaw-plugin`, and all `openclaw.json`/
|
||
`shared-mcp.json` cognee references. Free `/mnt/ssd/dbs/cognee`.
|
||
- **H5 · End-to-end verification** — state a fact → fresh session → recalled via
|
||
injected memory (no LLM on the recall path); measure recall latency; confirm
|
||
per-bank isolation; confirm no sweep/timer exists to stall.
|
||
|
||
## 9. What is unchanged
|
||
|
||
The Kimi/OpenClaw/Matrix substrate is untouched: `adolf` gateway, `adolf-llm`
|
||
(:8010) provider, the SSE-heartbeat/idle-watchdog fix (kb#71), the
|
||
`openclaw-tools` bridge, `kanboard`/`marketplace` MCP servers, Matrix allow-list
|
||
and E2EE. Only the memory backend and its two integration surfaces change.
|
||
|
||
## 10. LLM provider strategy — per-stage routing (kb#84, kb#88)
|
||
|
||
Hindsight calls an LLM in several distinct stages, and they have very different
|
||
cost/quality profiles. Hindsight supports a **separate provider per stage**
|
||
(`HINDSIGHT_API_<STAGE>_LLM_{PROVIDER,BASE_URL,MODEL,API_KEY}`, falling back to
|
||
the global `HINDSIGHT_API_LLM_*` when unset), so each is routed to the model that
|
||
fits it. Config lives in `openai/docker-compose.yml`, `hindsight` service.
|
||
|
||
| Stage | Model | Where | Why this model |
|
||
|-------|-------|-------|----------------|
|
||
| **RETAIN / extraction** | Kimi — `hindsight-llm:8012` | flat-rate subscription | Pulls atomic facts + entities out of raw **Russian** conversation. Quality-critical and user-visible (bad extraction ⇒ bad memory), and **low volume** — roughly one pass per turn. Worth the strong model. |
|
||
| **CONSOLIDATION** | `ollama/gemma3:4b` via LiteLLM `:4000` | local GPU (free) | Merges / dedups / reconciles stored memories. **Very high volume** — a background reconcile loop + per-retain triggers: ~900 calls / 3 h at ~190 memories. Mostly mechanical; a small model is good enough. |
|
||
| **REFLECT / mental-models** | `ollama/gemma3:4b` via LiteLLM `:4000` | local GPU (free) | Periodic synthesis over the bank and the `reflect` tool. Background, frequent (~170 calls / 3 h), never on the reply path. |
|
||
| Embeddings | `bge-m3` (ollama, GPU) | local | multilingual, GPU-served — see §3 / kb#84. |
|
||
| Reranker | `jina-reranker-v2-base-multilingual` | local (CPU) | multilingual; kb#84. |
|
||
|
||
**Why the split matters — the kb#88 incident.** Originally (H1b) the *entire*
|
||
Hindsight LLM was pointed at Kimi via `HINDSIGHT_API_LLM_PROVIDER` alone. The
|
||
background consolidation + mental-model jobs then hammered Kimi ~1100 calls / 3 h
|
||
and **maxed the Kimi 5-hour rate window (100 %)** with *no* chat traffic at all —
|
||
because Kimi is a **rate-limited flat subscription**, not a per-token API, and
|
||
these jobs run continuously regardless of user activity. Moving the
|
||
high-volume / low-stakes stages to a free local GPU model dropped Kimi 5 h usage
|
||
100 % → ~38 % while keeping fact extraction on the good model.
|
||
|
||
**Rule of thumb:** *frequent, background, mechanical* stages (consolidation,
|
||
reflect, mental-models) → **cheap local model**; *user-facing, quality-critical,
|
||
low-volume* work (extraction, and the assistant's own replies) → **Kimi**.
|
||
|
||
Levers if the local box gets loaded or quality is off:
|
||
`HINDSIGHT_API_CONSOLIDATION_RECONCILE_INTERVAL_SECONDS` (raise to run less
|
||
often), `_MAX_MEMORIES_PER_ROUND` (lower). If `gemma3:4b`'s Russian consolidation
|
||
quality is too weak, `qwen3.5:9b` (same ollama box, `:11436`) is the next step up.
|