@nowledge/openclaw-nowledge-mem 0.6.8 → 0.6.12

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.
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: nowledge-mem-guide
3
+ description: >
4
+ Cross-AI personal knowledge graph memory (Nowledge Mem). Stores memories from this tool,
5
+ Claude Code, Cursor, browser capture, imported documents, and more. Use when: (1) the user
6
+ asks about prior work, decisions, preferences, people, dates, plans, or todos, (2) the user
7
+ references something discussed before or says "remind me" / "what did we decide" / "what was
8
+ I working on", (3) the conversation produces a decision, preference, plan, or learning worth
9
+ keeping, (4) the user wants to browse recent activity or explore how ideas connect, (5) the
10
+ user asks about a past conversation or wants to find a specific thread.
11
+ ---
12
+
13
+ # Nowledge Mem
14
+
15
+ ## Search (memory_search)
16
+
17
+ Use natural language queries. This is semantic search, not file search.
18
+
19
+ - "database choice for task events" (not "MEMORY.md" or file paths)
20
+ - "meeting with Sarah about API redesign"
21
+ - "deployment procedure for production"
22
+
23
+ Results include `matchedVia` (scoring breakdown), `importance`, `labels`, `sourceThreadId`.
24
+
25
+ When results include `relatedThreads`, use `nowledge_mem_thread_fetch` with the `threadId` for full conversation context. Start with a small page; fetch more via `offset` + `limit` only when needed.
26
+
27
+ Bi-temporal filters narrow results by time:
28
+
29
+ - `event_date_from` / `event_date_to` — when the fact happened
30
+ - `recorded_date_from` / `recorded_date_to` — when it was saved
31
+
32
+ ## Save (nowledge_mem_save)
33
+
34
+ Save proactively when the conversation produces a decision, preference, plan, procedure, learning, or important context. Do not wait to be asked.
35
+
36
+ Structure saves:
37
+
38
+ - `unit_type`: fact | preference | decision | plan | procedure | learning | context | event
39
+ - `labels`: lowercase-hyphenated topic tags
40
+ - `importance`: 0.0-1.0 (most: 0.5; key decisions/preferences: 0.7-0.9)
41
+ - `event_start` / `event_end`: ISO dates when temporal
42
+
43
+ Skip trivial exchanges, greetings, and meta-conversation about memory itself.
44
+
45
+ ## Connections (nowledge_mem_connections)
46
+
47
+ Pass a `memoryId` from search results to explore related memories, evolution chains, and source documents. Edge types: EVOLVES, CRYSTALLIZED_FROM, SOURCED_FROM, MENTIONS.
48
+
49
+ ## Timeline (nowledge_mem_timeline)
50
+
51
+ Browse recent activity grouped by day. Filter by `event_type` or exact `date_from` / `date_to` range.
52
+
53
+ ## Threads (nowledge_mem_thread_search + nowledge_mem_thread_fetch)
54
+
55
+ Find past conversations by keyword, then progressively fetch messages. Threads span all sources: this tool, other AI tools, browser capture, imports.
56
+
57
+ ## Working Memory (nowledge_mem_context)
58
+
59
+ Read the user's daily briefing (priorities, active projects, flags). Patch a specific section with `patch_section` + `patch_content`/`patch_append` when the conversation updates a priority or resolves a flag.
60
+
61
+ ## Important
62
+
63
+ - Search uses natural language queries, not file paths. Never search for "MEMORY.md" or "memory/*.md".
64
+ - When `relatedThreads` appear in results, they often contain the most useful context.
65
+ - Use tools directly; do not tell the user to "check your memory."
package/src/client.js CHANGED
@@ -1,4 +1,4 @@
1
- import { spawnSync } from "node:child_process";
1
+ import { buildNmemSpawnEnv } from "./spawn-env.js";
2
2
 
3
3
  /**
4
4
  * Patch a single markdown section in a Working Memory document.
@@ -66,18 +66,21 @@ function patchWmSection(currentContent, heading, { content, append } = {}) {
66
66
  *
67
67
  * Credential rules:
68
68
  * - apiUrl: passed to CLI via --api-url flag (not a secret)
69
- * - apiKey: injected into the child process env as NMEM_API_KEY ONLY
69
+ * - apiKey: injected into the command runner env as NMEM_API_KEY ONLY
70
70
  * (never passed as a CLI arg to avoid exposure in `ps aux`)
71
71
  * (never logged, even at debug level)
72
72
  */
73
73
  export class NowledgeMemClient {
74
74
  /**
75
75
  * @param {object} logger
76
+ * @param {object} runtimeSystem
76
77
  * @param {{ apiUrl?: string; apiKey?: string }} [credentials]
77
78
  */
78
- constructor(logger, credentials = {}) {
79
+ constructor(logger, runtimeSystem, credentials = {}) {
79
80
  this.logger = logger;
81
+ this.runtimeSystem = runtimeSystem;
80
82
  this.nmemCmd = null;
83
+ this.nmemCmdPromise = null;
81
84
  // Resolved once from config + env (config wins over env, both win over default)
82
85
  this._apiUrl =
83
86
  (credentials.apiUrl || "").trim() || "http://127.0.0.1:14242";
@@ -138,28 +141,42 @@ export class NowledgeMemClient {
138
141
 
139
142
  // ── CLI helpers ────────────────────────────────────────────────────────────
140
143
 
141
- resolveCommand() {
144
+ async resolveCommand() {
142
145
  if (this.nmemCmd) return this.nmemCmd;
146
+ if (this.nmemCmdPromise) return this.nmemCmdPromise;
143
147
 
144
148
  const candidates = [["nmem"], ["uvx", "--from", "nmem-cli", "nmem"]];
145
149
 
146
- for (const cmd of candidates) {
147
- const [bin, ...baseArgs] = cmd;
148
- const result = spawnSync(bin, [...baseArgs, "--version"], {
149
- stdio: ["ignore", "pipe", "pipe"],
150
- timeout: 10_000,
151
- encoding: "utf-8",
152
- });
153
- if (result.status === 0) {
154
- this.nmemCmd = cmd;
155
- this.logger.info(`nmem resolved: ${cmd.join(" ")}`);
156
- return cmd;
150
+ this.nmemCmdPromise = (async () => {
151
+ for (const cmd of candidates) {
152
+ try {
153
+ const result = await this.runtimeSystem.runCommandWithTimeout(
154
+ [...cmd, "--version"],
155
+ {
156
+ timeoutMs: 10_000,
157
+ env: this._spawnEnv(),
158
+ },
159
+ );
160
+ if ((result.code ?? 1) === 0) {
161
+ this.nmemCmd = cmd;
162
+ this.logger.info(`nmem resolved: ${cmd.join(" ")}`);
163
+ return cmd;
164
+ }
165
+ } catch {
166
+ // Try the next candidate.
167
+ }
157
168
  }
158
- }
159
169
 
160
- throw new Error(
161
- "nmem CLI not found. Install with: pip install nmem-cli (or use uvx)",
162
- );
170
+ throw new Error(
171
+ "nmem CLI not found. Install with: pip install nmem-cli (or use uvx)",
172
+ );
173
+ })();
174
+
175
+ try {
176
+ return await this.nmemCmdPromise;
177
+ } finally {
178
+ this.nmemCmdPromise = null;
179
+ }
163
180
  }
164
181
 
165
182
  /**
@@ -167,15 +184,10 @@ export class NowledgeMemClient {
167
184
  * apiKey is injected here — NEVER via CLI args.
168
185
  */
169
186
  _spawnEnv() {
170
- const env = { ...process.env };
171
- // Explicit config wins over any existing env var
172
- if (this._apiUrl !== "http://127.0.0.1:14242") {
173
- env.NMEM_API_URL = this._apiUrl;
174
- }
175
- if (this._apiKey) {
176
- env.NMEM_API_KEY = this._apiKey;
177
- }
178
- return env;
187
+ return buildNmemSpawnEnv({
188
+ apiUrl: this._apiUrl,
189
+ apiKey: this._apiKey,
190
+ });
179
191
  }
180
192
 
181
193
  /**
@@ -188,41 +200,68 @@ export class NowledgeMemClient {
188
200
  : [];
189
201
  }
190
202
 
191
- exec(args, timeout = 30_000) {
192
- const cmd = this.resolveCommand();
193
- const [bin, ...baseArgs] = cmd;
203
+ _commandLabel(cmd, args) {
204
+ let i = cmd.length;
205
+ const argv = [...cmd, ...this._apiUrlArgs(), ...args];
206
+ while (i < argv.length) {
207
+ const token = argv[i];
208
+ if (token === "--api-url") {
209
+ i += 2;
210
+ continue;
211
+ }
212
+ break;
213
+ }
214
+ const route = argv
215
+ .slice(i, i + 2)
216
+ .filter(Boolean)
217
+ .join(" ");
218
+ return route ? `nmem ${route}` : cmd.join(" ");
219
+ }
220
+
221
+ _summarizeExecError(message) {
222
+ if (!message) return "failed";
223
+ if (message.includes("timed out")) return "timed out";
224
+ const codeMatch = message.match(/exited with code\s+(\d+)/i);
225
+ if (codeMatch) return `exited with code ${codeMatch[1]}`;
226
+ return "failed";
227
+ }
228
+
229
+ async exec(args, timeout = 30_000) {
230
+ const cmd = await this.resolveCommand();
231
+ const argv = [...cmd, ...this._apiUrlArgs(), ...args];
232
+ const commandLabel = this._commandLabel(cmd, args);
194
233
  try {
195
- const result = spawnSync(
196
- bin,
197
- [...baseArgs, ...this._apiUrlArgs(), ...args],
198
- {
199
- stdio: ["ignore", "pipe", "pipe"],
200
- timeout,
201
- encoding: "utf-8",
202
- env: this._spawnEnv(),
203
- },
204
- );
205
- if (result.error) {
206
- throw result.error;
234
+ const result = await this.runtimeSystem.runCommandWithTimeout(argv, {
235
+ timeoutMs: timeout,
236
+ env: this._spawnEnv(),
237
+ });
238
+ if (
239
+ result.termination === "timeout" ||
240
+ result.termination === "no-output-timeout"
241
+ ) {
242
+ const message =
243
+ (result.stderr || result.stdout || "").trim() ||
244
+ `nmem timed out after ${timeout}ms`;
245
+ throw new Error(message);
207
246
  }
208
- if (result.status !== 0) {
247
+ if ((result.code ?? 1) !== 0) {
209
248
  const message = (result.stderr || result.stdout || "").trim();
210
249
  throw new Error(
211
- message || `nmem exited with code ${String(result.status)}`,
250
+ message || `nmem exited with code ${String(result.code)}`,
212
251
  );
213
252
  }
214
253
  return String(result.stdout ?? "").trim();
215
254
  } catch (err) {
216
255
  const message = err instanceof Error ? err.message : String(err);
217
256
  this.logger.error(
218
- `nmem command failed: ${cmd.join(" ")} ${args.join(" ")} — ${message}`,
257
+ `nmem command failed (${commandLabel}): ${this._summarizeExecError(message)}`,
219
258
  );
220
259
  throw err;
221
260
  }
222
261
  }
223
262
 
224
- execJson(args, timeout = 30_000) {
225
- const raw = this.exec(args, timeout);
263
+ async execJson(args, timeout = 30_000) {
264
+ const raw = await this.exec(args, timeout);
226
265
  return JSON.parse(raw);
227
266
  }
228
267
 
@@ -237,7 +276,7 @@ export class NowledgeMemClient {
237
276
  100,
238
277
  Math.max(1, Math.trunc(Number(limit) || 5)),
239
278
  );
240
- const data = this.execJson([
279
+ const data = await this.execJson([
241
280
  "--json",
242
281
  "m",
243
282
  "search",
@@ -296,7 +335,7 @@ export class NowledgeMemClient {
296
335
 
297
336
  let data;
298
337
  try {
299
- data = this.execJson(args);
338
+ data = await this.execJson(args);
300
339
  } catch (err) {
301
340
  // Graceful fallback: CLI doesn't know --event-from yet → use API directly
302
341
  const message = err instanceof Error ? err.message : String(err);
@@ -388,7 +427,7 @@ export class NowledgeMemClient {
388
427
  ];
389
428
 
390
429
  try {
391
- return this.execJson(args);
430
+ return await this.execJson(args);
392
431
  } catch (err) {
393
432
  const message = err instanceof Error ? err.message : String(err);
394
433
  const needsFallback =
@@ -422,7 +461,7 @@ export class NowledgeMemClient {
422
461
  String(limit),
423
462
  ];
424
463
  try {
425
- return this.execJson(args);
464
+ return await this.execJson(args);
426
465
  } catch (err) {
427
466
  const message = err instanceof Error ? err.message : String(err);
428
467
  const needsFallback =
@@ -472,7 +511,7 @@ export class NowledgeMemClient {
472
511
  if (dateTo) args.push("--to", String(dateTo));
473
512
 
474
513
  try {
475
- const data = this.execJson(args);
514
+ const data = await this.execJson(args);
476
515
  return Array.isArray(data) ? data : (data.events ?? []);
477
516
  } catch (err) {
478
517
  const message = err instanceof Error ? err.message : String(err);
@@ -506,7 +545,7 @@ export class NowledgeMemClient {
506
545
  if (importance !== undefined && Number.isFinite(Number(importance))) {
507
546
  args.push("-i", String(importance));
508
547
  }
509
- const data = this.execJson(args);
548
+ const data = await this.execJson(args);
510
549
  return String(data.id ?? data.memory?.id ?? data.memory_id ?? "created");
511
550
  }
512
551
 
@@ -535,7 +574,7 @@ export class NowledgeMemClient {
535
574
  if (threadId) {
536
575
  args.push("--id", String(threadId));
537
576
  }
538
- data = this.execJson(args);
577
+ data = await this.execJson(args);
539
578
  } catch (err) {
540
579
  const message = err instanceof Error ? err.message : String(err);
541
580
  const needsApiFallback =
@@ -588,7 +627,7 @@ export class NowledgeMemClient {
588
627
  if (idempotencyKey) {
589
628
  args.push("--idempotency-key", String(idempotencyKey));
590
629
  }
591
- const data = this.execJson(args);
630
+ const data = await this.execJson(args);
592
631
  return {
593
632
  messagesAdded: Number(data.messages_added ?? 0),
594
633
  totalMessages: Number(data.total_messages ?? 0),
@@ -627,7 +666,7 @@ export class NowledgeMemClient {
627
666
  if (!id) {
628
667
  throw new Error("getMemory requires memoryId");
629
668
  }
630
- return this.execJson(["--json", "m", "show", id]);
669
+ return await this.execJson(["--json", "m", "show", id]);
631
670
  }
632
671
 
633
672
  isThreadNotFoundError(err) {
@@ -643,7 +682,7 @@ export class NowledgeMemClient {
643
682
 
644
683
  async readWorkingMemory() {
645
684
  try {
646
- const data = this.execJson(["--json", "wm", "read"], 15_000);
685
+ const data = await this.execJson(["--json", "wm", "read"], 15_000);
647
686
  const content = String(data.content ?? "").trim();
648
687
  const exists = Boolean(data.exists);
649
688
  return { content, available: exists && content.length > 0 };
@@ -667,7 +706,7 @@ export class NowledgeMemClient {
667
706
  if (append !== undefined) args.push("--append", String(append));
668
707
 
669
708
  try {
670
- return this.execJson(args, 20_000);
709
+ return await this.execJson(args, 20_000);
671
710
  } catch (err) {
672
711
  const message = err instanceof Error ? err.message : String(err);
673
712
  const notSupported =
@@ -772,7 +811,7 @@ export class NowledgeMemClient {
772
811
  * Unlike searchThreads() (best-effort, swallows errors), this throws on failure
773
812
  * and supports additional parameters.
774
813
  *
775
- * Uses CLI: `nmem --json t search "<query>" --limit N`
814
+ * Uses CLI: `nmem --json t search "<query>" -n N`
776
815
  * Falls back to API: GET /threads/search
777
816
  *
778
817
  * @param {string} query Search keywords
@@ -789,13 +828,13 @@ export class NowledgeMemClient {
789
828
  "t",
790
829
  "search",
791
830
  String(query || ""),
792
- "--limit",
831
+ "-n",
793
832
  String(normalizedLimit),
794
833
  ];
795
834
  if (source) args.push("--source", String(source));
796
835
 
797
836
  try {
798
- const data = this.execJson(args);
837
+ const data = await this.execJson(args);
799
838
  return {
800
839
  threads: data.threads ?? [],
801
840
  totalFound: Number(data.total_found ?? data.total ?? 0),
@@ -830,7 +869,7 @@ export class NowledgeMemClient {
830
869
  * Fetch full messages from a specific thread.
831
870
  * Supports pagination for progressive retrieval of long conversations.
832
871
  *
833
- * Uses CLI: `nmem --json t show <id> --limit N --offset O`
872
+ * Uses CLI: `nmem --json t show <id> -n N --offset O`
834
873
  * Falls back to API: GET /threads/{id}
835
874
  *
836
875
  * @param {string} threadId Thread ID
@@ -846,20 +885,13 @@ export class NowledgeMemClient {
846
885
  );
847
886
  const normalizedOffset = Math.max(0, Math.trunc(Number(offset) || 0));
848
887
 
849
- const args = [
850
- "--json",
851
- "t",
852
- "show",
853
- id,
854
- "--limit",
855
- String(normalizedLimit),
856
- ];
888
+ const args = ["--json", "t", "show", id, "-n", String(normalizedLimit)];
857
889
  if (normalizedOffset > 0) {
858
890
  args.push("--offset", String(normalizedOffset));
859
891
  }
860
892
 
861
893
  try {
862
- const data = this.execJson(args);
894
+ const data = await this.execJson(args);
863
895
  return {
864
896
  threadId: data.thread_id ?? data.id ?? id,
865
897
  title: data.title ?? "(untitled)",
@@ -906,7 +938,7 @@ export class NowledgeMemClient {
906
938
 
907
939
  async checkHealth() {
908
940
  try {
909
- this.exec(["status"]);
941
+ await this.execJson(["--json", "status"]);
910
942
  return true;
911
943
  } catch {
912
944
  return false;
@@ -38,7 +38,7 @@ export function createForgetCommand(client, logger) {
38
38
 
39
39
  try {
40
40
  if (isLikelyId) {
41
- client.exec(["--json", "m", "delete", "-f", text]);
41
+ await client.exec(["--json", "m", "delete", "-f", text]);
42
42
  logger.info(`/forget: deleted memory ${text}`);
43
43
  return { text: `Forgotten: memory ${text} deleted.` };
44
44
  }
@@ -50,7 +50,7 @@ export function createForgetCommand(client, logger) {
50
50
 
51
51
  if (results[0].score >= 0.85) {
52
52
  const target = results[0];
53
- client.exec(["--json", "m", "delete", "-f", target.id]);
53
+ await client.exec(["--json", "m", "delete", "-f", target.id]);
54
54
  logger.info(`/forget: deleted memory ${target.id}`);
55
55
  const preview = target.title || target.content.slice(0, 60);
56
56
  return {
package/src/config.js CHANGED
@@ -19,6 +19,7 @@ const ALLOWED_KEYS = new Set([
19
19
  "sessionDigest",
20
20
  "digestMinInterval",
21
21
  "maxContextResults",
22
+ "recallMinScore",
22
23
  "apiUrl",
23
24
  "apiKey",
24
25
  // Legacy aliases — accepted but not advertised
@@ -135,7 +136,7 @@ function firstDefined(...options) {
135
136
  * will configure via OpenClaw's plugin settings UI (pluginConfig).
136
137
  *
137
138
  * Canonical keys: sessionContext, sessionDigest, digestMinInterval,
138
- * maxContextResults, apiUrl, apiKey
139
+ * maxContextResults, recallMinScore, apiUrl, apiKey
139
140
  *
140
141
  * Legacy aliases (accepted from all sources; never shown in docs):
141
142
  * autoRecall → sessionContext
@@ -148,6 +149,7 @@ function firstDefined(...options) {
148
149
  * NMEM_SESSION_DIGEST — true/1/yes to enable (alias: NMEM_AUTO_CAPTURE)
149
150
  * NMEM_DIGEST_MIN_INTERVAL — seconds (0-86400)
150
151
  * NMEM_MAX_CONTEXT_RESULTS — integer (1-20)
152
+ * NMEM_RECALL_MIN_SCORE — integer (0-100)
151
153
  * NMEM_API_URL — remote server URL
152
154
  * NMEM_API_KEY — API key (never logged)
153
155
  */
@@ -234,6 +236,20 @@ export function parseConfig(raw, logger) {
234
236
  const maxContextResults = Math.min(20, Math.max(1, Math.trunc(mcr.value)));
235
237
  _sources.maxContextResults = mcr.source;
236
238
 
239
+ // --- recallMinScore: file > pluginConfig > env > default ---
240
+ const rmsEnv = envInt("NMEM_RECALL_MIN_SCORE");
241
+ const rms = firstDefined(
242
+ { value: pickNum(resolvedFile, "recallMinScore"), source: "file" },
243
+ {
244
+ value: pickNum(resolvedPlugin, "recallMinScore"),
245
+ source: "pluginConfig",
246
+ },
247
+ { value: rmsEnv, source: "env" },
248
+ { value: 0, source: "default" },
249
+ );
250
+ const recallMinScore = Math.min(100, Math.max(0, Math.trunc(rms.value)));
251
+ _sources.recallMinScore = rms.source;
252
+
237
253
  // --- apiUrl: file > pluginConfig > env > "" ---
238
254
  const fileUrl =
239
255
  typeof resolvedFile.apiUrl === "string" && resolvedFile.apiUrl.trim();
@@ -267,6 +283,7 @@ export function parseConfig(raw, logger) {
267
283
  sessionDigest,
268
284
  digestMinInterval,
269
285
  maxContextResults,
286
+ recallMinScore,
270
287
  apiUrl,
271
288
  apiKey,
272
289
  _sources,
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * This is the single most impactful change for memory adoption:
5
5
  * most LLMs ignore "call this proactively" in tool descriptions,
6
- * but reliably follow behavioral guidance in prepended context.
6
+ * but reliably follow behavioral guidance in system-prompt space.
7
7
  *
8
8
  * Cost: ~50 tokens per turn. Negligible.
9
9
  *
@@ -15,11 +15,11 @@
15
15
  const BASE_GUIDANCE = [
16
16
  "<nowledge-mem-guidance>",
17
17
  "You have access to the user's personal knowledge graph (Nowledge Mem).",
18
- "When the conversation produces something worth keeping — a decision made, a preference stated,",
19
- "something learned, a plan formed, a useful discovery — save it with nowledge_mem_save. Don't wait to be asked.",
20
- "When prior context would improve your response, search with memory_search.",
21
- "memory_search also returns relevant past conversation snippets alongside memories.",
22
- "When a memory has a sourceThreadId, fetch the full conversation with nowledge_mem_thread_fetch.",
18
+ "Before answering questions about prior work, decisions, dates, people, preferences, or plans:",
19
+ "search with memory_search using natural language queries (not file paths).",
20
+ "memory_search returns memories with scores, labels, and relevant past conversation snippets.",
21
+ "When results include a sourceThreadId, use nowledge_mem_thread_fetch for full conversation context.",
22
+ "When the conversation produces a decision, preference, plan, or learning, save it with nowledge_mem_save.",
23
23
  "</nowledge-mem-guidance>",
24
24
  ].join("\n");
25
25
 
@@ -27,10 +27,10 @@ const SESSION_CONTEXT_GUIDANCE = [
27
27
  "<nowledge-mem-guidance>",
28
28
  "You have access to the user's personal knowledge graph (Nowledge Mem).",
29
29
  "Relevant memories and your Working Memory have already been injected into this prompt.",
30
- "Use memory_search only when you need something specific beyond what was auto-recalled.",
31
- "When the conversation produces something worth keeping — a decision made, a preference stated,",
32
- "something learned, a plan formed, a useful discovery — save it with nowledge_mem_save. Don't wait to be asked.",
33
- "When a memory has a sourceThreadId, fetch the full conversation with nowledge_mem_thread_fetch.",
30
+ "Use memory_search when you need something specific beyond what was auto-recalled (natural language queries, not file paths).",
31
+ "memory_search also returns relevant past conversation snippets alongside memories.",
32
+ "When results include a sourceThreadId, use nowledge_mem_thread_fetch for full conversation context.",
33
+ "When the conversation produces a decision, preference, plan, or learning, save it with nowledge_mem_save.",
34
34
  "</nowledge-mem-guidance>",
35
35
  ].join("\n");
36
36
 
@@ -38,6 +38,6 @@ export function buildBehavioralHook(logger, { sessionContext = false } = {}) {
38
38
  const guidance = sessionContext ? SESSION_CONTEXT_GUIDANCE : BASE_GUIDANCE;
39
39
  return (_event, _ctx) => {
40
40
  logger.debug?.("behavioral: injecting guidance");
41
- return { prependContext: guidance };
41
+ return { appendSystemContext: guidance };
42
42
  };
43
43
  }
@@ -250,7 +250,10 @@ function buildConversationText(normalized) {
250
250
  const parts = [];
251
251
  let total = 0;
252
252
  for (const m of normalized) {
253
- const text = truncate(m.fullContent || m.content, MAX_DISTILL_MESSAGE_CHARS);
253
+ const text = truncate(
254
+ m.fullContent || m.content,
255
+ MAX_DISTILL_MESSAGE_CHARS,
256
+ );
254
257
  const line = `${m.role}: ${text}`;
255
258
  if (total + line.length > MAX_CONVERSATION_CHARS) break;
256
259
  parts.push(line);
@@ -297,7 +300,9 @@ export function buildAgentEndCaptureHandler(client, cfg, logger) {
297
300
 
298
301
  // Skip when no new messages were added (e.g. heartbeat re-sync).
299
302
  if (!result || result.messagesAdded === 0) {
300
- logger.debug?.("capture: no new messages since last sync, skipping triage");
303
+ logger.debug?.(
304
+ "capture: no new messages since last sync, skipping triage",
305
+ );
301
306
  return;
302
307
  }
303
308