clawmem 0.8.4 → 0.9.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.
package/AGENTS.md CHANGED
@@ -128,15 +128,15 @@ ln -sf ~/clawmem/bin/clawmem ~/.bun/bin/clawmem
128
128
  clawmem bootstrap ~/notes --name notes
129
129
 
130
130
  # Or step by step:
131
- ./bin/clawmem init
132
- ./bin/clawmem collection add ~/notes --name notes
133
- ./bin/clawmem update --embed
134
- ./bin/clawmem setup hooks
135
- ./bin/clawmem setup mcp
131
+ clawmem init
132
+ clawmem collection add ~/notes --name notes
133
+ clawmem update --embed
134
+ clawmem setup hooks
135
+ clawmem setup mcp
136
136
 
137
137
  # Verify
138
- ./bin/clawmem doctor # Full health check
139
- ./bin/clawmem status # Quick index status
138
+ clawmem doctor # Full health check
139
+ clawmem status # Quick index status
140
140
  ```
141
141
 
142
142
  ### Background Services (systemd user units)
@@ -206,18 +206,17 @@ systemctl --user status clawmem-watcher.service clawmem-embed.timer
206
206
 
207
207
  When using ClawMem with OpenClaw, choose one of two deployment options:
208
208
 
209
- ### Option 1: ClawMem Exclusive (Recommended)
209
+ **Active Memory coexistence:** ClawMem is fully compatible with OpenClaw's Active Memory plugin (v2026.4.10+). They search different backends (ClawMem vault vs dreaming/wiki) and inject into different prompt regions (user prompt vs system prompt). Both can run simultaneously — no configuration needed.
210
+
211
+ **OpenClaw v2026.4.10+ recommended:** Fixes a config normalization bug where `plugins.slots.contextEngine` was silently dropped (#64192).
210
212
 
211
- ClawMem handles 100% of memory operations via hooks + MCP tools. Zero redundancy.
213
+ ### Option 1: ClawMem Exclusive (Recommended)
212
214
 
213
- **Benefits:**
214
- - No context window waste (avoids 10-15% duplicate injection)
215
- - Prevents OpenClaw native memory auto-initialization on updates
216
- - All memory in ClawMem's hybrid search + graph traversal system
215
+ ClawMem handles 100% of structured memory. Disable native memory search (not Active Memory — that's separate and compatible):
217
216
 
218
217
  **Configuration:**
219
218
  ```bash
220
- # Disable OpenClaw's native memory
219
+ # Disable OpenClaw's native memory search
221
220
  openclaw config set agents.defaults.memorySearch.extraPaths "[]"
222
221
 
223
222
  # Verify
@@ -235,7 +234,7 @@ ls ~/.openclaw/agents/main/memory/
235
234
 
236
235
  ### Option 2: Hybrid (ClawMem + Native)
237
236
 
238
- Run both ClawMem and OpenClaw's native memory for redundancy.
237
+ Run both ClawMem and OpenClaw's native memory search for redundancy.
239
238
 
240
239
  **Configuration:**
241
240
  ```bash
@@ -243,9 +242,9 @@ openclaw config set agents.defaults.memorySearch.extraPaths '["~/documents", "~/
243
242
  ```
244
243
 
245
244
  **Tradeoffs:**
246
- - Redundant recall from two independent systems
247
- - 10-15% context window waste from duplicate facts
248
- - Two memory indices to maintain
245
+ - Redundant recall from two independent systems
246
+ - 10-15% context window waste from duplicate facts
247
+ - Two memory indices to maintain
249
248
 
250
249
  **Recommendation:** Use Option 1 unless you have a specific need for redundant memory systems.
251
250
 
@@ -259,11 +258,11 @@ ClawMem hooks handle ~90% of retrieval automatically. Agent-initiated MCP calls
259
258
 
260
259
  | Hook | Trigger | Budget | Content |
261
260
  |------|---------|--------|---------|
262
- | `context-surfacing` | UserPromptSubmit | profile-driven (default 800) | retrieval gate → **multi-turn query construction** (v0.8.1: current prompt + up to 2 recent same-session priors from `context_usage.query_text`, 10-min max age, capped at 2000 chars with current-first preservation — used only for discovery: vector/FTS/expansion, NOT for rerank/scoring/snippet extraction) → profile-driven hybrid search (vector if `useVector`, timeout from profile) → FTS supplement → file-aware supplemental search (E13, raw current prompt) → snooze filter → noise filter → spreading activation (E11: co-activated doc boost) → memory type diversification (E10) → tiered injection (HOT/WARM/COLD snippets) → `<vault-context><instruction>…</instruction><facts>…</facts><relationships>…</relationships></vault-context>` (v0.7.1: instruction always prepended when context is returned; relationships block lists memory-graph edges where BOTH endpoints are in the surfaced set, truncated first when over budget) + optional `<vault-routing>` hint. Budget, max results, vector timeout, and min score all driven by `CLAWMEM_PROFILE`. Raw prompt persisted to `context_usage.query_text` for future multi-turn lookback — except on gated skip paths (slash commands, heartbeats, too-short prompts) where the text is withheld for privacy. |
261
+ | `context-surfacing` | UserPromptSubmit | profile-driven (default 800 + factsTokens sub-budget) | retrieval gate → **multi-turn query construction** (v0.8.1: current prompt + up to 2 recent same-session priors from `context_usage.query_text`, 10-min max age, capped at 2000 chars with current-first preservation — used only for discovery: vector/FTS/expansion, NOT for rerank/scoring/snippet extraction) → **session focus topic resolution** (v0.9.0 §11.4: reads per-session focus file at `~/.cache/clawmem/sessions/<id>.focus`, threaded as intent hint to `expandQuery` + `rerank` + `extractSnippet`) → profile-driven hybrid search (vector if `useVector`, timeout from profile) → FTS supplement → file-aware supplemental search (E13, raw current prompt) → snooze filter → noise filter → spreading activation (E11: co-activated doc boost) → composite scoring → **session focus topic boost** (v0.9.0 §11.4: 1.4× match / 0.75× demote floor 50%, NO-OP on zero matches to preserve baseline ordering) → adaptive threshold → memory type diversification (E10) → tiered injection (HOT/WARM/COLD snippets) → `<vault-context><instruction>…</instruction><facts>…</facts><relationships>…</relationships><vault-facts>…</vault-facts></vault-context>` (v0.7.1: instruction always prepended when context is returned; relationships block lists memory-graph edges where BOTH endpoints are in the surfaced set, truncated first when over budget. **v0.9.0 §11.1:** `<vault-facts>` KG injection block appends raw SPO triple lines from entities seeded by the prompt via three-path prompt-only extraction — canonical IDs + proper nouns + longer-first n-grams — with a dedicated `factsTokens` sub-budget per profile (speed=0 disables the stage, balanced=200, deep=250), cross-entity triple dedup, and truncate-at-triple-boundary budget discipline; fail-open on every error path) + optional `<vault-routing>` hint. Budget, max results, vector timeout, min score, and facts sub-budget all driven by `CLAWMEM_PROFILE`. Raw prompt persisted to `context_usage.query_text` for future multi-turn lookback — except on gated skip paths (slash commands, heartbeats, too-short prompts) where the text is withheld for privacy. |
263
262
  | `postcompact-inject` | SessionStart (compact) | 1200 tokens | re-injects authoritative context after compaction: precompact state (600) + recent decisions (400) + antipatterns (150) + vault context (200) → `<vault-postcompact>` |
264
263
  | `curator-nudge` | SessionStart | 200 tokens | surfaces curator report actions, nudges when report is stale (>7 days) |
265
264
  | `precompact-extract` | PreCompact | — | extracts decisions, file paths, open questions → writes `precompact-state.md` to auto-memory. Query-aware decision ranking. Reindexes auto-memory collection. |
266
- | `decision-extractor` | Stop | — | LLM extracts observations → `_clawmem/agent/observations/`, infers causal links, detects contradictions, extracts SPO triples from decision/preference/milestone/problem facts. Background consolidation worker synthesizes deductive observations from related facts (Phase 3, every ~15 min). |
265
+ | `decision-extractor` | Stop | — | LLM extracts observations → `_clawmem/agent/observations/`, infers causal links, detects contradictions, persists observer-emitted SPO triples via `ensureEntityCanonical` (canonical `vault:type:slug` IDs shared with A-MEM) using the tight predicate vocabulary (adopted, migrated_to, deployed_to, runs_on, replaced, depends_on, integrates_with, uses, prefers, avoids, caused_by, resolved_by, owned_by). Eligible observation types: decision/preference/milestone/problem/discovery/feature. Background consolidation worker synthesizes deductive observations from related facts (Phase 3, every ~15 min). |
267
266
  | `handoff-generator` | Stop | — | LLM summarizes session → `_clawmem/agent/handoffs/` |
268
267
  | `feedback-loop` | Stop | — | tracks referenced notes → boosts confidence, records usage relations + co-activations between co-referenced docs, tracks utility signals (surfaced vs referenced ratio for lifecycle automation), per-turn recall attribution (marks which surfaced docs were cited in which turn) |
269
268
 
@@ -713,6 +712,18 @@ clawmem consolidate [--dry-run] # Find and archive duplicate low-confidence docu
713
712
  # Uses Jaccard similarity within same collection
714
713
  ```
715
714
 
715
+ **Session focus topic (v0.9.0 §11.4):** Per-session topic biasing for context-surfacing. Writes a focus file at `~/.cache/clawmem/sessions/<session_id>.focus` that steers query expansion, reranking, snippet extraction, and post-composite-score topic boost (1.4× match / 0.75× demote, NO-OP on zero matches). Session-isolated — never writes to SQLite or lifecycle columns. The session ID is read from `--session-id <id>`, then `CLAUDE_SESSION_ID`, then `CLAWMEM_SESSION_ID`. When to use: user says "focus on authentication for this session" / "only surface X-related docs right now" / "let's work on Y this session." Clear the focus at the end of the subsession to return to baseline surfacing.
716
+
717
+ ```bash
718
+ # Set a focus topic for the current session (multi-word OK)
719
+ clawmem focus set "authentication flow" # uses CLAUDE_SESSION_ID / CLAWMEM_SESSION_ID env var
720
+ clawmem focus set "authentication flow" --session-id abc123 # explicit
721
+
722
+ # Show / clear
723
+ clawmem focus show --session-id abc123
724
+ clawmem focus clear --session-id abc123
725
+ ```
726
+
716
727
  ## Integration Notes
717
728
 
718
729
  - **Memory nudge (v0.2.0):** Every N prompts (default 15) without a lifecycle MCP tool call (`memory_pin`/`memory_forget`/`memory_snooze`), context-surfacing appends `<vault-nudge>` prompting proactive memory management. Counter resets on lifecycle tool use. Configure via `CLAWMEM_NUDGE_INTERVAL` (0 to disable).
package/CLAUDE.md CHANGED
@@ -128,15 +128,15 @@ ln -sf ~/clawmem/bin/clawmem ~/.bun/bin/clawmem
128
128
  clawmem bootstrap ~/notes --name notes
129
129
 
130
130
  # Or step by step:
131
- ./bin/clawmem init
132
- ./bin/clawmem collection add ~/notes --name notes
133
- ./bin/clawmem update --embed
134
- ./bin/clawmem setup hooks
135
- ./bin/clawmem setup mcp
131
+ clawmem init
132
+ clawmem collection add ~/notes --name notes
133
+ clawmem update --embed
134
+ clawmem setup hooks
135
+ clawmem setup mcp
136
136
 
137
137
  # Verify
138
- ./bin/clawmem doctor # Full health check
139
- ./bin/clawmem status # Quick index status
138
+ clawmem doctor # Full health check
139
+ clawmem status # Quick index status
140
140
  ```
141
141
 
142
142
  ### Background Services (systemd user units)
@@ -258,11 +258,11 @@ ClawMem hooks handle ~90% of retrieval automatically. Agent-initiated MCP calls
258
258
 
259
259
  | Hook | Trigger | Budget | Content |
260
260
  |------|---------|--------|---------|
261
- | `context-surfacing` | UserPromptSubmit | profile-driven (default 800) | retrieval gate → **multi-turn query construction** (v0.8.1: current prompt + up to 2 recent same-session priors from `context_usage.query_text`, 10-min max age, capped at 2000 chars with current-first preservation — used only for discovery: vector/FTS/expansion, NOT for rerank/scoring/snippet extraction) → profile-driven hybrid search (vector if `useVector`, timeout from profile) → FTS supplement → file-aware supplemental search (E13, raw current prompt) → snooze filter → noise filter → spreading activation (E11: co-activated doc boost) → memory type diversification (E10) → tiered injection (HOT/WARM/COLD snippets) → `<vault-context><instruction>…</instruction><facts>…</facts><relationships>…</relationships></vault-context>` (v0.7.1: instruction always prepended when context is returned; relationships block lists memory-graph edges where BOTH endpoints are in the surfaced set, truncated first when over budget) + optional `<vault-routing>` hint. Budget, max results, vector timeout, and min score all driven by `CLAWMEM_PROFILE`. Raw prompt persisted to `context_usage.query_text` for future multi-turn lookback — except on gated skip paths (slash commands, heartbeats, too-short prompts) where the text is withheld for privacy. |
261
+ | `context-surfacing` | UserPromptSubmit | profile-driven (default 800 + factsTokens sub-budget) | retrieval gate → **multi-turn query construction** (v0.8.1: current prompt + up to 2 recent same-session priors from `context_usage.query_text`, 10-min max age, capped at 2000 chars with current-first preservation — used only for discovery: vector/FTS/expansion, NOT for rerank/scoring/snippet extraction) → **session focus topic resolution** (v0.9.0 §11.4: reads per-session focus file at `~/.cache/clawmem/sessions/<id>.focus`, threaded as intent hint to `expandQuery` + `rerank` + `extractSnippet`) → profile-driven hybrid search (vector if `useVector`, timeout from profile) → FTS supplement → file-aware supplemental search (E13, raw current prompt) → snooze filter → noise filter → spreading activation (E11: co-activated doc boost) → composite scoring → **session focus topic boost** (v0.9.0 §11.4: 1.4× match / 0.75× demote floor 50%, NO-OP on zero matches to preserve baseline ordering) → adaptive threshold → memory type diversification (E10) → tiered injection (HOT/WARM/COLD snippets) → `<vault-context><instruction>…</instruction><facts>…</facts><relationships>…</relationships><vault-facts>…</vault-facts></vault-context>` (v0.7.1: instruction always prepended when context is returned; relationships block lists memory-graph edges where BOTH endpoints are in the surfaced set, truncated first when over budget. **v0.9.0 §11.1:** `<vault-facts>` KG injection block appends raw SPO triple lines from entities seeded by the prompt via three-path prompt-only extraction — canonical IDs + proper nouns + longer-first n-grams — with a dedicated `factsTokens` sub-budget per profile (speed=0 disables the stage, balanced=200, deep=250), cross-entity triple dedup, and truncate-at-triple-boundary budget discipline; fail-open on every error path) + optional `<vault-routing>` hint. Budget, max results, vector timeout, min score, and facts sub-budget all driven by `CLAWMEM_PROFILE`. Raw prompt persisted to `context_usage.query_text` for future multi-turn lookback — except on gated skip paths (slash commands, heartbeats, too-short prompts) where the text is withheld for privacy. |
262
262
  | `postcompact-inject` | SessionStart (compact) | 1200 tokens | re-injects authoritative context after compaction: precompact state (600) + recent decisions (400) + antipatterns (150) + vault context (200) → `<vault-postcompact>` |
263
263
  | `curator-nudge` | SessionStart | 200 tokens | surfaces curator report actions, nudges when report is stale (>7 days) |
264
264
  | `precompact-extract` | PreCompact | — | extracts decisions, file paths, open questions → writes `precompact-state.md` to auto-memory. Query-aware decision ranking. Reindexes auto-memory collection. |
265
- | `decision-extractor` | Stop | — | LLM extracts observations → `_clawmem/agent/observations/`, infers causal links, detects contradictions, extracts SPO triples from decision/preference/milestone/problem facts. Background consolidation worker synthesizes deductive observations from related facts (Phase 3, every ~15 min). |
265
+ | `decision-extractor` | Stop | — | LLM extracts observations → `_clawmem/agent/observations/`, infers causal links, detects contradictions, persists observer-emitted SPO triples via `ensureEntityCanonical` (canonical `vault:type:slug` IDs shared with A-MEM) using the tight predicate vocabulary (adopted, migrated_to, deployed_to, runs_on, replaced, depends_on, integrates_with, uses, prefers, avoids, caused_by, resolved_by, owned_by). Eligible observation types: decision/preference/milestone/problem/discovery/feature. Background consolidation worker synthesizes deductive observations from related facts (Phase 3, every ~15 min). |
266
266
  | `handoff-generator` | Stop | — | LLM summarizes session → `_clawmem/agent/handoffs/` |
267
267
  | `feedback-loop` | Stop | — | tracks referenced notes → boosts confidence, records usage relations + co-activations between co-referenced docs, tracks utility signals (surfaced vs referenced ratio for lifecycle automation), per-turn recall attribution (marks which surfaced docs were cited in which turn) |
268
268
 
@@ -712,6 +712,18 @@ clawmem consolidate [--dry-run] # Find and archive duplicate low-confidence docu
712
712
  # Uses Jaccard similarity within same collection
713
713
  ```
714
714
 
715
+ **Session focus topic (v0.9.0 §11.4):** Per-session topic biasing for context-surfacing. Writes a focus file at `~/.cache/clawmem/sessions/<session_id>.focus` that steers query expansion, reranking, snippet extraction, and post-composite-score topic boost (1.4× match / 0.75× demote, NO-OP on zero matches). Session-isolated — never writes to SQLite or lifecycle columns. The session ID is read from `--session-id <id>`, then `CLAUDE_SESSION_ID`, then `CLAWMEM_SESSION_ID`. When to use: user says "focus on authentication for this session" / "only surface X-related docs right now" / "let's work on Y this session." Clear the focus at the end of the subsession to return to baseline surfacing.
716
+
717
+ ```bash
718
+ # Set a focus topic for the current session (multi-word OK)
719
+ clawmem focus set "authentication flow" # uses CLAUDE_SESSION_ID / CLAWMEM_SESSION_ID env var
720
+ clawmem focus set "authentication flow" --session-id abc123 # explicit
721
+
722
+ # Show / clear
723
+ clawmem focus show --session-id abc123
724
+ clawmem focus clear --session-id abc123
725
+ ```
726
+
715
727
  ## Integration Notes
716
728
 
717
729
  - **Memory nudge (v0.2.0):** Every N prompts (default 15) without a lifecycle MCP tool call (`memory_pin`/`memory_forget`/`memory_snooze`), context-surfacing appends `<vault-nudge>` prompting proactive memory management. Counter resets on lifecycle tool use. Configure via `CLAWMEM_NUDGE_INTERVAL` (0 to disable).
package/README.md CHANGED
@@ -31,6 +31,8 @@ ClawMem turns your markdown notes, project docs, and research dumps into persist
31
31
  - **Guards against cross-entity merges** during consolidation — name-aware dual-threshold merge safety compares entity anchors before merging similar observations, preventing "Alice decided X" from merging into "Bob decided X" (v0.7.1)
32
32
  - **Prevents context bleed in derived insights** — the Phase 3 deductive synthesis pipeline validates every draft against an anti-contamination wrapper (deterministic entity contamination check + LLM validator + dedupe) before writing cross-session deductive observations (v0.7.1)
33
33
  - **Frames surfaced facts as background knowledge** — `context-surfacing` wraps injected content in `<instruction>` + `<facts>` + `<relationships>` blocks, telling the model to treat facts as already-known and exposing memory-graph edges between surfaced docs directly in-prompt (v0.7.1)
34
+ - **Injects knowledge-graph facts as structured triples** — when the user's prompt mentions entities already known to the vault, `context-surfacing` resolves them via a three-path prompt-only extractor (canonical IDs, proper nouns, lowercased n-grams), queries the SPO graph for current-state triples, and appends a `<vault-facts>` block of raw `subject predicate object` lines to `<vault-context>` — off for `speed`, 200 tokens on `balanced`, 250 on `deep`, token-truncated at the triple boundary (v0.9.0)
35
+ - **Session-scoped focus topic boost** — `clawmem focus set "<topic>" --session-id <id>` writes a per-session focus file that steers query expansion, reranking, chunk selection, snippet extraction, and post-composite-score topic boosting (1.4× match / 0.75× demote) for that session only — session-isolated, fail-open, never writes to SQLite or lifecycle columns (v0.9.0)
34
36
  - **Scores document quality** using structure, keywords, and metadata richness signals
35
37
  - **Boosts co-accessed documents** — notes frequently surfaced together get retrieval reinforcement
36
38
  - **Decomposes complex queries** into typed retrieval clauses (BM25/vector/graph) for multi-topic questions
@@ -717,7 +719,7 @@ Registered by `clawmem setup mcp`. Available to any MCP-compatible client.
717
719
  |---|---|
718
720
  | `build_graphs` | Build temporal and/or semantic graphs from document corpus |
719
721
  | `find_causal_links` | Trace decision chains: "what led to X", "how we got from A to B". Follow up `intent_search` with this tool on a top result to walk the full causal chain. Traverses causes / caused_by / both up to N hops with depth-annotated reasoning. |
720
- | `kg_query` | Query the SPO knowledge graph: "what does X relate to?", "what was true about X when?". Returns temporal entity-relationship triples with validity windows. Uses entity resolution for lookup. |
722
+ | `kg_query` | Query the SPO knowledge graph: "what does X relate to?", "what was true about X when?". Returns temporal entity-relationship triples with validity windows. Accepts entity name (resolved via `searchEntities`) or canonical ID in `vault:type:slug` form. Triples are populated by the decision-extractor hook from observer-emitted `<triples>` blocks. |
721
723
  | `memory_evolution_status` | Show how a document's A-MEM metadata evolved over time |
722
724
  | `timeline` | Show the temporal neighborhood around a document — what was created/modified before and after it. Progressive disclosure: search → timeline (context) → get (full content). Supports same-collection scoping and session correlation. |
723
725
 
@@ -1073,40 +1075,36 @@ Manual layers benefit from periodic re-indexing — a cron job running `clawmem
1073
1075
  ### Setup
1074
1076
 
1075
1077
  ```bash
1076
- # Bootstrap workspace collection (use your agent's workspace path)
1077
- ./bin/clawmem bootstrap ~/workspace --name workspace
1078
-
1079
- # Bootstrap each project
1080
- ./bin/clawmem bootstrap ~/Projects/my-project --name my-project
1078
+ # Bootstrap a content directory (creates vault + indexes + embeds + installs hooks + MCP)
1079
+ clawmem bootstrap ~/notes --name notes
1081
1080
 
1082
- # Enable auto-embed for real-time indexing
1083
- # Edit ~/.config/clawmem/config.yaml autoEmbed: true
1081
+ # Bootstrap each project you want indexed
1082
+ clawmem bootstrap ~/Projects/my-project --name my-project
1084
1083
 
1085
- # Install watcher as systemd service
1086
- ./bin/clawmem install-service --enable
1084
+ # Install watcher + embed timer as systemd services
1085
+ clawmem install-service --enable
1087
1086
  ```
1088
1087
 
1089
- #### OpenClaw-Specific
1088
+ #### OpenClaw-specific
1090
1089
 
1091
1090
  ```bash
1092
- # OpenClaw uses ~/.openclaw/workspace/ as its workspace root
1093
- ./bin/clawmem bootstrap ~/.openclaw/workspace --name workspace
1091
+ # Install the ContextEngine plugin (auto-symlinks into ~/.openclaw/extensions/)
1092
+ clawmem setup openclaw
1093
+ # Then follow the printed next steps: restart gateway, set slot, configure GPU endpoints
1094
1094
  ```
1095
1095
 
1096
- #### Hermes-Specific
1096
+ Index your content directories with `clawmem bootstrap` as above. The OpenClaw plugin shares the same vault as Claude Code hooks.
1097
1097
 
1098
- ```bash
1099
- # Hermes uses ~/.hermes/ as its home directory
1100
- ./bin/clawmem bootstrap ~/.hermes --name hermes-home
1098
+ #### Hermes-specific
1101
1099
 
1102
- # Install the memory provider plugin
1103
- cp -r src/hermes /path/to/hermes-agent/plugins/memory/clawmem
1100
+ ```bash
1101
+ # Install the memory provider plugin (symlink or copy)
1102
+ ln -s $(npm root -g)/clawmem/src/hermes /path/to/hermes-agent/plugins/memory/clawmem
1104
1103
 
1105
- # Start clawmem serve (external mode)
1104
+ # Start the REST API (required for Hermes tool calls)
1106
1105
  clawmem serve --port 7438 &
1107
1106
 
1108
- # Configure Hermes to use ClawMem
1109
- # In your Hermes config.yaml:
1107
+ # Configure Hermes to use ClawMem (in your Hermes config.yaml):
1110
1108
  # memory:
1111
1109
  # provider: clawmem
1112
1110
  ```
package/SKILL.md CHANGED
@@ -118,15 +118,15 @@ ln -sf ~/clawmem/bin/clawmem ~/.bun/bin/clawmem
118
118
  clawmem bootstrap ~/notes --name notes
119
119
 
120
120
  # Or step by step:
121
- ./bin/clawmem init
122
- ./bin/clawmem collection add ~/notes --name notes
123
- ./bin/clawmem update --embed
124
- ./bin/clawmem setup hooks
125
- ./bin/clawmem setup mcp
121
+ clawmem init
122
+ clawmem collection add ~/notes --name notes
123
+ clawmem update --embed
124
+ clawmem setup hooks
125
+ clawmem setup mcp
126
126
 
127
127
  # Verify
128
- ./bin/clawmem doctor # Full health check
129
- ./bin/clawmem status # Quick index status
128
+ clawmem doctor # Full health check
129
+ clawmem status # Quick index status
130
130
  ```
131
131
 
132
132
  ### Background Services (systemd user units)
@@ -190,7 +190,7 @@ Hooks handle ~90% of retrieval. Zero agent effort.
190
190
 
191
191
  | Hook | Trigger | Budget | Content |
192
192
  |------|---------|--------|---------|
193
- | `context-surfacing` | UserPromptSubmit | profile-driven (default 800) | retrieval gate -> **multi-turn query** (v0.8.1: current + up to 2 recent same-session priors from `context_usage.query_text`, 10-min max age, 2000-char cap with current-first, used only for discovery not rerank/scoring/snippet) -> profile-driven hybrid search (vector if `useVector`, timeout from profile) -> FTS supplement -> file-aware search (E13, raw current) -> snooze filter -> noise filter -> spreading activation (E11) -> memory type diversification (E10) -> tiered injection (HOT/WARM/COLD) -> `<vault-context><instruction>...</instruction><facts>...</facts><relationships>...</relationships></vault-context>` (v0.7.1: instruction always prepended; relationships list memory-graph edges where BOTH endpoints are in the surfaced set; relationships truncated first when over budget) + optional `<vault-routing>` hint. Budget, max results, vector timeout, min score all driven by `CLAWMEM_PROFILE`. Raw prompt persisted to `context_usage.query_text` for future lookback — gated skip paths (slash commands, heartbeats, too-short prompts) withhold the text for privacy. |
193
+ | `context-surfacing` | UserPromptSubmit | profile-driven (default 800 + factsTokens sub-budget) | retrieval gate -> **multi-turn query** (v0.8.1: current + up to 2 recent same-session priors, discovery only) -> **session focus topic resolution** (v0.9.0 §11.4: reads `~/.cache/clawmem/sessions/<id>.focus`, threaded as intent hint to expansion/rerank/snippet) -> profile-driven hybrid search -> FTS supplement -> file-aware search (E13) -> snooze/noise filters -> spreading activation (E11) -> composite scoring -> **session focus topic boost** (v0.9.0 §11.4: 1.4x match / 0.75x demote, NO-OP on zero matches) -> adaptive threshold -> memory type diversification (E10) -> tiered injection (HOT/WARM/COLD) -> `<vault-context><instruction>...</instruction><facts>...</facts><relationships>...</relationships><vault-facts>...</vault-facts></vault-context>` (v0.7.1: instruction always prepended; relationships = memory-graph edges where BOTH endpoints are in the surfaced set, truncated first when over budget. **v0.9.0 §11.1:** `<vault-facts>` appends raw SPO triple lines when the prompt mentions known entities via three-path extraction (canonical-id regex + proper-noun validation + longer-first n-grams), dedicated `factsTokens` sub-budget per profile (speed=0, balanced=200, deep=250), cross-entity triple dedup, truncate-at-triple-boundary, fail-open on every error path) + optional `<vault-routing>` hint. Budget, max results, vector timeout, min score, facts sub-budget all driven by `CLAWMEM_PROFILE`. Raw prompt persisted to `context_usage.query_text` for future lookback — gated skip paths withhold the text for privacy. |
194
194
  | `postcompact-inject` | SessionStart (compact) | 1200 tokens | re-injects authoritative context after compaction: precompact state (600) + decisions (400) + antipatterns (150) + vault context (200) -> `<vault-postcompact>` |
195
195
  | `curator-nudge` | SessionStart | 200 tokens | surfaces curator report actions, nudges when report is stale (>7 days) |
196
196
  | `precompact-extract` | PreCompact | — | extracts decisions, file paths, open questions -> writes `precompact-state.md`. Query-aware ranking. Reindexes auto-memory. |
@@ -294,7 +294,7 @@ Once escalated, route by query type:
294
294
  | `timeline` | Temporal neighborhood around a document — what was modified before/after. Progressive disclosure: search → timeline → get. Supports same-collection scoping and session correlation. |
295
295
  | `list_vaults` | Show configured vault names and paths. Empty in single-vault mode. |
296
296
  | `vault_sync` | Index markdown from a directory into a named vault. Restricted-path validation rejects sensitive directories. |
297
- | `kg_query` | Query SPO knowledge graph for entity relationships with temporal validity. Uses entity resolution. |
297
+ | `kg_query` | Query SPO knowledge graph for entity relationships with temporal validity. Accepts entity name or canonical ID (`vault:type:slug`). Triples are populated by decision-extractor from observer-emitted `<triples>` blocks using a canonical predicate vocabulary. |
298
298
  | `diary_write` | Write diary entry. Use proactively in non-hooked environments. Do NOT use in Claude Code. |
299
299
  | `diary_read` | Read recent diary entries. Filter by agent name. |
300
300
  | `lifecycle_status` | Document lifecycle statistics: active, archived, forgotten, pinned, snoozed counts and policy summary. |
@@ -761,6 +761,19 @@ clawmem consolidate [--dry-run] # Find and archive duplicate low-confidence docu
761
761
  # Jaccard similarity within same collection
762
762
  ```
763
763
 
764
+ ### Session Focus Topic (v0.9.0 §11.4)
765
+
766
+ Per-session topic biasing for context-surfacing. Writes a focus file at `~/.cache/clawmem/sessions/<session_id>.focus` that steers query expansion, reranking, snippet extraction, and post-composite-score topic boost (1.4x match / 0.75x demote, NO-OP on zero matches). Session-isolated — never writes to SQLite or lifecycle columns. Session ID resolved from `--session-id <id>` > `CLAUDE_SESSION_ID` env > `CLAWMEM_SESSION_ID` env.
767
+
768
+ **When to use:** user says "focus on X for this session" / "only surface Y right now" / "let's work on Z." Clear at end of subsession to return to baseline.
769
+
770
+ ```bash
771
+ clawmem focus set "authentication flow" # uses CLAUDE_SESSION_ID env
772
+ clawmem focus set "authentication flow" --session-id abc123 # explicit session id
773
+ clawmem focus show --session-id abc123
774
+ clawmem focus clear --session-id abc123
775
+ ```
776
+
764
777
 
765
778
  ---
766
779
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmem",
3
- "version": "0.8.4",
3
+ "version": "0.9.0",
4
4
  "description": "On-device context engine and memory for AI agents. Claude Code and OpenClaw. Hooks + MCP server + hybrid RAG search.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/amem.ts CHANGED
@@ -649,11 +649,18 @@ export async function postIndexEnrich(
649
649
  }
650
650
 
651
651
  /**
652
- * Observation with document ID for causal inference
652
+ * Observation with document ID for causal inference and SPO triple extraction.
653
+ *
654
+ * Populated by the decision-extractor hook after an observation is successfully
655
+ * persisted. Consumed by:
656
+ * - `inferCausalLinks` (A-MEM) — uses docId + facts
657
+ * - `insertObservationTriples` (decision-extractor) — uses docId + obsType + triples
653
658
  */
654
659
  export interface ObservationWithDoc {
655
660
  docId: number;
656
661
  facts: string[];
662
+ obsType?: string;
663
+ triples?: Array<{ subject: string; predicate: string; object: string }>;
657
664
  }
658
665
 
659
666
  /**
package/src/clawmem.ts CHANGED
@@ -64,6 +64,12 @@ import { precompactExtract } from "./hooks/precompact-extract.ts";
64
64
  import { postcompactInject } from "./hooks/postcompact-inject.ts";
65
65
  import { pretoolInject } from "./hooks/pretool-inject.ts";
66
66
  import { curatorNudge } from "./hooks/curator-nudge.ts";
67
+ import {
68
+ readSessionFocus,
69
+ writeSessionFocus,
70
+ clearSessionFocus,
71
+ focusFilePath,
72
+ } from "./session-focus.ts";
67
73
 
68
74
  enableProductionMode();
69
75
 
@@ -1906,6 +1912,91 @@ async function cmdProfile(args: string[]) {
1906
1912
  }
1907
1913
  }
1908
1914
 
1915
+ // §11.4 (v0.9.0): session-scoped focus topic — read/write/clear the
1916
+ // per-session focus file at ~/.cache/clawmem/sessions/<session_id>.focus.
1917
+ // The file is the primary signal read by context-surfacing for topic
1918
+ // boosting; the CLAWMEM_SESSION_FOCUS env var is a debug-only override
1919
+ // that does NOT provide per-session scoping on multi-session hosts.
1920
+ async function cmdFocus(args: string[]) {
1921
+ const subCmd = args[0];
1922
+
1923
+ function resolveSessionId(rest: string[]): string {
1924
+ const sidIdx = rest.indexOf("--session-id");
1925
+ if (sidIdx >= 0 && rest[sidIdx + 1]) return rest[sidIdx + 1]!;
1926
+ const envSid = (
1927
+ process.env.CLAUDE_SESSION_ID ||
1928
+ process.env.CLAWMEM_SESSION_ID ||
1929
+ ""
1930
+ ).trim();
1931
+ if (envSid) return envSid;
1932
+ die(
1933
+ "No session id. Pass --session-id <id>, or set CLAUDE_SESSION_ID " +
1934
+ "(Claude Code exposes this) or CLAWMEM_SESSION_ID env var before " +
1935
+ "invoking this command."
1936
+ );
1937
+ }
1938
+
1939
+ function stripSessionIdArg(rest: string[]): string[] {
1940
+ const sidIdx = rest.indexOf("--session-id");
1941
+ if (sidIdx < 0) return rest;
1942
+ return [...rest.slice(0, sidIdx), ...rest.slice(sidIdx + 2)];
1943
+ }
1944
+
1945
+ switch (subCmd) {
1946
+ case "set": {
1947
+ const rest = args.slice(1);
1948
+ const sessionId = resolveSessionId(rest);
1949
+ const positional = stripSessionIdArg(rest);
1950
+ const topic = positional.join(" ").trim();
1951
+ if (!topic) {
1952
+ die("Usage: clawmem focus set <topic> [--session-id <id>]");
1953
+ }
1954
+ try {
1955
+ writeSessionFocus(sessionId, topic);
1956
+ } catch (err: any) {
1957
+ die(`Failed to set focus: ${err?.message ?? err}`);
1958
+ }
1959
+ console.log(
1960
+ `${c.green}Focus set${c.reset} for session ${c.cyan}${sessionId}${c.reset}: ${topic}`
1961
+ );
1962
+ console.log(`${c.dim}File: ${focusFilePath(sessionId)}${c.reset}`);
1963
+ break;
1964
+ }
1965
+ case "show": {
1966
+ const rest = args.slice(1);
1967
+ const sessionId = resolveSessionId(rest);
1968
+ const topic = readSessionFocus(sessionId);
1969
+ if (topic) {
1970
+ console.log(
1971
+ `${c.green}Focus${c.reset} for session ${c.cyan}${sessionId}${c.reset}: ${topic}`
1972
+ );
1973
+ console.log(`${c.dim}File: ${focusFilePath(sessionId)}${c.reset}`);
1974
+ } else {
1975
+ console.log(
1976
+ `${c.yellow}No focus${c.reset} set for session ${c.cyan}${sessionId}${c.reset}.`
1977
+ );
1978
+ console.log(
1979
+ `${c.dim}Expected file: ${focusFilePath(sessionId)}${c.reset}`
1980
+ );
1981
+ }
1982
+ break;
1983
+ }
1984
+ case "clear": {
1985
+ const rest = args.slice(1);
1986
+ const sessionId = resolveSessionId(rest);
1987
+ clearSessionFocus(sessionId);
1988
+ console.log(
1989
+ `${c.green}Focus cleared${c.reset} for session ${c.cyan}${sessionId}${c.reset}.`
1990
+ );
1991
+ break;
1992
+ }
1993
+ default:
1994
+ die(
1995
+ "Usage: clawmem focus <set|show|clear> [<topic>] [--session-id <id>]"
1996
+ );
1997
+ }
1998
+ }
1999
+
1909
2000
  // =============================================================================
1910
2001
  // Main dispatch
1911
2002
  // =============================================================================
@@ -1994,6 +2085,9 @@ async function main() {
1994
2085
  case "profile":
1995
2086
  await cmdProfile(subArgs);
1996
2087
  break;
2088
+ case "focus":
2089
+ await cmdFocus(subArgs);
2090
+ break;
1997
2091
  case "update-context":
1998
2092
  await cmdUpdateContext();
1999
2093
  break;
@@ -2644,6 +2738,9 @@ ${c.bold}Memory:${c.reset}
2644
2738
  clawmem log [--last N] Session history
2645
2739
  clawmem profile Show user profile
2646
2740
  clawmem profile rebuild Force profile rebuild
2741
+ clawmem focus set <topic> [--session-id ID] Set per-session focus topic (steers context-surfacing)
2742
+ clawmem focus show [--session-id ID] Show current focus topic
2743
+ clawmem focus clear [--session-id ID] Clear focus topic
2647
2744
 
2648
2745
  ${c.bold}Hooks:${c.reset}
2649
2746
  clawmem hook <name> Run hook (stdin JSON)
package/src/config.ts CHANGED
@@ -84,12 +84,23 @@ export interface ProfileConfig {
84
84
  deepEscalation: boolean;
85
85
  /** Max time (ms) allowed for the fast path before escalation is considered */
86
86
  escalationBudgetMs: number;
87
+ /**
88
+ * §11.1 (v0.9.0): sub-budget for the `<vault-facts>` KG injection block.
89
+ * Dedicated token allowance so `<vault-facts>` cannot steal budget from
90
+ * the existing `<facts>` / `<relationships>` blocks. `speed` profile is
91
+ * gated off (factsTokens=0 → stage skipped entirely). `balanced` / `deep`
92
+ * get 200 / 250 respectively. If the serialized facts would exceed this
93
+ * sub-budget, truncation happens at the triple boundary. If the total
94
+ * hook output would push past `tokenBudget + factsTokens`, the whole
95
+ * `<vault-facts>` block is dropped (established blocks take priority).
96
+ */
97
+ factsTokens: number;
87
98
  }
88
99
 
89
100
  export const PROFILES: Record<PerformanceProfile, ProfileConfig> = {
90
- speed: { tokenBudget: 400, maxResults: 5, useVector: false, vectorTimeout: 0, minScore: 0.55, minScoreRatio: 0.65, absoluteFloor: 0.18, activationFloor: 0.24, thresholdMode: "adaptive", deepEscalation: false, escalationBudgetMs: 0 },
91
- balanced: { tokenBudget: 800, maxResults: 10, useVector: true, vectorTimeout: 900, minScore: 0.45, minScoreRatio: 0.55, absoluteFloor: 0.15, activationFloor: 0.20, thresholdMode: "adaptive", deepEscalation: false, escalationBudgetMs: 0 },
92
- deep: { tokenBudget: 1200, maxResults: 15, useVector: true, vectorTimeout: 2000, minScore: 0.25, minScoreRatio: 0.45, absoluteFloor: 0.12, activationFloor: 0.16, thresholdMode: "adaptive", deepEscalation: true, escalationBudgetMs: 4000 },
101
+ speed: { tokenBudget: 400, maxResults: 5, useVector: false, vectorTimeout: 0, minScore: 0.55, minScoreRatio: 0.65, absoluteFloor: 0.18, activationFloor: 0.24, thresholdMode: "adaptive", deepEscalation: false, escalationBudgetMs: 0, factsTokens: 0 },
102
+ balanced: { tokenBudget: 800, maxResults: 10, useVector: true, vectorTimeout: 900, minScore: 0.45, minScoreRatio: 0.55, absoluteFloor: 0.15, activationFloor: 0.20, thresholdMode: "adaptive", deepEscalation: false, escalationBudgetMs: 0, factsTokens: 200 },
103
+ deep: { tokenBudget: 1200, maxResults: 15, useVector: true, vectorTimeout: 2000, minScore: 0.25, minScoreRatio: 0.45, absoluteFloor: 0.12, activationFloor: 0.16, thresholdMode: "adaptive", deepEscalation: true, escalationBudgetMs: 4000, factsTokens: 250 },
93
104
  };
94
105
 
95
106
  export function getActiveProfile(): ProfileConfig {
package/src/entity.ts CHANGED
@@ -354,6 +354,69 @@ export function resolveEntityCanonical(
354
354
  // Entity Storage + Mentions + Co-occurrences
355
355
  // =============================================================================
356
356
 
357
+ /**
358
+ * Resolve the entity_type for a name via exact case-insensitive match.
359
+ *
360
+ * Returns the type only when EXACTLY ONE active entity in the given vault shares
361
+ * the name. Zero matches → null (caller should default to a safe type). Multiple
362
+ * matches (ambiguous across buckets, e.g. "Alice" as person AND "Alice" as project)
363
+ * → null so the caller falls back to a safe default instead of arbitrarily picking.
364
+ *
365
+ * Exact match only — no fuzzy matching — to avoid false inheritance on near-names.
366
+ */
367
+ export function resolveEntityTypeExact(
368
+ db: Database,
369
+ name: string,
370
+ vault: string = 'default'
371
+ ): string | null {
372
+ const rows = db.prepare(`
373
+ SELECT DISTINCT entity_type FROM entity_nodes
374
+ WHERE LOWER(name) = LOWER(?) AND vault = ?
375
+ `).all(name, vault) as Array<{ entity_type: string }>;
376
+
377
+ if (rows.length !== 1) return null; // zero or ambiguous
378
+ return rows[0]!.entity_type;
379
+ }
380
+
381
+ /**
382
+ * Resolve-or-create a canonical entity without incrementing mention_count.
383
+ *
384
+ * Used by consumers that reference an entity but do NOT constitute a document
385
+ * mention (e.g. SPO triple extraction). Semantically distinct from upsertEntity,
386
+ * which treats every call as a doc mention and inflates the count.
387
+ *
388
+ * Flow: resolveEntityCanonical (FTS5 + fuzzy + bucket match) → reuse if found,
389
+ * otherwise mint a new canonical `vault:type:slug` entity with mention_count = 0.
390
+ *
391
+ * Returns the entity_id.
392
+ */
393
+ export function ensureEntityCanonical(
394
+ db: Database,
395
+ name: string,
396
+ type: string,
397
+ vault: string = 'default'
398
+ ): string {
399
+ const canonicalId = resolveEntityCanonical(db, name, type, vault);
400
+ if (canonicalId) return canonicalId;
401
+
402
+ const entityId = makeEntityId(name, type, vault);
403
+ db.prepare(`
404
+ INSERT OR IGNORE INTO entity_nodes (entity_id, entity_type, name, description, created_at, mention_count, last_seen, vault)
405
+ VALUES (?, ?, ?, NULL, datetime('now'), 0, datetime('now'), ?)
406
+ `).run(entityId, type, name, vault);
407
+
408
+ try {
409
+ db.prepare(`
410
+ INSERT OR IGNORE INTO entities_fts (entity_id, name, entity_type)
411
+ VALUES (?, ?, ?)
412
+ `).run(entityId, name.toLowerCase(), type);
413
+ } catch {
414
+ // FTS insert may fail if table doesn't exist yet — non-fatal
415
+ }
416
+
417
+ return entityId;
418
+ }
419
+
357
420
  /**
358
421
  * Upsert an entity into entity_nodes and entities_fts.
359
422
  * Returns the entity_id (canonical or new).