cogmem 3.7.0 → 3.7.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/MEMORY_ATLAS.md +16 -6
  3. package/README.md +16 -11
  4. package/RELEASE_CHECKLIST.md +4 -4
  5. package/dist/agent/AgentMemoryBackend.d.ts +11 -2
  6. package/dist/agent/AgentMemoryBackend.js +185 -36
  7. package/dist/agent/AgentRecallQueryCompiler.d.ts +1 -1
  8. package/dist/agent/AgentRecallQueryCompiler.js +13 -1
  9. package/dist/agent/MemoryUsageReceipt.js +8 -10
  10. package/dist/agent/SessionWorkingState.js +3 -2
  11. package/dist/agent/UntrustedMemorySerializer.d.ts +2 -0
  12. package/dist/agent/UntrustedMemorySerializer.js +14 -0
  13. package/dist/atlas/ActionFrameExtractor.js +2 -0
  14. package/dist/atlas/EpisodeTitleGenerator.js +42 -3
  15. package/dist/atlas/FacetQueryPlanner.d.ts +2 -0
  16. package/dist/atlas/FacetQueryPlanner.js +57 -16
  17. package/dist/atlas/GraphCurator.d.ts +3 -0
  18. package/dist/atlas/GraphCurator.js +118 -14
  19. package/dist/atlas/MemoryAtlasIndexer.d.ts +12 -0
  20. package/dist/atlas/MemoryAtlasIndexer.js +36 -0
  21. package/dist/atlas/MemoryAtlasQueryCompiler.js +10 -7
  22. package/dist/atlas/MemoryAtlasService.d.ts +1 -0
  23. package/dist/atlas/MemoryAtlasService.js +102 -26
  24. package/dist/atlas/MemoryAtlasTypes.d.ts +7 -0
  25. package/dist/bin/memory.js +14 -4
  26. package/dist/factory.d.ts +18 -0
  27. package/dist/factory.js +41 -2
  28. package/dist/host/openclaw/AutoMemoryPluginInstaller.js +71 -44
  29. package/dist/mcp/server.js +1 -1
  30. package/dist/recall/RecallExplanation.d.ts +1 -1
  31. package/dist/store/MemoryAtlasStore.d.ts +4 -1
  32. package/dist/store/MemoryAtlasStore.js +203 -25
  33. package/dist/utils/ActionKindRegistry.d.ts +10 -0
  34. package/dist/utils/ActionKindRegistry.js +18 -0
  35. package/dist/utils/EntityCueExtractor.d.ts +13 -0
  36. package/dist/utils/EntityCueExtractor.js +81 -0
  37. package/examples/hermes-backend/AGENTS.md +1 -1
  38. package/examples/hermes-backend/README.md +1 -1
  39. package/examples/hermes-backend/SKILL.md +10 -3
  40. package/examples/hermes-backend/references/operations.md +9 -8
  41. package/examples/openclaw-backend/AGENTS.md +1 -1
  42. package/examples/openclaw-backend/README.md +2 -2
  43. package/examples/openclaw-backend/SKILL.md +24 -7
  44. package/examples/openclaw-backend/references/operations.md +26 -10
  45. package/package.json +1 -1
@@ -28,6 +28,7 @@ function readArgs(argv) {
28
28
  command,
29
29
  query: stringArg(values, 'query') || stringArg(values, 'q'),
30
30
  eventId: stringArg(values, 'event') || stringArg(values, 'event-id'),
31
+ episodeId: stringArg(values, 'episode') || stringArg(values, 'episode-id'),
31
32
  nodeId: stringArg(values, 'id') || stringArg(values, 'node-id'),
32
33
  fromId: stringArg(values, 'from'),
33
34
  toId: stringArg(values, 'to'),
@@ -96,6 +97,7 @@ function usage() {
96
97
  ' graph-neighbors expand --id by --hops 1..2',
97
98
  ' graph-path find a bounded path from --from to --to',
98
99
  ' graph-timeline reconstruct entity/time/action history for --query',
100
+ ' graph-reindex reproject one Atlas episode/card by --event or --episode without rebuilding the whole graph',
99
101
  '',
100
102
  'Common options:',
101
103
  ' --project <id> scope to one project',
@@ -122,7 +124,7 @@ function usage() {
122
124
  ' --interval-ms <n> watch sleep interval, default 300000',
123
125
  ' --max-runs <n> stop watch after n iterations; omit for long-running worker',
124
126
  ' --agent <id> agent id for governed recall, default openclaw',
125
- ' --intent <intent> memory_recall, previous_session_summary, forensic_quote, or historical_discussion',
127
+ ' --intent <intent> memory_recall, previous_session_summary, forensic_quote, historical_discussion, or action_history',
126
128
  ' --db <memory.db> open an explicit database path',
127
129
  ' --config <toml> open a cogmem TOML config',
128
130
  ' --include-evidence include bounded raw excerpts; event ids are always returned',
@@ -158,7 +160,8 @@ function isMemoryCommand(value) {
158
160
  || value === 'graph-node'
159
161
  || value === 'graph-neighbors'
160
162
  || value === 'graph-path'
161
- || value === 'graph-timeline';
163
+ || value === 'graph-timeline'
164
+ || value === 'graph-reindex';
162
165
  }
163
166
  function reviewActionArg(values, key) {
164
167
  const raw = stringArg(values, key);
@@ -172,9 +175,9 @@ function recallIntentArg(values, key) {
172
175
  const raw = stringArg(values, key);
173
176
  if (!raw)
174
177
  return undefined;
175
- if (raw === 'memory_recall' || raw === 'previous_session_summary' || raw === 'forensic_quote' || raw === 'historical_discussion')
178
+ if (raw === 'memory_recall' || raw === 'previous_session_summary' || raw === 'forensic_quote' || raw === 'historical_discussion' || raw === 'action_history')
176
179
  return raw;
177
- throw new Error(`--${key} must be one of memory_recall, previous_session_summary, forensic_quote, historical_discussion`);
180
+ throw new Error(`--${key} must be one of memory_recall, previous_session_summary, forensic_quote, historical_discussion, action_history`);
178
181
  }
179
182
  function orderArg(values, key) {
180
183
  const raw = stringArg(values, key);
@@ -603,6 +606,13 @@ function runGraphCommand(kernel, args) {
603
606
  evidenceLimit: args.evidenceLimit, now: args.now,
604
607
  refresh: args.refresh ? true : (args.staleOk ? false : undefined),
605
608
  staleOk: args.staleOk || !args.refresh };
609
+ if (args.command === 'graph-reindex') {
610
+ if (!args.eventId && !args.episodeId)
611
+ throw new Error(`graph-reindex requires --event or --episode.\n${usage()}`);
612
+ if (args.eventId && args.episodeId)
613
+ throw new Error(`graph-reindex accepts exactly one of --event or --episode.\n${usage()}`);
614
+ return kernel.reindexMemoryAtlas({ projectId, eventId: args.eventId, episodeId: args.episodeId });
615
+ }
606
616
  if (args.command === 'graph')
607
617
  return kernel.graphOverview(options);
608
618
  if (args.command === 'graph-search') {
package/dist/factory.d.ts CHANGED
@@ -326,9 +326,15 @@ export type EpisodeRepairInput = {
326
326
  };
327
327
  export interface EpisodeRepairResult {
328
328
  repairId: string;
329
+ applied: boolean;
329
330
  operation: EpisodeRepairInput['operation'];
330
331
  affectedEpisodeIds: string[];
332
+ changedFields: string[];
331
333
  staleCandidateIds: string[];
334
+ requeuedDream: boolean;
335
+ graphRefreshNeeded: boolean;
336
+ nextCommands: string[];
337
+ note: string;
332
338
  }
333
339
  export interface ToolCallMemoryEventInput {
334
340
  projectId?: string;
@@ -635,6 +641,18 @@ export declare class MemoryKernel {
635
641
  documents: number;
636
642
  actions: number;
637
643
  };
644
+ reindexMemoryAtlas(options: {
645
+ projectId: string;
646
+ eventId?: string;
647
+ episodeId?: string;
648
+ }): {
649
+ projectId: string;
650
+ episodeIds: string[];
651
+ refreshed: boolean;
652
+ curatedEpisodes: number;
653
+ facetEdges: number;
654
+ reviewNeeded: number;
655
+ };
638
656
  ensureMemoryAtlas(options: {
639
657
  projectId: string;
640
658
  }): {
package/dist/factory.js CHANGED
@@ -83,7 +83,7 @@ import { SqliteVecStore } from './store/SqliteVecStore.js';
83
83
  import { VectorStore } from './store/VectorStore.js';
84
84
  import { config } from './utils/Config.js';
85
85
  import { KernelRunningError, SnapshotExporter, SnapshotImporter, } from './snapshot/index.js';
86
- const CORE_VERSION = '3.7.0';
86
+ const CORE_VERSION = '3.7.2';
87
87
  const LATEST_SCHEMA_VERSION = 27;
88
88
  export class MemoryKernel {
89
89
  options;
@@ -1056,6 +1056,7 @@ export class MemoryKernel {
1056
1056
  const affected = new Set();
1057
1057
  const before = {};
1058
1058
  const previousStatuses = new Map();
1059
+ let requeuedDream = input.operation === 'requeue-dream' || input.operation === 'invalidate-dream-run';
1059
1060
  if (input.operation === 'move-event') {
1060
1061
  const link = this.episodeStore.getEventLink(input.eventId);
1061
1062
  const source = link ? this.episodeStore.getEpisode(link.episodeId) : undefined;
@@ -1169,11 +1170,30 @@ export class MemoryKernel {
1169
1170
  const episode = this.episodeStore.getEpisode(episodeId);
1170
1171
  if (episode?.eventCount && episode.status === 'sealed') {
1171
1172
  this.episodeStore.requeueDreamForRepair(episodeId, input.operation === 'invalidate-dream-run' ? input.mode || 'normal' : 'normal', now);
1173
+ requeuedDream = true;
1172
1174
  }
1173
1175
  }
1174
1176
  const after = Object.fromEntries([...affected].map((episodeId) => [episodeId, this.episodeStore.getEpisode(episodeId)]));
1175
1177
  const repairId = this.episodeStore.recordRepairAudit({ projectId: input.projectId, operation: input.operation, payload: input, before, after, now });
1176
- return { repairId, operation: input.operation, affectedEpisodeIds: [...affected], staleCandidateIds };
1178
+ const affectedEpisodeIds = [...affected];
1179
+ const nextCommands = affectedEpisodeIds.length
1180
+ ? affectedEpisodeIds.map((episodeId) => `cogmem memory graph-reindex --project ${input.projectId} --episode ${episodeId} --json`)
1181
+ : [];
1182
+ return {
1183
+ repairId,
1184
+ applied: true,
1185
+ operation: input.operation,
1186
+ affectedEpisodeIds,
1187
+ changedFields: changedFieldsForRepair(input),
1188
+ staleCandidateIds,
1189
+ requeuedDream,
1190
+ graphRefreshNeeded: affectedEpisodeIds.length > 0,
1191
+ nextCommands: [
1192
+ ...nextCommands,
1193
+ `cogmem memory graph-explore --project ${input.projectId} --query "<query>" --json`,
1194
+ ],
1195
+ note: 'repairId is an audit id, not a dream candidate id; do not run memory dream --promote just because repairId exists.',
1196
+ };
1177
1197
  }
1178
1198
  listDreamCandidates(options = {}) {
1179
1199
  return this.deepWriteCandidateStore.listCandidates(options);
@@ -1265,6 +1285,9 @@ export class MemoryKernel {
1265
1285
  rebuildMemoryAtlas(options = {}) {
1266
1286
  return this.memoryAtlasIndexer.rebuild(options);
1267
1287
  }
1288
+ reindexMemoryAtlas(options) {
1289
+ return this.memoryAtlasIndexer.reindex(options);
1290
+ }
1268
1291
  ensureMemoryAtlas(options) {
1269
1292
  return this.memoryAtlasIndexer.ensureFresh(options);
1270
1293
  }
@@ -2002,6 +2025,22 @@ export function createMemoryKernelFromConfig(input = {}) {
2002
2025
  const { configPath: _configPath, cwd: _cwd, env: _env, ...explicitOptions } = options;
2003
2026
  return createMemoryKernel({ ...loaded.options, ...explicitOptions });
2004
2027
  }
2028
+ function changedFieldsForRepair(input) {
2029
+ if (input.operation === 'reclassify') {
2030
+ return [
2031
+ input.episodeType ? 'episodeType' : '',
2032
+ input.topicPath ? 'topicPath' : '',
2033
+ input.importance !== undefined ? 'importance' : '',
2034
+ ].filter(Boolean);
2035
+ }
2036
+ if (input.operation === 'move-event')
2037
+ return ['episodeEvents'];
2038
+ if (input.operation === 'split' || input.operation === 'merge')
2039
+ return ['episodeEvents', 'crossRefs'];
2040
+ if (input.operation === 'requeue-dream' || input.operation === 'invalidate-dream-run')
2041
+ return ['dreamQueue'];
2042
+ return [];
2043
+ }
2005
2044
  function requiredGovernancePayloadString(payload, field) {
2006
2045
  const value = payload[field];
2007
2046
  if (typeof value !== 'string' || value.trim() === '')
@@ -5,7 +5,7 @@ import { basename, dirname, join, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { resolveCogmemConfigPath } from '../../config/CogmemConfig.js';
7
7
  const PLUGIN_ID = 'cogmem-auto-memory';
8
- const PLUGIN_VERSION = '0.7.0';
8
+ const PLUGIN_VERSION = '0.7.1';
9
9
  function defaultPublicEntrypoint() {
10
10
  return join(resolve(dirname(fileURLToPath(import.meta.url)), '../..'), 'public.js');
11
11
  }
@@ -516,15 +516,29 @@ function createMemoryUsageReceipt(input) {
516
516
 
517
517
  function formatSourceAnchor(anchor) {
518
518
  return [
519
- anchor.memoryId ? 'memory:' + anchor.memoryId : '',
520
- anchor.eventId ? 'event:' + anchor.eventId : '',
521
- anchor.sessionId ? 'session:' + anchor.sessionId : '',
522
- anchor.role ? 'role:' + anchor.role : '',
519
+ anchor.memoryId ? 'memory:' + serializeUntrustedMemory(anchor.memoryId, 160) : '',
520
+ anchor.eventId ? 'event:' + serializeUntrustedMemory(anchor.eventId, 160) : '',
521
+ anchor.sessionId ? 'session:' + serializeUntrustedMemory(anchor.sessionId, 160) : '',
522
+ anchor.role ? 'role:' + serializeUntrustedMemory(anchor.role, 80) : '',
523
523
  ].filter(Boolean).join('; ');
524
524
  }
525
525
 
526
526
  function listLines(values) {
527
- return values.length ? values.map((value) => '- ' + value) : ['- none'];
527
+ return values.length ? values.map((value) => '- ' + serializeUntrustedMemory(value, 260)) : ['- none'];
528
+ }
529
+
530
+ function serializeUntrustedMemory(input, limit) {
531
+ const clean = stripCogmemRecallBlocks(String(input || '')).text
532
+ .replace(new RegExp('</?COGMEM_[A-Z0-9_:-]+[^>]*>', 'gi'), '')
533
+ .replace(new RegExp('[\\\\u0000-\\\\u0008\\\\u000b\\\\u000c\\\\u000e-\\\\u001f\\\\u007f]', 'g'), ' ')
534
+ .replace(new RegExp('\\\\s+', 'g'), ' ')
535
+ .trim()
536
+ .replace(/&/g, '&amp;')
537
+ .replace(/</g, '&lt;')
538
+ .replace(/>/g, '&gt;')
539
+ .replace(/"/g, '&quot;')
540
+ .replace(/'/g, '&#39;');
541
+ return clean.slice(0, Math.max(0, Number(limit || 500)));
528
542
  }
529
543
 
530
544
  function clampBlock(text, closingTag, maxChars) {
@@ -535,14 +549,14 @@ function clampBlock(text, closingTag, maxChars) {
535
549
 
536
550
  function formatMemoryUsageBridge(receipt, maxChars) {
537
551
  const lines = [
538
- '<COGMEM_TURN_BRIDGE turn_id="' + String(receipt.turnId).replace(/"/g, '&quot;') + '" source="cogmem" compact="true" ttl_turns="' + receipt.ttlTurns + '" compile_allowed="false">',
552
+ '<COGMEM_TURN_BRIDGE turn_id="' + serializeUntrustedMemory(receipt.turnId, 180) + '" source="cogmem" compact="true" ttl_turns="' + receipt.ttlTurns + '" compile_allowed="false">',
539
553
  'Previous assistant answer used Cogmem memory.',
540
554
  '',
541
555
  'Used memory themes:',
542
556
  ...listLines(receipt.usedThemes || []),
543
557
  '',
544
558
  'Working conclusion produced in that turn:',
545
- '- ' + (receipt.workingConclusion || 'No compact conclusion recorded.'),
559
+ '- ' + serializeUntrustedMemory(receipt.workingConclusion || 'No compact conclusion recorded.', 260),
546
560
  '',
547
561
  'Source anchors:',
548
562
  ...listLines((receipt.sourceAnchors || []).map(formatSourceAnchor)),
@@ -608,7 +622,7 @@ function formatSessionWorkingState(state, maxChars) {
608
622
  const lines = [
609
623
  '<COGMEM_SESSION_STATE scope="current_session" compact="true" persistence="session_only" compile_allowed="false">',
610
624
  'Current working topic:',
611
- '- ' + (state.currentTopic || 'unspecified'),
625
+ '- ' + serializeUntrustedMemory(state.currentTopic || 'unspecified', 180),
612
626
  '',
613
627
  'Current design direction:',
614
628
  ...listLines(state.designDirection || []),
@@ -663,6 +677,9 @@ function classifyRecallIntent(query) {
663
677
  if (/原话|怎么说的|完整对话|上一句|下一句|exact quote|verbatim/.test(text)) {
664
678
  return 'forensic_quote';
665
679
  }
680
+ if (/(让你.{0,20}(对|给|把)?.{0,20}(做过|做了|启动|安装|配置|修改|重启|停止|操作|处理|执行)|对.{0,30}(做过什么|做了什么|哪些操作)|what did (i ask you to do|you do) to|operations? on)/i.test(text)) {
681
+ return 'action_history';
682
+ }
666
683
  if (/记得.{0,12}(聊过|讨论过|说过)|还记得|之前.{0,12}(聊过|讨论过|说过)|以前.{0,12}(聊过|讨论过|说过)|(过去|几个月前|半年前|上个月|前几天|昨天|上次|上个).{0,20}(聊过|讨论过|说过)|当时.{0,12}(聊|说|问)|那次.{0,12}(聊|说|问)|有没有.{0,12}(记录|聊过|讨论过)|have we discussed|did we talk about|previously discussed/.test(text)) {
667
684
  return 'historical_discussion';
668
685
  }
@@ -1314,7 +1331,7 @@ async function recallPayload(input, config, kernel, memory, formatStrategyContex
1314
1331
  let result = await memory.recall({
1315
1332
  agentId: config.agentId || 'openclaw', projectId: config.projectId || 'openclaw',
1316
1333
  query: input.query || '', sessionId: input.sessionId, threadId: input.threadId,
1317
- excludeSessionId: input.excludeSessionId, intent: input.intent || 'memory_recall',
1334
+ excludeSessionId: input.excludeSessionId, intent: input.intent || undefined,
1318
1335
  anchorEventId: input.anchorEventId, anchorText: input.anchorText,
1319
1336
  limit: Number(config.limit || 3), retrievalPolicy: strategyCapsule && strategyCapsule.retrievalPolicy,
1320
1337
  });
@@ -1327,7 +1344,7 @@ async function recallPayload(input, config, kernel, memory, formatStrategyContex
1327
1344
  result = await memory.recall({
1328
1345
  agentId: config.agentId || 'openclaw', projectId: config.projectId || 'openclaw',
1329
1346
  query: input.query || '', sessionId: input.sessionId, threadId: input.threadId,
1330
- excludeSessionId: input.excludeSessionId, intent: input.intent || 'memory_recall',
1347
+ excludeSessionId: input.excludeSessionId, intent: input.intent || undefined,
1331
1348
  anchorEventId: input.anchorEventId, anchorText: input.anchorText,
1332
1349
  limit: Number(config.limit || 3), retrievalPolicy: strategyCapsule && strategyCapsule.retrievalPolicy,
1333
1350
  });
@@ -1368,7 +1385,7 @@ async function recallPayload(input, config, kernel, memory, formatStrategyContex
1368
1385
  return {
1369
1386
  context: recallContext ? (strategyCapsule ? formatStrategyContext(strategyCapsule) + '\n\n' : '') + recallContext : '',
1370
1387
  items: compactRecallItems(plannedResult.items, config), itemCount: plannedResult.items.length,
1371
- recallMode: result.recallMode, fallbackUsed: result.fallbackUsed, intent: input.intent || 'memory_recall',
1388
+ recallMode: result.recallMode, fallbackUsed: result.fallbackUsed, intent: (result.queryPlan && result.queryPlan.intent) || input.intent || 'memory_recall',
1372
1389
  anchorEventId: anchorItem && anchorItem.sourceAnchor && anchorItem.sourceAnchor.eventId,
1373
1390
  anchorText: anchorItem && anchorItem.text, queryPlan: result.queryPlan, decisionTrace: result.decisionTrace,
1374
1391
  atlasCards: result.atlasCards, selectedEpisodeCards,
@@ -1470,10 +1487,20 @@ function formatAtlasContext(result, maxChars) {
1470
1487
  }
1471
1488
 
1472
1489
  function safeAtlasText(input, limit) {
1490
+ return serializeUntrustedMemory(input, limit);
1491
+ }
1492
+
1493
+ function serializeUntrustedMemory(input, limit) {
1473
1494
  const clean = stripCogmemRecallBlocks(String(input || '')).text
1474
- .replace(/[<>]/g, (value) => value === '<' ? '\\u003c' : '\\u003e')
1495
+ .replace(/<\/?COGMEM_[A-Z0-9_:-]+[^>]*>/gi, '')
1496
+ .replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, ' ')
1475
1497
  .replace(/\s+/g, ' ')
1476
- .trim();
1498
+ .trim()
1499
+ .replace(/&/g, '&amp;')
1500
+ .replace(/</g, '&lt;')
1501
+ .replace(/>/g, '&gt;')
1502
+ .replace(/"/g, '&quot;')
1503
+ .replace(/'/g, '&#39;');
1477
1504
  return clean.slice(0, Math.max(0, Number(limit || 500)));
1478
1505
  }
1479
1506
 
@@ -1773,43 +1800,43 @@ function formatRecallContext(result, config) {
1773
1800
  lines.push('selectedMemoryCards=' + result.atlasCards.slice(0, Number(config.memoryContextMaxItems || config.limit || 3)).map(formatAtlasCard).join(' | '));
1774
1801
  }
1775
1802
  if (Array.isArray(result.relaxationTrace) && result.relaxationTrace.length) {
1776
- lines.push('relaxationTrace=' + result.relaxationTrace.map((step) => step.from + ' -> ' + step.to + ' (' + step.reason + ')').join(' | '));
1803
+ lines.push('relaxationTrace=' + result.relaxationTrace.map((step) => serializeUntrustedMemory(step.from, 80) + ' -> ' + serializeUntrustedMemory(step.to, 80) + ' (' + serializeUntrustedMemory(step.reason, 160) + ')').join(' | '));
1777
1804
  }
1778
1805
  if (Array.isArray(result.relatedButNotSelected) && result.relatedButNotSelected.length) {
1779
- lines.push('relatedButNotSelected=' + result.relatedButNotSelected.slice(0, 4).map((item) => item.displayTitle + ' [' + item.reason + ']').join(' | '));
1806
+ lines.push('relatedButNotSelected=' + result.relatedButNotSelected.slice(0, 4).map((item) => serializeUntrustedMemory(item.displayTitle, 160) + ' [' + serializeUntrustedMemory(item.reason, 160) + ']').join(' | '));
1780
1807
  }
1781
1808
  lines.push('');
1782
1809
  if (result.narrative && result.narrative.summary) {
1783
- lines.push(result.narrative.summary);
1810
+ lines.push(serializeUntrustedMemory(result.narrative.summary, 800));
1784
1811
  }
1785
1812
  const maxItems = Number(config.memoryContextMaxItems || config.limit || 3);
1786
1813
  const sourceWindowMaxChars = Number(config.sourceWindowMaxChars || 1200);
1787
1814
  const includeSourceWindow = config.includeSourceWindowByDefault === true;
1788
1815
  for (const item of result.items.slice(0, maxItems)) {
1789
- const source = item.source ? ' [' + item.source + ']' : '';
1790
- lines.push('- ' + item.text + source);
1816
+ const source = item.source ? ' [' + serializeUntrustedMemory(item.source, 220) + ']' : '';
1817
+ lines.push('- ' + serializeUntrustedMemory(item.text, 500) + source);
1791
1818
  const sourceType = item.sourceType || 'compiled_memory';
1792
1819
  const quote = item.canAnswerExactQuote === true ? 'true' : 'false';
1793
1820
  const confidence = Number.isFinite(item.confidence) ? String(item.confidence) : 'unknown';
1794
- const anchor = item.sourceAnchor ? '; anchorEvent=' + (item.sourceAnchor.eventId || 'unknown')
1795
- + (item.sourceAnchor.sessionId ? '; session=' + item.sourceAnchor.sessionId : '')
1796
- + (item.sourceAnchor.role ? '; role=' + item.sourceAnchor.role : '') : '';
1797
- const why = item.whyMatched ? '; whyMatched=' + item.whyMatched : '';
1798
- const canonical = item.canonicalId ? '; canonicalId=' + item.canonicalId : '';
1821
+ const anchor = item.sourceAnchor ? '; anchorEvent=' + serializeUntrustedMemory(item.sourceAnchor.eventId || 'unknown', 160)
1822
+ + (item.sourceAnchor.sessionId ? '; session=' + serializeUntrustedMemory(item.sourceAnchor.sessionId, 160) : '')
1823
+ + (item.sourceAnchor.role ? '; role=' + serializeUntrustedMemory(item.sourceAnchor.role, 80) : '') : '';
1824
+ const why = item.whyMatched ? '; whyMatched=' + serializeUntrustedMemory(item.whyMatched, 180) : '';
1825
+ const canonical = item.canonicalId ? '; canonicalId=' + serializeUntrustedMemory(item.canonicalId, 180) : '';
1799
1826
  const matchedFacets = Array.isArray(item.matchedFacets) && item.matchedFacets.length
1800
- ? '; matchedFacets=' + item.matchedFacets.map((facet) => facet.type + ':' + facet.value).join(',')
1827
+ ? '; matchedFacets=' + item.matchedFacets.map((facet) => serializeUntrustedMemory(facet.type, 80) + ':' + serializeUntrustedMemory(facet.value, 160)).join(',')
1801
1828
  : '';
1802
- lines.push(' sourceType=' + sourceType + '; confidence=' + confidence + '; canAnswerExactQuote=' + quote + canonical + matchedFacets + anchor + why);
1829
+ lines.push(' sourceType=' + serializeUntrustedMemory(sourceType, 120) + '; confidence=' + confidence + '; canAnswerExactQuote=' + quote + canonical + matchedFacets + anchor + why);
1803
1830
  if (item.sourceContext && item.sourceContext.event) {
1804
1831
  const anchorEvent = item.sourceContext.event;
1805
1832
  const anchorFormatted = formatContextEvent(anchorEvent, Math.min(220, sourceWindowMaxChars));
1806
1833
  lines.push(' sourceContext=' + anchorFormatted.line);
1807
1834
  lines.push(' sourceWindow=' + formatSourceWindow(item.sourceContext.window, item.sourceContext));
1808
1835
  if (item.sourceContext.locator && item.sourceContext.locator.command) {
1809
- lines.push(' sourceLocator=' + item.sourceContext.locator.command);
1836
+ lines.push(' sourceLocator=' + serializeUntrustedMemory(item.sourceContext.locator.command, 500));
1810
1837
  } else if (item.sourceContext.event.eventId) {
1811
1838
  const project = item.sourceContext.event.projectId ? ' --project ' + item.sourceContext.event.projectId : '';
1812
- lines.push(' sourceLocator=cogmem memory show --event ' + item.sourceContext.event.eventId + project + ' --before 2 --after 2 --json');
1839
+ lines.push(' sourceLocator=' + serializeUntrustedMemory('cogmem memory show --event ' + item.sourceContext.event.eventId + project + ' --before 2 --after 2 --json', 500));
1813
1840
  }
1814
1841
  const seenEventIds = new Set([anchorEvent.eventId].filter(Boolean));
1815
1842
  const before = uniqueWindowEvents(Array.isArray(item.sourceContext.before) ? item.sourceContext.before : [], seenEventIds).slice(-2);
@@ -1854,16 +1881,16 @@ function formatRecallDecision(trace) {
1854
1881
 
1855
1882
  function formatAtlasCard(card) {
1856
1883
  const facets = Array.isArray(card && card.matchedFacets)
1857
- ? card.matchedFacets.map((facet) => facet.type + ':' + facet.value).join(',')
1884
+ ? card.matchedFacets.map((facet) => serializeUntrustedMemory(facet.type, 80) + ':' + serializeUntrustedMemory(facet.value, 160)).join(',')
1858
1885
  : '';
1859
1886
  const paths = Array.isArray(card && card.matchedPaths)
1860
- ? card.matchedPaths.map((path) => path.relation + '@' + (path.facet && path.facet.nodeId || 'facet')).slice(0, 6).join(',')
1887
+ ? card.matchedPaths.map((path) => serializeUntrustedMemory(path.relation, 80) + '@' + serializeUntrustedMemory(path.facet && path.facet.nodeId || 'facet', 180)).slice(0, 6).join(',')
1861
1888
  : '';
1862
- const locator = card && card.sourceLocator && card.sourceLocator.command ? '; sourceLocator=' + card.sourceLocator.command : '';
1863
- const why = card && card.whyMatched ? '; whyMatched=' + truncateLineWithMeta(card.whyMatched, 180).text : '';
1864
- return (card && card.canonicalId || 'episode:unknown')
1865
- + '; title=' + truncateLineWithMeta(card && card.displayTitle, 120).text
1866
- + '; summary=' + truncateLineWithMeta(card && card.oneLineSummary, 180).text
1889
+ const locator = card && card.sourceLocator && card.sourceLocator.command ? '; sourceLocator=' + serializeUntrustedMemory(card.sourceLocator.command, 500) : '';
1890
+ const why = card && card.whyMatched ? '; whyMatched=' + serializeUntrustedMemory(card.whyMatched, 180) : '';
1891
+ return serializeUntrustedMemory(card && card.canonicalId || 'episode:unknown', 180)
1892
+ + '; title=' + serializeUntrustedMemory(card && card.displayTitle, 120)
1893
+ + '; summary=' + serializeUntrustedMemory(card && card.oneLineSummary, 180)
1867
1894
  + (facets ? '; matchedFacets=' + facets : '')
1868
1895
  + (paths ? '; matchedPaths=' + paths : '')
1869
1896
  + why
@@ -1878,10 +1905,10 @@ function clampRecallContext(text, maxChars) {
1878
1905
  }
1879
1906
 
1880
1907
  function formatContextEvent(event, limit) {
1881
- const truncation = truncateLineWithMeta(event && event.text, limit);
1882
- const label = contextEventLabel(event);
1883
- const role = event && event.role ? event.role : 'unknown';
1884
- const eventId = event && event.eventId ? event.eventId : 'unknown';
1908
+ const truncation = truncateLineWithMeta(serializeUntrustedMemory(event && event.text, limit), limit);
1909
+ const label = serializeUntrustedMemory(contextEventLabel(event), 120);
1910
+ const role = serializeUntrustedMemory(event && event.role ? event.role : 'unknown', 80);
1911
+ const eventId = serializeUntrustedMemory(event && event.eventId ? event.eventId : 'unknown', 160);
1885
1912
  const charRange = event && event.charRange ? '; charRange=' + event.charRange.start + '-' + event.charRange.end : '';
1886
1913
  const sourceRange = formatSourceRange(event && event.sourceRange);
1887
1914
  const textLength = Number.isFinite(event && event.textLength) ? event.textLength : truncation.originalChars;
@@ -1903,17 +1930,17 @@ function formatSourceWindow(window, context) {
1903
1930
  const dropped = Array.isArray(window && window.droppedOverlapEventIds) ? window.droppedOverlapEventIds : [];
1904
1931
  return 'before=' + formatWindowSide(before)
1905
1932
  + '; after=' + formatWindowSide(after)
1906
- + '; overlap=' + (overlapEventIds.length ? overlapEventIds.join(',') : 'none')
1907
- + '; droppedOverlap=' + (dropped.length ? dropped.join(',') : 'none')
1908
- + '; overlapHandling=' + ((window && window.overlapHandling) || 'drop_from_after');
1933
+ + '; overlap=' + (overlapEventIds.length ? overlapEventIds.map((id) => serializeUntrustedMemory(id, 120)).join(',') : 'none')
1934
+ + '; droppedOverlap=' + (dropped.length ? dropped.map((id) => serializeUntrustedMemory(id, 120)).join(',') : 'none')
1935
+ + '; overlapHandling=' + serializeUntrustedMemory((window && window.overlapHandling) || 'drop_from_after', 120);
1909
1936
  }
1910
1937
 
1911
1938
  function formatWindowSide(side) {
1912
1939
  return 'requestedCount=' + Number(side.requestedCount || 0)
1913
1940
  + ', count=' + Number(side.count || 0)
1914
1941
  + ', excludesAnchor=' + (side.excludesAnchor !== false)
1915
- + ', ordering=' + (side.ordering || 'chronological')
1916
- + ', roleFilter=' + (side.roleFilter || 'all');
1942
+ + ', ordering=' + serializeUntrustedMemory(side.ordering || 'chronological', 80)
1943
+ + ', roleFilter=' + serializeUntrustedMemory(side.roleFilter || 'all', 80);
1917
1944
  }
1918
1945
 
1919
1946
  function uniqueWindowEvents(events, seenEventIds) {
@@ -5,7 +5,7 @@ import { callCogmemMcpTool, listCogmemMcpTools, } from './CoreMcpTools.js';
5
5
  export function createCogmemMcpServer(runtime = {}) {
6
6
  const server = new Server({
7
7
  name: 'cogmem-core',
8
- version: '3.7.0',
8
+ version: '3.7.2',
9
9
  }, {
10
10
  capabilities: {
11
11
  tools: {},
@@ -44,7 +44,7 @@ export interface RecallExplanation {
44
44
  projectId?: string;
45
45
  agentId?: string;
46
46
  collection?: string;
47
- recallMode: MemoryKernelNavigationResult['recallMode'] | 'raw_ledger_fallback';
47
+ recallMode: MemoryKernelNavigationResult['recallMode'] | 'raw_ledger_fallback' | 'atlas_facet_recall' | 'atlas_raw_grounded_recall';
48
48
  fallbackUsed: boolean;
49
49
  narrative?: NonNullable<MemoryKernelNavigationResult['navigation']>['narrative'];
50
50
  pulseTrace?: NonNullable<MemoryKernelNavigationResult['navigation']>['pulse']['trace'];
@@ -2,7 +2,7 @@ import type Database from 'bun:sqlite';
2
2
  import type { FacetQueryPlan } from '../atlas/FacetQueryPlanner.js';
3
3
  import type { MemoryAtlasAction, MemoryAtlasCard, MemoryAtlasEdge, MemoryAtlasNode, MemoryAtlasRelatedCard } from '../atlas/MemoryAtlasTypes.js';
4
4
  export declare const MEMORY_ATLAS_PROJECTION_NAME = "memory_atlas.v2";
5
- export declare const MEMORY_ATLAS_PROJECTION_SCHEMA_VERSION = "3.7.0";
5
+ export declare const MEMORY_ATLAS_PROJECTION_SCHEMA_VERSION = "3.7.2";
6
6
  export declare class MemoryAtlasStore {
7
7
  readonly db: Database;
8
8
  constructor(db: Database);
@@ -33,6 +33,7 @@ export declare class MemoryAtlasStore {
33
33
  evidenceTotal(nodeId: string, projectId: string): number;
34
34
  listEdges(projectId: string): MemoryAtlasEdge[];
35
35
  listEdgesForNodes(projectId: string, nodeIds: string[], limit?: number): MemoryAtlasEdge[];
36
+ listEdgesWithinNodes(projectId: string, nodeIds: string[], limit?: number): MemoryAtlasEdge[];
36
37
  findEdgesBetween(projectId: string, leftNodeId: string, rightNodeId: string): MemoryAtlasEdge[];
37
38
  findEdgesFromNodesToTarget(projectId: string, leftNodeIds: string[], rightNodeId: string): MemoryAtlasEdge[];
38
39
  listActions(projectId: string, options: {
@@ -52,6 +53,8 @@ export declare class MemoryAtlasStore {
52
53
  decay(projectId?: string, factor?: number, now?: number): number;
53
54
  projectionNeedsRefresh(projectId: string): boolean;
54
55
  markProjectionClean(projectId: string, metadata?: Record<string, unknown>, now?: number): void;
56
+ markProjectionDirty(projectId: string, metadata?: Record<string, unknown>, now?: number): void;
57
+ aggregateFacetNodeSupport(projectId: string, now?: number): void;
55
58
  markProjectionFailed(projectId: string, error: string, now?: number): void;
56
59
  getProjectionState(projectId: string): {
57
60
  status: string;