clawmem 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/AGENTS.md +165 -677
  2. package/CLAUDE.md +4 -747
  3. package/README.md +20 -191
  4. package/SKILL.md +157 -711
  5. package/docs/clawmem-architecture.excalidraw +2415 -0
  6. package/docs/clawmem-architecture.png +0 -0
  7. package/docs/clawmem_hero.jpg +0 -0
  8. package/docs/concepts/architecture.md +413 -0
  9. package/docs/concepts/composite-scoring.md +133 -0
  10. package/docs/concepts/hooks-vs-mcp.md +156 -0
  11. package/docs/concepts/multi-vault.md +71 -0
  12. package/docs/contributing.md +101 -0
  13. package/docs/guides/cloud-embedding.md +134 -0
  14. package/docs/guides/hermes-plugin.md +187 -0
  15. package/docs/guides/inference-services.md +144 -0
  16. package/docs/guides/multi-vault-config.md +84 -0
  17. package/docs/guides/openclaw-plugin.md +306 -0
  18. package/docs/guides/setup-hooks.md +146 -0
  19. package/docs/guides/setup-mcp.md +76 -0
  20. package/docs/guides/systemd-services.md +332 -0
  21. package/docs/guides/upgrading.md +566 -0
  22. package/docs/internals/entity-resolution.md +135 -0
  23. package/docs/internals/graph-traversal.md +85 -0
  24. package/docs/internals/intent-search-pipeline.md +103 -0
  25. package/docs/internals/query-pipeline.md +100 -0
  26. package/docs/introduction.md +104 -0
  27. package/docs/quickstart.md +158 -0
  28. package/docs/reference/cli.md +195 -0
  29. package/docs/reference/configuration.md +101 -0
  30. package/docs/reference/mcp-tools.md +336 -0
  31. package/docs/reference/rest-api.md +204 -0
  32. package/docs/troubleshooting.md +330 -0
  33. package/package.json +2 -1
  34. package/src/clawmem.ts +60 -0
  35. package/src/health/rerank-golden.json +54 -0
  36. package/src/health/rerank-health.ts +150 -0
  37. package/src/mcp.ts +20 -1
  38. package/src/memory.ts +2 -0
  39. package/src/search-utils.ts +35 -4
  40. package/src/store.ts +115 -22
@@ -0,0 +1,156 @@
1
+ # Claude Code hooks vs MCP tools
2
+
3
+ ClawMem delivers memory through two tiers: hooks handle about 90% of context delivery automatically, while MCP server tools (available to any MCP-compatible client, not just Claude Code) cover the remaining 10% when the agent needs to escalate.
4
+
5
+ ## Tier 2 — Hooks (automatic)
6
+
7
+ Hooks fire on Claude Code lifecycle events with zero agent effort:
8
+
9
+ | Hook | Trigger | Budget | What it does |
10
+ |------|---------|--------|-------------|
11
+ | `context-surfacing` | UserPromptSubmit | profile-driven (default 800 tokens + factsTokens sub-budget) | Searches vault for context relevant to the user's prompt. Injects results as `<vault-context>` XML with four inner blocks: `<instruction>` (framing — always present), `<facts>` (the surfaced docs), `<relationships>` (memory-graph edges between surfaced docs, v0.7.1), and `<vault-facts>` (raw SPO triples for prompt-seeded entities, v0.9.0 §11.1). Also applies a session-scoped topic boost when a focus file is set for the session (v0.9.0 §11.4). |
12
+ | `postcompact-inject` | SessionStart (after compact) | 1200 tokens | Re-injects authoritative state after context window compaction. |
13
+ | `curator-nudge` | SessionStart | 200 tokens | Surfaces maintenance suggestions from the curator report. |
14
+ | `precompact-extract` | PreCompact | — | Extracts decisions, file paths, and open questions before compaction. Writes `precompact-state.md`. |
15
+ | `decision-extractor` | Stop | — | LLM extracts observations from the conversation. Infers causal links. Detects contradictions with prior decisions. Extracts SPO triples from decision/preference/milestone/problem facts. |
16
+ | `handoff-generator` | Stop | — | LLM summarizes the session for cross-session continuity. |
17
+ | `feedback-loop` | Stop | — | Tracks which notes were referenced. Boosts their confidence. Per-turn recall attribution marks which surfaced docs were actually cited. |
18
+
19
+ ### How context-surfacing works
20
+
21
+ 1. Validate prompt (skip slash commands, greetings, heartbeats, duplicates)
22
+ 2. Load performance profile (`speed` / `balanced` / `deep`) for budget and thresholds
23
+ 3. **Resolve session focus topic** (v0.9.0 §11.4) — read `~/.cache/clawmem/sessions/<id>.focus` if present. If set, thread as `intent` hint to the next stages
24
+ 4. Search: vector (if profile enables it, with profile-driven timeout) + BM25 supplement
25
+ 5. Filter: exclude private paths, snoozed documents, noise
26
+ 6. Score: composite scoring (relevance + recency + confidence + quality)
27
+ 7. **Apply session focus topic boost** (v0.9.0 §11.4) — if a focus topic was resolved, multiply matching docs' compositeScore by 1.4× and non-matching by 0.75× (floor 50%). NO-OP when zero docs match — the baseline ordering passes through unchanged, so the threshold filter never shrinks the result set because of a non-matching topic
28
+ 8. Adaptive threshold + memory-type diversification
29
+ 9. Build facts block within `tokenBudget - instructionCost` so the always-on `<instruction>` frame fits
30
+ 10. Fetch relationship snippets from `memory_relations` for edges where BOTH endpoints are in the surfaced doc set — these become a `<relationships>` block, the first thing dropped when the payload would overflow budget
31
+ 11. **Seed entities from the prompt** (v0.9.0 §11.1) — three-path extraction (canonical-id regex → proper-noun validation via `resolveEntityTypeExact` → longer-first n-gram scan). Prompt-only: seeds NEVER come from surfaced doc bodies, so topic-boosted off-topic docs cannot pollute the facts block
32
+ 12. **Query SPO triples for seeded entities**, dedupe by `(subject, predicate, object)` across all entities, emit a token-bounded `<vault-facts>` block using the dedicated `factsTokens` sub-budget (speed=0 disables the stage, balanced=200, deep=250). Truncate at triple boundary, never mid-triple, never emit an empty block. Fail-open on every error path
33
+ 13. Inject as `<vault-context><instruction>...</instruction><facts>...</facts><relationships>...</relationships><vault-facts>...</vault-facts></vault-context>` XML in the prompt
34
+
35
+ The `<instruction>` frame tells the model to treat the surfaced facts as background knowledge it already holds unless the user corrects them, reducing prompt-level ambiguity about how to use the injected context. The `<relationships>` block exposes the vault's knowledge graph (semantic, supporting, contradicts, causal, temporal edges) directly in-prompt so the model can reason over document connections without having to call `intent_search`. The `<vault-facts>` block adds raw SPO triples for prompt-seeded entities so the model has structured "what is currently true about these entities" without needing an explicit `kg_query`. `<vault-facts>` / `<relationships>` landed in v0.9.0 / v0.7.1 respectively.
36
+
37
+ ### Tuning context-surfacing with profiles
38
+
39
+ Set `CLAWMEM_PROFILE` to adjust the context-surfacing hook's behavior:
40
+
41
+ | Profile | Token budget | `factsTokens` | Max results | Vector | Vector timeout | Score ratio | Activation floor | Deep escalation |
42
+ |---------|-------------|---------------|-------------|--------|----------------|-------------|-----------------|-----------------|
43
+ | `speed` | 400 | 0 (disabled) | 5 | Off | — | 65% | 0.24 | No |
44
+ | `balanced` (default) | 800 | 200 | 10 | On | 900ms | 55% | 0.20 | No |
45
+ | `deep` | 1200 | 250 | 15 | On | 2000ms | 45% | 0.16 | Yes |
46
+
47
+ `factsTokens` is a dedicated sub-budget for the `<vault-facts>` KG injection block (v0.9.0 §11.1) that cannot steal from the main `tokenBudget`. Setting `factsTokens: 0` on a profile disables the stage entirely.
48
+
49
+ Profiles only affect the automatic context-surfacing hook. MCP tools are not affected — agents control their own `limit`, `compact`, and tool selection per call.
50
+
51
+ ### Adaptive thresholds
52
+
53
+ Context-surfacing uses adaptive ratio-based thresholds that adjust to your vault's score distribution instead of fixed absolute values. The filter works in two steps:
54
+
55
+ 1. **Activation floor** — if the best result in the entire set scores below the activation floor (e.g., 0.20 for balanced), the hook returns empty. This prevents surfacing all-weak results where even the top hit isn't relevant enough to be useful.
56
+
57
+ 2. **Score ratio** — results are kept if they score within the ratio of the best result's composite score. For balanced at 55%, a best score of 0.60 keeps everything above 0.33 (`0.60 * 0.55`). An absolute floor (0.15 for balanced) prevents the ratio from going too low when the best score is itself marginal.
58
+
59
+ This adapts to vault size (smaller vaults produce higher absolute scores), embedding model (different cosine similarity distributions), document quality (the 0.7x-1.3x quality multiplier shifts all scores), and content age (recency decay affects absolute scores but the ratio stays stable).
60
+
61
+ For backward compatibility, set `thresholdMode: "absolute"` in the profile to use fixed `minScore` values instead. MCP tools always use absolute thresholds since agents control their own limits directly.
62
+
63
+ ### Deep escalation (deep profile only)
64
+
65
+ On the `deep` profile, context-surfacing uses a budget-aware escalation strategy. After the fast path (BM25 + vector search) completes, the hook checks how much of its 8-second timeout remains. If the fast path finished in under 4 seconds, the remaining time is spent on two additional phases:
66
+
67
+ 1. **Query expansion** — the LLM generates lexical and semantic variants of the prompt. These expanded queries run through BM25 to discover candidates that the original query terms missed. New candidates are merged into the result set (deduplicated).
68
+
69
+ 2. **Cross-encoder reranking** — the top 15 candidates are scored by the reranker with 2000 chars of document context per candidate. Results are blended (60% original score, 40% reranker score) to avoid over-relying on either signal.
70
+
71
+ Both phases have a hard stop at 6 seconds (leaving 2 seconds of headroom within the hook timeout). If GPU services are unavailable or either phase times out, the hook falls back to the fast-path results. On a GPU system, expansion typically takes ~300ms and reranking ~200ms, so both phases fire on nearly every prompt. On CPU-only systems, the fast path alone usually consumes most of the 4-second budget, so escalation skips naturally.
72
+
73
+ The effect: `deep` profile hooks produce results closer to what the `query` MCP tool returns (which always runs expansion + reranking), while `speed` and `balanced` continue using the fast path only.
74
+
75
+ ### Session focus topic boost (v0.9.0 §11.4)
76
+
77
+ The session focus topic is a per-session bias that steers context-surfacing toward a declared topic for the duration of a working session, without writing to SQLite or mutating any lifecycle column. Use it when the user asks to focus on one thing ("let's focus on the auth refactor for this session" / "only surface X-related docs right now") — the topic biases retrieval toward matching docs while leaving the underlying vault state untouched. Clearing the focus at the end of the subsession returns surfacing to baseline.
78
+
79
+ Set / show / clear with the CLI:
80
+
81
+ ```bash
82
+ clawmem focus set "authentication flow" --session-id abc123
83
+ clawmem focus show --session-id abc123
84
+ clawmem focus clear --session-id abc123
85
+ ```
86
+
87
+ The session ID is resolved from `--session-id <id>`, then `CLAUDE_SESSION_ID`, then `CLAWMEM_SESSION_ID` — Claude Code exposes `CLAUDE_SESSION_ID` natively so the env-var path works automatically inside a Claude Code session. The focus file lives at `~/.cache/clawmem/sessions/<session_id>.focus`.
88
+
89
+ When a focus topic is active:
90
+
91
+ - **Intent-threaded retrieval** — the topic is passed as `intent` to `expandQuery`, `rerank`, and `extractSnippet`. Query expansion generates topic-aware variants, rerank prioritizes topic-aligned segments, and snippet extraction prefers sentences containing the topic tokens. Same `intent` lever that already exists on the query-time pipeline.
92
+ - **Post-composite-score topic boost** — after `applyCompositeScoring` and before the adaptive threshold filter, docs that match all topic tokens (bag-of-word against title/path/body[:800], case-insensitive) get a 1.4× multiplier on `compositeScore`; non-matching docs get a 0.75× demote (clamped at a 0.5 floor). Boost fires per-result so it interacts cleanly with the adaptive threshold — matching docs surface higher, non-matching docs drop.
93
+ - **Zero-match NO-OP** — if no docs in the scored result set match the topic, the boost stage is a no-op. The baseline ordering is returned unchanged, so the adaptive threshold sees byte-identical input and the result set never shrinks because of a non-matching focus. This fail-open contract is locked in by hook-level integration tests that assert byte-equality between a non-matching-topic run and a no-topic run.
94
+ - **Session isolation** — the focus file is keyed by `sessionId`, never touches SQLite, and concurrent sessions on the same host cannot cross-contaminate each other's topic biasing. `CLAWMEM_SESSION_FOCUS` env var is a debug-only override that does NOT provide per-session scoping — do not rely on it for multi-session deployments.
95
+
96
+ ### Hook blind spots
97
+
98
+ Hooks filter aggressively — they enforce score thresholds, cap token budgets, and exclude system artifacts. If a memory exists but wasn't surfaced in `<vault-context>`, it doesn't mean it's missing from the vault. It means it didn't make the top-k cut for this prompt.
99
+
100
+ ## Tier 3 — MCP Tools (agent-initiated)
101
+
102
+ The agent should escalate to MCP tools only when one of three rules fires:
103
+
104
+ 1. **Low-specificity** — `<vault-context>` is empty or missing the specific fact needed
105
+ 2. **Cross-session** — the task references prior sessions or decisions ("why did we decide X")
106
+ 3. **Pre-irreversible** — about to make a destructive or hard-to-reverse change
107
+
108
+ ### Preferred entry point
109
+
110
+ Use `memory_retrieve(query)` — it auto-classifies the query and routes to the optimal backend:
111
+
112
+ - "why did we decide X" → intent_search (causal graph traversal)
113
+ - "what happened last session" → session_log
114
+ - "what else relates to X" → find_similar (vector neighbors)
115
+ - complex multi-topic → query_plan (parallel decomposition)
116
+ - general recall → query (full hybrid pipeline)
117
+
118
+ ### Direct routing (when you know which tool to use)
119
+
120
+ | Query type | Tool | Why not query()? |
121
+ |-----------|------|-----------------|
122
+ | Why / what caused / decision | `intent_search` | Graph traversal finds causal chains query() can't |
123
+ | Last session / yesterday | `session_log` | Session-specific data not in search index |
124
+ | What else relates to X | `find_similar` | k-NN vector neighbors, not keyword overlap |
125
+ | Entity facts / relationships | `kg_query` | Structured SPO triples, not document search |
126
+ | Complex multi-topic | `query_plan` | Decomposes into typed parallel retrieval |
127
+ | General recall | `query` | Full hybrid: BM25 + vector + expansion + reranking |
128
+ | Keyword spot check | `search` | BM25 only, zero GPU cost |
129
+ | Conceptual / fuzzy | `vsearch` | Vector only, semantic similarity |
130
+
131
+ `diary_write` and `diary_read` are for non-hooked environments only (Hermes, Gemini, plain MCP clients). In Claude Code, hooks capture observations and handoffs automatically.
132
+
133
+ ### Anti-patterns
134
+
135
+ - Do NOT call MCP tools every turn — the three rules above are the only gates
136
+ - Do NOT re-search what's already in `<vault-context>`
137
+ - Do NOT use `query()` for "why" questions — use `intent_search` or `memory_retrieve`
138
+ - Do NOT use `query()` for session history — use `session_log`
139
+ - Do NOT use `kg_query()` for causal "why" questions — use `intent_search`. `kg_query` returns structured facts, not reasoning chains
140
+ - Do NOT use `diary_write` in Claude Code — hooks handle this automatically
141
+
142
+ ## Why hooks handle 90%
143
+
144
+ The 90/10 split between hooks and MCP tools is a deliberate architectural response to a real limitation: agents are not reliably proactive with memory tools.
145
+
146
+ When an agent has both native tools (Read, Grep) and memory tools (query, intent_search) available, it will consistently default to the simpler native tools or answer from existing context — even when a vault search would produce better results. This isn't a bug in any particular model. MCP tool calls add latency and consume context window. The agent's implicit cost/benefit calculation favors "answer now" over "search first, answer better." The result is that purely agent-initiated memory systems get underused in practice.
147
+
148
+ Hooks bypass this problem entirely. Context-surfacing fires on every prompt regardless of what the agent decides to do. Decision-extractor captures observations after every response. Feedback-loop tracks what was referenced. The agent doesn't get to skip these — they run as part of the Claude Code lifecycle, not as tool calls the agent can choose to make.
149
+
150
+ The remaining 10% — the MCP tools — covers situations where hooks can't help: the agent needs deeper search than what context-surfacing provided, the question spans multiple sessions, or a destructive action needs a vault check first. These genuinely require agent initiative, and the 3-rule escalation gate keeps the scope narrow enough that agents can follow it.
151
+
152
+ ### Making agents more proactive
153
+
154
+ For the proactive operations agents should be doing (pinning critical decisions, snoozing noisy context, running deeper searches when surfaced context is relevant but thin), instruction redundancy helps. Place the routing rules and escalation gates in your global CLAUDE.md or AGENTS.md so they load on every conversation. The trigger block in the README's [Agent Instructions](../README.md#agent-instructions) section is designed for this — it gives the agent routing rules always loaded, with SKILL.md as on-demand deep reference.
155
+
156
+ This stubbornness around proactive memory tool use is unlikely to change until model providers include memory management patterns in their training data. Until then, hooks carry the weight, and instruction redundancy is the best mitigation for the rest.
@@ -0,0 +1,71 @@
1
+ # Multi-vault configuration
2
+
3
+ By default, ClawMem stores all AI agent memory in a single vault at `~/.cache/clawmem/index.sqlite`. Multi-vault is opt-in for users who want separate memory domains.
4
+
5
+ ## When to use multiple vaults
6
+
7
+ - **Project isolation** — keep work and personal memory separate
8
+ - **Shared vs private** — a shared team vault alongside a personal vault
9
+ - **Runtime separation** — separate vaults for Claude Code and OpenClaw (though shared single vault is the recommended default)
10
+
11
+ ## Configuration
12
+
13
+ ### Via config.yaml
14
+
15
+ ```yaml
16
+ # ~/.config/clawmem/config.yaml
17
+ vaults:
18
+ work: ~/.cache/clawmem/work.sqlite
19
+ personal: ~/.cache/clawmem/personal.sqlite
20
+ ```
21
+
22
+ ### Via environment variable
23
+
24
+ ```bash
25
+ export CLAWMEM_VAULTS='{"work":"~/.cache/clawmem/work.sqlite","personal":"~/.cache/clawmem/personal.sqlite"}'
26
+ ```
27
+
28
+ Environment variables override config file values. Vault paths support `~` expansion.
29
+
30
+ ## Using vaults with tools
31
+
32
+ All MCP tools accept an optional `vault` parameter. Omit it for the default vault.
33
+
34
+ ```
35
+ # Search the work vault
36
+ query("authentication setup", vault="work", compact=true)
37
+
38
+ # Pin a memory in the personal vault
39
+ memory_pin("remember preference X", vault="personal")
40
+
41
+ # Get lifecycle stats for a specific vault
42
+ lifecycle_status(vault="work")
43
+ ```
44
+
45
+ Tools that support vault: `query`, `search`, `vsearch`, `intent_search`, `memory_retrieve`, `query_plan`, `get`, `multi_get`, `find_similar`, `find_causal_links`, `timeline`, `memory_evolution_status`, `session_log`, `memory_pin`, `memory_snooze`, `memory_forget`, `lifecycle_status`, `lifecycle_sweep`, `lifecycle_restore`, `status`, `reindex`, `index_stats`, `build_graphs`.
46
+
47
+ ## Populating a vault
48
+
49
+ Use `vault_sync` to index content into a named vault:
50
+
51
+ ```
52
+ vault_sync(vault="work", content_root="~/projects/work-notes", pattern="**/*.md")
53
+ ```
54
+
55
+ `vault_sync` includes restricted-path validation — it rejects sensitive directories like `/etc/`, `/root/`, `.ssh`, `.env`, `credentials`, `.aws`, `.kube`.
56
+
57
+ ## Listing vaults
58
+
59
+ ```
60
+ list_vaults()
61
+ ```
62
+
63
+ Returns configured vault names and paths. Empty in single-vault mode.
64
+
65
+ ## Implementation details
66
+
67
+ - Named vault stores are cached in memory (`Map<string, Store>`) and reused across tool calls
68
+ - All cached stores are closed on MCP server shutdown (SIGINT/SIGTERM)
69
+ - Each vault is an independent SQLite database with its own documents, embeddings, graphs, and sessions
70
+ - WAL mode + `busy_timeout=5000ms` ensures safe concurrent access
71
+ - Hooks always operate on the default vault — vault selection is only available through MCP tools and REST API
@@ -0,0 +1,101 @@
1
+ # Contributing to ClawMem
2
+
3
+ Development guide for the ClawMem memory engine (TypeScript on Bun, SQLite vector search, Claude Code hooks, MCP server).
4
+
5
+ ## Development setup
6
+
7
+ ```bash
8
+ git clone https://github.com/yoloshii/clawmem.git
9
+ cd clawmem
10
+ bun install
11
+ ```
12
+
13
+ ## Running tests
14
+
15
+ ```bash
16
+ bun test # All tests
17
+ bun test tests/unit # Unit tests only
18
+ ```
19
+
20
+ Tests use in-memory SQLite databases and don't require GPU services.
21
+
22
+ ## Type checking
23
+
24
+ ```bash
25
+ npx tsc --noEmit
26
+ ```
27
+
28
+ Must pass with zero errors on source files.
29
+
30
+ ## Project structure
31
+
32
+ ```
33
+ src/
34
+ clawmem.ts CLI entry point
35
+ mcp.ts MCP server
36
+ server.ts REST API server
37
+ store.ts SQLite store (documents, vectors, relations)
38
+ llm.ts LLM abstraction (embedding, generation, reranking)
39
+ config.ts Vault configuration, profiles, lifecycle policy
40
+ memory.ts Composite scoring (SAME)
41
+ search-utils.ts RRF, enrichment, ranking utilities
42
+ mmr.ts Maximal Marginal Relevance diversity filter
43
+ intent.ts Intent classification (MAGMA)
44
+ graph-traversal.ts Adaptive multi-hop traversal
45
+ indexer.ts Collection scanner, document indexer
46
+ collections.ts Collection configuration loader
47
+ validation.ts Input validation helpers
48
+ normalize.ts Conversation format normalizer (Claude, ChatGPT, Slack, plain text)
49
+ recall-buffer.ts Recall event writing (direct SQLite write during context-surfacing)
50
+ recall-attribution.ts Per-turn reference attribution (transcript segmentation + usage linkage)
51
+ limits.ts Constants (max path length, query length)
52
+ errors.ts Error types
53
+ promptguard.ts Prompt injection sanitization
54
+ retrieval-gate.ts Adaptive retrieval filtering
55
+ hooks.ts Hook utilities (output format, dedup, logging)
56
+ hooks/
57
+ context-surfacing.ts UserPromptSubmit hook
58
+ decision-extractor.ts Stop hook (observations)
59
+ handoff-generator.ts Stop hook (session summary)
60
+ feedback-loop.ts Stop hook (reference tracking)
61
+ precompact-extract.ts PreCompact hook
62
+ session-bootstrap.ts SessionStart hook (optional)
63
+ staleness-check.ts SessionStart hook (optional)
64
+ curator-nudge.ts SessionStart hook
65
+ openclaw/
66
+ index.ts Plugin entry point (registers as kind=memory, wires hook handlers)
67
+ engine.ts Retrieval/extraction engine (invoked from hook handlers in index.ts)
68
+ shell.ts Shell-out transport utilities
69
+ tools.ts REST API agent tools
70
+ openclaw.plugin.json Legacy plugin manifest (still shipped; parsed at runtime)
71
+ package.json OpenClaw v2026.4.11+ discovery manifest (openclaw.extensions)
72
+ tests/
73
+ unit/ Unit tests
74
+ integration/ Integration tests (when present)
75
+ docs/ Documentation (this folder)
76
+ bin/
77
+ clawmem Wrapper script (sets env defaults)
78
+ ```
79
+
80
+ ## Pull request guidelines
81
+
82
+ 1. **Describe what and why** — not just what changed, but why
83
+ 2. **Include test coverage** — new features need tests, bug fixes should include a regression test
84
+ 3. **Type check clean** — `npx tsc --noEmit` must pass
85
+ 4. **All tests pass** — `bun test` must pass
86
+ 5. **Keep changes focused** — one feature or fix per PR
87
+
88
+ ## What gets indexed
89
+
90
+ Only `.md` files. Never add indexing for binary files, source code, or credentials.
91
+
92
+ ## Security considerations
93
+
94
+ - Never index or expose credential files (`.env`, `*secrets*`, `*credentials*`)
95
+ - `vault_sync` validates paths against a deny-list — don't weaken it
96
+ - Prompt injection sanitization (`promptguard.ts`) strips control sequences from injected context
97
+ - Bearer token auth on REST API when `CLAWMEM_API_TOKEN` is set
98
+
99
+ ## License
100
+
101
+ MIT. See [LICENSE](../LICENSE).
@@ -0,0 +1,134 @@
1
+ # Cloud embedding for ClawMem
2
+
3
+ By default, ClawMem embeds AI agent memory locally via `llama-server` or in-process `node-llama-cpp` fallback (Metal on Apple Silicon, Vulkan where available, CPU as last resort). With GPU acceleration the fallback is fast; CPU-only is significantly slower. As an alternative, you can use a cloud embedding provider instead of running models on your machine.
4
+
5
+ ## Supported providers
6
+
7
+ | Provider | URL | Model | Dimensions |
8
+ |----------|-----|-------|-----------|
9
+ | Jina AI | `https://api.jina.ai` | `jina-embeddings-v5-text-small` | 1024 |
10
+ | OpenAI | `https://api.openai.com` | `text-embedding-3-small` | 1536 |
11
+ | Voyage AI | `https://api.voyageai.com` | `voyage-4-large` | 1024 |
12
+ | Cohere | `https://api.cohere.com` | `embed-v4.0` | 1024 |
13
+
14
+ All providers use the OpenAI-compatible `/v1/embeddings` endpoint with Bearer token auth.
15
+
16
+ ## Configuration
17
+
18
+ Copy `.env.example` to `.env` and set your provider credentials:
19
+
20
+ ```bash
21
+ cp .env.example .env
22
+ # Edit .env:
23
+ CLAWMEM_EMBED_URL=https://api.jina.ai
24
+ CLAWMEM_EMBED_API_KEY=jina_your-key-here
25
+ CLAWMEM_EMBED_MODEL=jina-embeddings-v5-text-small
26
+ ```
27
+
28
+ Or export them in your shell before running `clawmem`.
29
+
30
+ **Precedence:** shell environment > `.env` file > `bin/clawmem` wrapper defaults. The wrapper sources `.env` from the project root before applying its defaults, so `.env` values override defaults but explicit shell exports still win.
31
+
32
+ ## Cloud mode features
33
+
34
+ When `CLAWMEM_EMBED_API_KEY` is set, cloud mode activates:
35
+
36
+ - **Batch embedding** — 50 fragments per API request (vs 1 per request for local). Reduces total API calls ~50×.
37
+ - **Provider-specific retrieval params** — Auto-detected from your `CLAWMEM_EMBED_URL`. Documents and queries get different params for optimal retrieval quality (see table below).
38
+ - **Server-side truncation** — Provider-appropriate truncation params sent automatically. Oversized inputs are truncated server-side instead of returning errors.
39
+ - **Adaptive TPM-aware pacing** — Delays between batches computed from actual token usage to stay within your tier's tokens-per-minute limit. No hardcoded tier assumptions.
40
+ - **Retry with jitter** — 429 responses trigger retries with exponential backoff (5s → 10s → 20s, max 3 retries) plus random jitter to prevent synchronized retry storms.
41
+
42
+ ### Provider-specific parameters
43
+
44
+ ClawMem auto-detects your provider from the URL and sends the right params:
45
+
46
+ | Provider | Document embedding | Query embedding | Truncation | Extra |
47
+ |----------|-------------------|-----------------|------------|-------|
48
+ | Jina AI | `task: "retrieval.passage"` | `task: "retrieval.query"` | `truncate: true` | |
49
+ | Voyage AI | `input_type: "document"` | `input_type: "query"` | Default (true) | |
50
+ | Cohere | `input_type: "search_document"` | `input_type: "search_query"` | `truncate: "END"` | |
51
+ | OpenAI | Symmetric (none) | Symmetric (none) | None (8192 max) | `dimensions` via `CLAWMEM_EMBED_DIMENSIONS` |
52
+
53
+ No configuration needed — just set `CLAWMEM_EMBED_URL` to the provider and the correct params are applied. For OpenAI's `text-embedding-3-*` models, optionally set `CLAWMEM_EMBED_DIMENSIONS` to reduce output dimensions (e.g. `512` or `1024`).
54
+
55
+ **Note on OpenAI truncation:** OpenAI does not auto-truncate — inputs exceeding 8192 tokens return an error. ClawMem's default chunk size is ~800 tokens, well under this limit, so this is not an issue in practice.
56
+
57
+ ## Rate limiting
58
+
59
+ Cloud providers enforce rate limits on both requests-per-minute (RPM) and tokens-per-minute (TPM). TPM is typically the binding constraint for embedding.
60
+
61
+ Set `CLAWMEM_EMBED_TPM_LIMIT` to match your provider tier:
62
+
63
+ | Tier | RPM | TPM | `CLAWMEM_EMBED_TPM_LIMIT` |
64
+ |------|-----|-----|--------------------------|
65
+ | Jina Free | 100 | 100,000 | `100000` (default) |
66
+ | Jina Paid | 500 | 2,000,000 | `2000000` |
67
+ | Jina Premium | 5,000 | 50,000,000 | `50000000` |
68
+
69
+ ```bash
70
+ # Example: paid tier
71
+ export CLAWMEM_EMBED_TPM_LIMIT=2000000
72
+ ```
73
+
74
+ The adaptive pacer computes delay as `(batchTokens / (TPM_LIMIT × 0.85)) × 60s`, using actual token counts from the API response when available, falling back to character-based estimation. The 0.85 safety factor leaves headroom for retries.
75
+
76
+ ## Truncation behavior
77
+
78
+ - **Local embedding** (no API key): Input truncated to `CLAWMEM_EMBED_MAX_CHARS` (default 6000) before sending. This prevents oversized inputs from exceeding the model's token context.
79
+ - **Cloud embedding** (API key set): Client-side truncation is skipped. Server-side truncation is requested via `truncate: true`.
80
+
81
+ To override the local truncation limit:
82
+
83
+ ```bash
84
+ export CLAWMEM_EMBED_MAX_CHARS=4000 # for models with smaller context
85
+ ```
86
+
87
+ ## Localhost warning
88
+
89
+ If you set `CLAWMEM_EMBED_API_KEY` but your `CLAWMEM_EMBED_URL` points to localhost or 127.0.0.1, ClawMem prints a one-time warning. This catches accidental configurations where an API key is sent to a local server. If you're using a local API gateway intentionally, the warning is safe to ignore.
90
+
91
+ ## Mixing local and cloud
92
+
93
+ The LLM (query expansion) and reranker always use local `llama-server` or in-process `node-llama-cpp` fallback. Only embedding supports cloud providers. This means:
94
+
95
+ - **Embedding** — local GPU, cloud API, or in-process via `node-llama-cpp` (Metal/Vulkan/CPU — fast with GPU acceleration, slow on CPU-only)
96
+ - **LLM** — local GPU, falls back to in-process `node-llama-cpp`
97
+ - **Reranker** — local GPU, falls back to in-process `node-llama-cpp`
98
+
99
+ **Note:** In-process fallback is silent — if a GPU server crashes, there is no warning. With Metal/Vulkan the fallback is fast; on CPU-only it is significantly slower. Set `CLAWMEM_NO_LOCAL_MODELS=true` to fail fast instead, or use [systemd services](systemd-services.md) to keep servers running.
100
+
101
+ ## Model recommendations
102
+
103
+ **Default (QMD native combo, any GPU or in-process):** EmbeddingGemma-300M-Q8_0 (314MB, 768d) + qwen3-reranker-0.6B (600MB) + qmd-query-expansion-1.7B (~1.1GB). All three auto-download via `node-llama-cpp` if no server is running (Metal on Apple Silicon, Vulkan where available, CPU as last resort). Fast with GPU acceleration; significantly slower on CPU-only.
104
+
105
+ ```bash
106
+ llama-server -m embeddinggemma-300M-Q8_0.gguf \
107
+ --embeddings --port 8088 --host 0.0.0.0 -ngl 99 -c 2048 --batch-size 2048
108
+ ```
109
+
110
+ **SOTA upgrade (16GB+ GPU):** **ZeroEntropy zembed-1** (2560 dimensions, 32K context, SOTA retrieval quality, ~4.4GB VRAM) paired with the **zerank-2 seq-cls reranker sidecar** (distillation-paired via zELO). **CC-BY-NC-4.0** — non-commercial only.
111
+
112
+ ```bash
113
+ llama-server -m zembed-1-Q4_K_M.gguf \
114
+ --embeddings --port 8088 --host 0.0.0.0 -ngl 99 -c 8192 -b 2048 -ub 2048
115
+ ```
116
+
117
+ The reranker is **not** a GGUF — serve it via the seq-cls sidecar (transformers, bf16): see [`extras/rerankers/zerank-2-seq/`](../../extras/rerankers/zerank-2-seq/). The old `zerank-2-Q4_K_M` GGUF is deprecated (llama.cpp drops its score head → near-zero, uninformative scores; final ordering stays RRF-dominated).
118
+
119
+ For cloud, **Jina AI `jina-embeddings-v5-text-small`** is recommended (1024 dimensions, 32K context, task-specific LoRA adapters for retrieval).
120
+
121
+ ## Switching embedding models
122
+
123
+ When changing to a model with different output dimensions (e.g. 768d → 2560d), a full re-embed is required:
124
+
125
+ ```bash
126
+ clawmem embed --force
127
+ ```
128
+
129
+ This clears all existing vectors and rebuilds with the new model's dimensions. The vector table is automatically recreated with the correct dimension size on the first embedded fragment.
130
+
131
+ **Important notes:**
132
+ - `--force` is safe to interrupt and resume — embed is idempotent and skips already-embedded documents
133
+ - `-ub` must equal `-b` on `llama-server` for embedding/reranking models (non-causal attention). Omitting `-ub` causes assertion crashes.
134
+ - Set `-c` (context) high enough for your largest fragments. zembed-1 supports 32K; `-c 8192` is recommended.