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 +31 -20
- package/CLAUDE.md +21 -9
- package/README.md +20 -22
- package/SKILL.md +22 -9
- package/package.json +1 -1
- package/src/amem.ts +8 -1
- package/src/clawmem.ts +97 -0
- package/src/config.ts +14 -3
- package/src/entity.ts +63 -0
- package/src/hooks/context-surfacing.ts +87 -6
- package/src/hooks/decision-extractor.ts +145 -115
- package/src/mcp.ts +19 -6
- package/src/observer.ts +132 -15
- package/src/session-focus.ts +227 -0
- package/src/store.ts +5 -0
- package/src/vault-facts.ts +506 -0
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
213
|
+
### Option 1: ClawMem Exclusive (Recommended)
|
|
212
214
|
|
|
213
|
-
|
|
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
|
-
-
|
|
247
|
-
-
|
|
248
|
-
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
139
|
-
|
|
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,
|
|
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,
|
|
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.
|
|
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
|
|
1077
|
-
|
|
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
|
-
#
|
|
1083
|
-
|
|
1081
|
+
# Bootstrap each project you want indexed
|
|
1082
|
+
clawmem bootstrap ~/Projects/my-project --name my-project
|
|
1084
1083
|
|
|
1085
|
-
# Install watcher as systemd
|
|
1086
|
-
|
|
1084
|
+
# Install watcher + embed timer as systemd services
|
|
1085
|
+
clawmem install-service --enable
|
|
1087
1086
|
```
|
|
1088
1087
|
|
|
1089
|
-
#### OpenClaw-
|
|
1088
|
+
#### OpenClaw-specific
|
|
1090
1089
|
|
|
1091
1090
|
```bash
|
|
1092
|
-
#
|
|
1093
|
-
|
|
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
|
-
|
|
1096
|
+
Index your content directories with `clawmem bootstrap` as above. The OpenClaw plugin shares the same vault as Claude Code hooks.
|
|
1097
1097
|
|
|
1098
|
-
|
|
1099
|
-
# Hermes uses ~/.hermes/ as its home directory
|
|
1100
|
-
./bin/clawmem bootstrap ~/.hermes --name hermes-home
|
|
1098
|
+
#### Hermes-specific
|
|
1101
1099
|
|
|
1102
|
-
|
|
1103
|
-
|
|
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
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
129
|
-
|
|
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
|
|
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.
|
|
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
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).
|