clawmem 0.2.9 → 0.3.2
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 +2 -1
- package/CLAUDE.md +2 -1
- package/SKILL.md +5 -1
- package/package.json +1 -1
- package/src/clawmem.ts +8 -3
- package/src/openclaw/engine.ts +51 -29
- package/src/openclaw/index.ts +11 -30
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
package/src/clawmem.ts
CHANGED
|
@@ -970,11 +970,15 @@ async function cmdSetupHooks(args: string[]) {
|
|
|
970
970
|
Stop: ["decision-extractor", "handoff-generator", "feedback-loop"],
|
|
971
971
|
};
|
|
972
972
|
|
|
973
|
+
// Use Claude Code's native timeout property instead of shell `timeout` wrapper.
|
|
974
|
+
// Shell `timeout` kills the process with SIGTERM (exit 124) which produces
|
|
975
|
+
// "Stop hook error: Failed with non-blocking status code" in Claude Code.
|
|
976
|
+
// Native timeout is handled gracefully by the hook runner.
|
|
973
977
|
const timeouts: Record<string, number> = {
|
|
974
978
|
UserPromptSubmit: 8,
|
|
975
979
|
SessionStart: 5,
|
|
976
980
|
PreCompact: 5,
|
|
977
|
-
Stop:
|
|
981
|
+
Stop: 30, // LLM-based extraction hooks need more time
|
|
978
982
|
};
|
|
979
983
|
|
|
980
984
|
for (const [event, hooks] of Object.entries(hookConfig)) {
|
|
@@ -987,12 +991,13 @@ async function cmdSetupHooks(args: string[]) {
|
|
|
987
991
|
|
|
988
992
|
const timeout = timeouts[event] || 5;
|
|
989
993
|
|
|
990
|
-
// Add new entries with timeout
|
|
994
|
+
// Add new entries with native timeout property
|
|
991
995
|
settings.hooks[event].push({
|
|
992
996
|
matcher: "",
|
|
993
997
|
hooks: hooks.map(name => ({
|
|
994
998
|
type: "command",
|
|
995
|
-
command:
|
|
999
|
+
command: `${binPath} hook ${name}`,
|
|
1000
|
+
timeout,
|
|
996
1001
|
})),
|
|
997
1002
|
});
|
|
998
1003
|
}
|
package/src/openclaw/engine.ts
CHANGED
|
@@ -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
|
|
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
|
|
79
|
+
ownsCompaction: false, // Delegate compaction to OpenClaw runtime
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
-
private
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
//
|
|
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
|
|
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
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
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
|
-
//
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
273
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/openclaw/index.ts
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
|
117
|
-
|
|
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
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
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) {
|