clawmem 0.2.8 → 0.3.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
@@ -682,4 +682,5 @@ clawmem consolidate [--dry-run] # Find and archive duplicate low-confidence docu
682
682
  - Consolidation worker (`CLAWMEM_ENABLE_CONSOLIDATION=true`) backfills unenriched docs with A-MEM notes + links. Only runs if the MCP process stays alive long enough to tick (every 5min).
683
683
  - Beads integration: `syncBeadsIssues()` queries `bd` CLI (Dolt backend, v0.58.0+) for live issue data, creates markdown docs in `beads` collection, maps all dependency edge types into `memory_relations`, and triggers A-MEM enrichment for new docs. Watcher auto-triggers on `.beads/` directory changes; `beads_sync` MCP tool for manual sync. Requires `bd` binary on PATH or at `~/go/bin/bd`.
684
684
  - HTTP REST API: `clawmem serve [--port 7438]` — optional REST server on localhost. Search, retrieval, lifecycle, and graph traversal. `POST /retrieve` mirrors `memory_retrieve` with auto-routing (keyword/semantic/causal/timeline/hybrid). `POST /search` provides direct mode selection. Bearer token auth via `CLAWMEM_API_TOKEN` env var (disabled if unset).
685
- - OpenClaw ContextEngine plugin: `clawmem setup openclaw` — registers ClawMem as a native OpenClaw context engine. Uses `before_prompt_build` for retrieval (prompt-aware), `afterTurn()` for extraction, `compact()` for pre-compaction. Shares same vault as Claude Code hooks (dual-mode). SQLite busy_timeout=5000ms for concurrent access safety.
685
+ - OpenClaw ContextEngine plugin: `clawmem setup openclaw` — registers ClawMem as a native OpenClaw context engine. Uses `before_prompt_build` for retrieval (prompt-aware), `afterTurn()` for extraction, `compact()` for pre-compaction + runtime delegation. Shares same vault as Claude Code hooks (dual-mode). SQLite busy_timeout=5000ms for concurrent access safety.
686
+ - **OpenClaw v2026.3.28+ compaction fix (v0.3.0):** `compact()` now delegates to OpenClaw's runtime compactor via `delegateCompactionToRuntime()` from `openclaw/plugin-sdk/core`. Previous versions returned `compacted: false` expecting legacy fallback — that fallback no longer exists. Without this fix, sessions never compact. Bootstrap context is now cached in `bootstrap()` and consumed once in `before_prompt_build`, eliminating duplicate hook invocations.
package/CLAUDE.md CHANGED
@@ -682,4 +682,5 @@ clawmem consolidate [--dry-run] # Find and archive duplicate low-confidence docu
682
682
  - Consolidation worker (`CLAWMEM_ENABLE_CONSOLIDATION=true`) backfills unenriched docs with A-MEM notes + links. Only runs if the MCP process stays alive long enough to tick (every 5min).
683
683
  - Beads integration: `syncBeadsIssues()` queries `bd` CLI (Dolt backend, v0.58.0+) for live issue data, creates markdown docs in `beads` collection, maps all dependency edge types into `memory_relations`, and triggers A-MEM enrichment for new docs. Watcher auto-triggers on `.beads/` directory changes; `beads_sync` MCP tool for manual sync. Requires `bd` binary on PATH or at `~/go/bin/bd`.
684
684
  - HTTP REST API: `clawmem serve [--port 7438]` — optional REST server on localhost. Search, retrieval, lifecycle, and graph traversal. `POST /retrieve` mirrors `memory_retrieve` with auto-routing (keyword/semantic/causal/timeline/hybrid). `POST /search` provides direct mode selection. Bearer token auth via `CLAWMEM_API_TOKEN` env var (disabled if unset).
685
- - OpenClaw ContextEngine plugin: `clawmem setup openclaw` — registers ClawMem as a native OpenClaw context engine. Uses `before_prompt_build` for retrieval (prompt-aware), `afterTurn()` for extraction, `compact()` for pre-compaction. Shares same vault as Claude Code hooks (dual-mode). SQLite busy_timeout=5000ms for concurrent access safety.
685
+ - OpenClaw ContextEngine plugin: `clawmem setup openclaw` — registers ClawMem as a native OpenClaw context engine. Uses `before_prompt_build` for retrieval (prompt-aware), `afterTurn()` for extraction, `compact()` for pre-compaction + runtime delegation. Shares same vault as Claude Code hooks (dual-mode). SQLite busy_timeout=5000ms for concurrent access safety.
686
+ - **OpenClaw v2026.3.28+ compaction fix (v0.3.0):** `compact()` now delegates to OpenClaw's runtime compactor via `delegateCompactionToRuntime()` from `openclaw/plugin-sdk/core`. Previous versions returned `compacted: false` expecting legacy fallback — that fallback no longer exists. Without this fix, sessions never compact. Bootstrap context is now cached in `bootstrap()` and consumed once in `before_prompt_build`, eliminating duplicate hook invocations.
package/SKILL.md CHANGED
@@ -592,6 +592,10 @@ openclaw config set agents.defaults.memorySearch.extraPaths '["~/documents", "~/
592
592
 
593
593
  **Tradeoffs:** Redundant recall but 10-15% context window waste from duplicate facts.
594
594
 
595
+ ### Compaction (v0.3.0+, required for OpenClaw v2026.3.28+)
596
+
597
+ `compact()` delegates to OpenClaw's runtime compactor via `delegateCompactionToRuntime()`. Previous versions returned `compacted: false` expecting legacy fallback — that fallback no longer exists. Without this fix, OpenClaw sessions never compact and context grows unbounded.
598
+
595
599
  ---
596
600
 
597
601
  ## Troubleshooting
@@ -701,7 +705,7 @@ clawmem consolidate [--dry-run] # Find and archive duplicate low-confidence docu
701
705
  - Consolidation worker (`CLAWMEM_ENABLE_CONSOLIDATION=true`) backfills unenriched docs. Only runs if MCP process stays alive long enough (every 5min).
702
706
  - Beads integration: `syncBeadsIssues()` queries `bd` CLI (Dolt backend, v0.58.0+), creates markdown docs, maps dependency edges into `memory_relations`. Watcher auto-triggers on `.beads/` changes; `beads_sync` MCP for manual sync.
703
707
  - HTTP REST API: `clawmem serve [--port 7438]` — optional REST server on localhost. Search, retrieval, lifecycle, and graph traversal. `POST /retrieve` mirrors `memory_retrieve` with auto-routing (keyword/semantic/causal/timeline/hybrid). `POST /search` provides direct mode selection. Bearer token auth via `CLAWMEM_API_TOKEN` env var (disabled if unset).
704
- - OpenClaw ContextEngine plugin: `clawmem setup openclaw` — registers as native OpenClaw context engine. Dual-mode: shares vault with Claude Code hooks. Uses `before_prompt_build` for retrieval, `afterTurn()` for extraction, `compact()` for pre-compaction.
708
+ - OpenClaw ContextEngine plugin: `clawmem setup openclaw` — registers as native OpenClaw context engine. Dual-mode: shares vault with Claude Code hooks. Uses `before_prompt_build` for retrieval, `afterTurn()` for extraction, `compact()` for pre-compaction + runtime delegation (v0.3.0+, required for OpenClaw v2026.3.28+).
705
709
 
706
710
  ## Tool Selection (one-liner)
707
711
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmem",
3
- "version": "0.2.8",
3
+ "version": "0.3.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": {
@@ -7,13 +7,13 @@
7
7
  * Architecture (per GPT 5.4 High review):
8
8
  * - assemble(): minimal pass-through (real retrieval happens in before_prompt_build hook)
9
9
  * - afterTurn(): shells out to decision-extractor, handoff-generator, feedback-loop
10
- * - compact(): shells out to precompact-extract, then delegates to legacy compaction (ownsCompaction: false)
10
+ * - compact(): shells out to precompact-extract, then delegates to OpenClaw runtime compactor
11
11
  * - ingest(): no-op (ClawMem captures at turn boundaries, not per-message)
12
12
  * - bootstrap(): session registration only (context injection via before_prompt_build hook)
13
13
  */
14
14
 
15
15
  import type { ClawMemConfig } from "./shell.js";
16
- import { execHook, parseHookOutput } from "./shell.js";
16
+ import { execHook, parseHookOutput, extractContext } from "./shell.js";
17
17
 
18
18
  // =============================================================================
19
19
  // Types (matching OpenClaw's ContextEngine interface without importing it)
@@ -76,10 +76,10 @@ export class ClawMemContextEngine {
76
76
  id: "clawmem",
77
77
  name: "ClawMem Memory System",
78
78
  version: "0.2.0",
79
- ownsCompaction: false, // Delegate compaction to legacy engine
79
+ ownsCompaction: false, // Delegate compaction to OpenClaw runtime
80
80
  };
81
81
 
82
- private bootstrappedSessions = new Set<string>();
82
+ private bootstrapContexts = new Map<string, string>();
83
83
 
84
84
  constructor(
85
85
  private readonly cfg: ClawMemConfig,
@@ -96,23 +96,37 @@ export class ClawMemContextEngine {
96
96
  sessionKey?: string;
97
97
  sessionFile: string;
98
98
  }): Promise<BootstrapResult> {
99
- this.bootstrappedSessions.add(params.sessionId);
100
-
101
- // Fire session-bootstrap hook for session registration + side effects
102
- // (profile refresh, session_log entry). Context is NOT injected here —
103
- // that happens in the before_prompt_build plugin hook.
99
+ // Fire session-bootstrap hook and cache returned context for first-turn
100
+ // injection via before_prompt_build. Running it here (once) avoids the
101
+ // duplicate invocation that previously existed in the prompt hook.
104
102
  const result = await execHook(this.cfg, "session-bootstrap", {
105
103
  session_id: params.sessionId,
106
104
  transcript_path: params.sessionFile,
107
105
  });
108
106
 
109
- if (result.exitCode !== 0) {
107
+ if (result.exitCode === 0) {
108
+ const parsed = parseHookOutput(result.stdout);
109
+ const ctx = extractContext(parsed);
110
+ if (ctx) {
111
+ this.bootstrapContexts.set(params.sessionId, ctx);
112
+ }
113
+ } else {
110
114
  this.logger.warn(`clawmem: bootstrap hook failed: ${result.stderr}`);
111
115
  }
112
116
 
113
117
  return { bootstrapped: true };
114
118
  }
115
119
 
120
+ /**
121
+ * Consume cached bootstrap context for a session (one-shot).
122
+ * Returns the context string and removes it from cache.
123
+ */
124
+ takeBootstrapContext(sessionId: string): string | undefined {
125
+ const ctx = this.bootstrapContexts.get(sessionId);
126
+ if (ctx) this.bootstrapContexts.delete(sessionId);
127
+ return ctx;
128
+ }
129
+
116
130
  // ---------------------------------------------------------------------------
117
131
  // ingest — no-op (ClawMem captures at turn boundaries via afterTurn)
118
132
  // ---------------------------------------------------------------------------
@@ -207,10 +221,11 @@ export class ClawMemContextEngine {
207
221
  }
208
222
 
209
223
  // ---------------------------------------------------------------------------
210
- // compact — run precompact-extract for state preservation, then return
211
- // ownsCompaction: false so OpenClaw's legacy compaction still runs.
212
- // P1 finding: compact() must still perform real compaction; precompact-extract
213
- // is a side effect, not a compactor. We delegate real compaction to legacy.
224
+ // compact — run precompact-extract for state preservation, then delegate
225
+ // to OpenClaw's built-in runtime compactor. ownsCompaction=false means we
226
+ // don't own the algorithm but v2026.3.30 requires engines to explicitly
227
+ // delegate via delegateCompactionToRuntime() instead of returning
228
+ // compacted:false and hoping for legacy fallback (which no longer exists).
214
229
  // ---------------------------------------------------------------------------
215
230
 
216
231
  async compact(params: {
@@ -222,6 +237,7 @@ export class ClawMemContextEngine {
222
237
  currentTokenCount?: number;
223
238
  compactionTarget?: "budget" | "threshold";
224
239
  customInstructions?: string;
240
+ runtimeContext?: Record<string, unknown>;
225
241
  }): Promise<CompactResult> {
226
242
  // Run precompact-extract to preserve state before compaction
227
243
  const extractResult = await execHook(this.cfg, "precompact-extract", {
@@ -233,13 +249,20 @@ export class ClawMemContextEngine {
233
249
  this.logger.warn(`clawmem: precompact-extract failed: ${extractResult.stderr}`);
234
250
  }
235
251
 
236
- // Return not-compacted let legacy engine handle actual compaction
237
- // (ownsCompaction: false means OpenClaw will call legacy compact() after us)
238
- return {
239
- ok: true,
240
- compacted: false,
241
- reason: "clawmem-precompact-only",
242
- };
252
+ // Delegate actual compaction to OpenClaw's built-in runtime compactor.
253
+ // Lazy import avoids requiring openclaw as a build dependency — the SDK
254
+ // path is resolved at runtime by OpenClaw's plugin loader alias system.
255
+ try {
256
+ const { delegateCompactionToRuntime } = await import("openclaw/plugin-sdk/core");
257
+ return await delegateCompactionToRuntime(params as any);
258
+ } catch (err) {
259
+ this.logger.warn(`clawmem: delegateCompactionToRuntime failed: ${String(err)}`);
260
+ return {
261
+ ok: false,
262
+ compacted: false,
263
+ reason: `delegation-failed: ${String(err)}`,
264
+ };
265
+ }
243
266
  }
244
267
 
245
268
  // ---------------------------------------------------------------------------
@@ -269,15 +292,14 @@ export class ClawMemContextEngine {
269
292
  // dispose — cleanup
270
293
  // ---------------------------------------------------------------------------
271
294
 
272
- async dispose(): Promise<void> {
273
- this.bootstrappedSessions.clear();
295
+ /**
296
+ * Clean up per-session state. Called from session_end and before_reset hooks.
297
+ */
298
+ clearSession(sessionId: string): void {
299
+ this.bootstrapContexts.delete(sessionId);
274
300
  }
275
301
 
276
- // ---------------------------------------------------------------------------
277
- // Helper: check if session has been bootstrapped (used by prompt hook)
278
- // ---------------------------------------------------------------------------
279
-
280
- isBootstrapped(sessionId: string): boolean {
281
- return this.bootstrappedSessions.has(sessionId);
302
+ async dispose(): Promise<void> {
303
+ this.bootstrapContexts.clear();
282
304
  }
283
305
  }
@@ -10,7 +10,7 @@
10
10
  * Architecture per GPT 5.4 High review:
11
11
  * - Prompt-aware retrieval goes through before_prompt_build (has the user prompt)
12
12
  * - Post-turn extraction goes through ContextEngine.afterTurn() (has messages[])
13
- * - Compaction goes through ContextEngine.compact() → precompact-extract → delegate to legacy
13
+ * - Compaction goes through ContextEngine.compact() → precompact-extract → delegate to runtime
14
14
  * - assemble() is minimal pass-through (retrieval already injected via hook)
15
15
  */
16
16
 
@@ -109,24 +109,14 @@ const clawmemPlugin = {
109
109
 
110
110
  let context = "";
111
111
 
112
- // On first turn: run session-bootstrap for profile + handoff + decisions + stale
112
+ // On first turn: consume cached bootstrap context from engine.bootstrap()
113
+ // (avoids duplicate session-bootstrap hook invocation)
113
114
  if (isFirstTurn) {
114
115
  surfacedSessions.add(sessionId);
115
116
 
116
- const bootstrapResult = await execHook(cfg, "session-bootstrap", {
117
- session_id: sessionId,
118
- });
119
-
120
- if (bootstrapResult.exitCode === 0) {
121
- const parsed = parseHookOutput(bootstrapResult.stdout);
122
- // Session-bootstrap outputs context directly to stdout (not via hookSpecificOutput)
123
- // It uses the system_message field for SessionStart hooks
124
- const bootstrapContext = (parsed?.systemMessage as string) || "";
125
- if (bootstrapContext) {
126
- context += bootstrapContext + "\n\n";
127
- }
128
- } else {
129
- logger.warn(`clawmem: session-bootstrap failed: ${bootstrapResult.stderr}`);
117
+ const bootstrapContext = engine.takeBootstrapContext(sessionId);
118
+ if (bootstrapContext) {
119
+ context += bootstrapContext + "\n\n";
130
120
  }
131
121
  }
132
122
 
@@ -168,6 +158,7 @@ const clawmemPlugin = {
168
158
  ) => {
169
159
  // Cleanup tracked state
170
160
  surfacedSessions.delete(event.sessionId);
161
+ engine.clearSession(event.sessionId);
171
162
  logger.info?.(`clawmem: session ended ${event.sessionId} (${event.messageCount} messages)`);
172
163
  });
173
164
 
@@ -192,22 +183,12 @@ const clawmemPlugin = {
192
183
  ]);
193
184
 
194
185
  surfacedSessions.delete(ctx.sessionId);
186
+ engine.clearSession(ctx.sessionId);
195
187
  });
196
188
 
197
- // ----- Plugin Hook: before_compaction -----
198
- // Fire precompact-extract before compaction starts (additional safety compact()
199
- // also runs it, but before_compaction fires earlier in the pipeline)
200
- api.on("before_compaction", async (
201
- event: { sessionFile?: string; messageCount: number },
202
- ctx: { sessionId?: string }
203
- ) => {
204
- if (!event.sessionFile || !ctx.sessionId) return;
205
-
206
- await execHook(cfg, "precompact-extract", {
207
- session_id: ctx.sessionId,
208
- transcript_path: event.sessionFile,
209
- });
210
- });
189
+ // NOTE: before_compaction hook removed — precompact-extract now fires only
190
+ // in engine.compact() before delegating to the runtime compactor. This avoids
191
+ // the duplicate invocation that previously existed.
211
192
 
212
193
  // ----- Register Tools -----
213
194
  if (cfg.enableTools) {