cogmem 3.6.5 → 3.7.1

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 (46) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/MEMORY_ATLAS.md +59 -12
  3. package/README.md +21 -13
  4. package/RELEASE_CHECKLIST.md +5 -4
  5. package/dist/agent/AgentMemoryBackend.d.ts +24 -3
  6. package/dist/agent/AgentMemoryBackend.js +285 -55
  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.d.ts +31 -0
  15. package/dist/atlas/EpisodeTitleGenerator.js +191 -0
  16. package/dist/atlas/FacetQueryPlanner.d.ts +33 -0
  17. package/dist/atlas/FacetQueryPlanner.js +229 -0
  18. package/dist/atlas/GraphCurator.d.ts +30 -0
  19. package/dist/atlas/GraphCurator.js +501 -0
  20. package/dist/atlas/MemoryAtlasIndexer.d.ts +15 -0
  21. package/dist/atlas/MemoryAtlasIndexer.js +55 -6
  22. package/dist/atlas/MemoryAtlasQueryCompiler.js +10 -7
  23. package/dist/atlas/MemoryAtlasService.d.ts +4 -1
  24. package/dist/atlas/MemoryAtlasService.js +174 -11
  25. package/dist/atlas/MemoryAtlasTypes.d.ts +75 -11
  26. package/dist/bin/memory.js +18 -5
  27. package/dist/factory.d.ts +18 -0
  28. package/dist/factory.js +41 -2
  29. package/dist/host/openclaw/AutoMemoryPluginInstaller.js +135 -39
  30. package/dist/mcp/server.js +1 -1
  31. package/dist/recall/RecallExplanation.d.ts +1 -1
  32. package/dist/store/MemoryAtlasStore.d.ts +12 -1
  33. package/dist/store/MemoryAtlasStore.js +353 -15
  34. package/dist/utils/ActionKindRegistry.d.ts +10 -0
  35. package/dist/utils/ActionKindRegistry.js +18 -0
  36. package/dist/utils/EntityCueExtractor.d.ts +13 -0
  37. package/dist/utils/EntityCueExtractor.js +81 -0
  38. package/examples/hermes-backend/AGENTS.md +3 -3
  39. package/examples/hermes-backend/README.md +1 -1
  40. package/examples/hermes-backend/SKILL.md +14 -5
  41. package/examples/hermes-backend/references/operations.md +12 -9
  42. package/examples/openclaw-backend/AGENTS.md +3 -2
  43. package/examples/openclaw-backend/README.md +3 -2
  44. package/examples/openclaw-backend/SKILL.md +31 -9
  45. package/examples/openclaw-backend/references/operations.md +29 -11
  46. package/package.json +1 -1
@@ -1,9 +1,10 @@
1
1
  import type { EventStore } from '../store/EventStore.js';
2
- import type { MemoryAtlasStore } from '../store/MemoryAtlasStore.js';
2
+ import { type MemoryAtlasStore } from '../store/MemoryAtlasStore.js';
3
3
  import type { MemoryAtlasNodeDetail, MemoryAtlasPathResult, MemoryAtlasQueryOptions, MemoryAtlasSlice, MemoryAtlasTimelineResult } from './MemoryAtlasTypes.js';
4
4
  export declare class MemoryAtlasService {
5
5
  private store;
6
6
  private eventStore;
7
+ private readonly facetPlanner;
7
8
  constructor(store: MemoryAtlasStore, eventStore: EventStore);
8
9
  overview(options: MemoryAtlasQueryOptions): MemoryAtlasSlice;
9
10
  search(query: string, options: MemoryAtlasQueryOptions): MemoryAtlasSlice;
@@ -16,7 +17,9 @@ export declare class MemoryAtlasService {
16
17
  maxHops?: number;
17
18
  }): MemoryAtlasPathResult;
18
19
  timeline(query: string, options: MemoryAtlasQueryOptions): MemoryAtlasTimelineResult;
20
+ private searchFacetCardsWithRelaxation;
19
21
  private attachEvidence;
22
+ private attachCardEvidence;
20
23
  private evidence;
21
24
  private edgesFor;
22
25
  private safeEdges;
@@ -1,8 +1,11 @@
1
+ import { MEMORY_ATLAS_PROJECTION_NAME } from '../store/MemoryAtlasStore.js';
1
2
  import { eventTextForMemory } from '../episode/CogmemBlockStripper.js';
3
+ import { FacetQueryPlanner } from './FacetQueryPlanner.js';
2
4
  import { compileAtlasQuery } from './MemoryAtlasQueryCompiler.js';
3
5
  export class MemoryAtlasService {
4
6
  store;
5
7
  eventStore;
8
+ facetPlanner = new FacetQueryPlanner();
6
9
  constructor(store, eventStore) {
7
10
  this.store = store;
8
11
  this.eventStore = eventStore;
@@ -14,13 +17,29 @@ export class MemoryAtlasService {
14
17
  }
15
18
  search(query, options) {
16
19
  const projectId = requiredProject(options.projectId);
17
- const nodes = this.store.search(boundedQuery(query), projectId, boundedLimit(options.limit));
20
+ const limit = boundedLimit(options.limit);
21
+ const facetResult = this.searchFacetCardsWithRelaxation(query, projectId, limit, options);
22
+ const cards = facetResult.cards;
23
+ let nodes = this.store.search(boundedQuery(query), projectId, limit);
24
+ if (cards.length) {
25
+ const cardNodes = cards.map((card) => this.store.getNode(card.canonicalId, projectId)).filter((node) => Boolean(node));
26
+ nodes = uniqueNodes([...cardNodes, ...nodes]).slice(0, limit);
27
+ }
18
28
  const withEvidence = this.attachEvidence(nodes, projectId, options);
19
- return slice(projectId, withEvidence, this.edgesFor(withEvidence, projectId), query);
29
+ const result = slice(projectId, withEvidence, this.edgesFor(withEvidence, projectId), query);
30
+ result.facets = facetsForPlan(facetResult.plan);
31
+ result.matchedFacets = cards.flatMap((card) => card.matchedFacets);
32
+ if (cards.length)
33
+ result.cards = this.attachCardEvidence(cards, projectId, options);
34
+ if (facetResult.relaxationTrace.length)
35
+ result.relaxationTrace = facetResult.relaxationTrace;
36
+ return result;
20
37
  }
21
38
  explore(query, options) {
22
39
  const projectId = requiredProject(options.projectId);
23
40
  const limit = boundedLimit(options.limit);
41
+ const facetResult = this.searchFacetCardsWithRelaxation(query, projectId, limit, options);
42
+ const cards = facetResult.cards;
24
43
  const compiled = compileAtlasQuery(boundedQuery(query), options.now);
25
44
  const target = this.store.resolveTargetNodeIds(projectId, compiled.text);
26
45
  let nodes = this.store.searchFaceted(query, projectId, limit, {
@@ -28,6 +47,10 @@ export class MemoryAtlasService {
28
47
  keywords: target.nodeIds.length ? compiled.keywords : compiled.tokens,
29
48
  targetNodeIds: target.nodeIds.length ? target.nodeIds : undefined,
30
49
  });
50
+ if (cards.length) {
51
+ const cardNodes = cards.map((card) => this.store.getNode(card.canonicalId, projectId)).filter((node) => Boolean(node));
52
+ nodes = uniqueNodes([...cardNodes, ...nodes]).slice(0, limit);
53
+ }
31
54
  if (compiled.actionIntent) {
32
55
  const actions = this.store.listActions(projectId, { target: compiled.target, targetEntityIds: target.entitySourceIds,
33
56
  from: compiled.range?.from, to: compiled.range?.to, limit });
@@ -36,7 +59,15 @@ export class MemoryAtlasService {
36
59
  const nodesWithEvidence = this.attachEvidence(nodes, projectId, options);
37
60
  const edges = this.edgesFor(nodesWithEvidence, projectId);
38
61
  const result = slice(projectId, nodesWithEvidence, edges, query);
39
- result.facets = { time: compiled.range, target: target.labels.join(', ') || compiled.target, memoryKinds: compiled.memoryKinds, keywords: compiled.keywords };
62
+ result.facets = {
63
+ ...facetsForPlan(facetResult.plan),
64
+ legacy: { time: compiled.range, target: target.labels.join(', ') || compiled.target, memoryKinds: compiled.memoryKinds, keywords: compiled.keywords },
65
+ };
66
+ result.matchedFacets = cards.flatMap((card) => card.matchedFacets);
67
+ if (cards.length)
68
+ result.cards = this.attachCardEvidence(cards, projectId, options);
69
+ if (facetResult.relaxationTrace.length)
70
+ result.relaxationTrace = facetResult.relaxationTrace;
40
71
  const hasFacet = Boolean(compiled.range || compiled.target || compiled.memoryKinds.length || compiled.tokens.length);
41
72
  result.coldMemoryResurrected = hasFacet && nodes.some((node) => node.activation <= 0.1);
42
73
  return result;
@@ -57,7 +88,8 @@ export class MemoryAtlasService {
57
88
  if (!Number.isInteger(hops) || hops < 1 || hops > 2)
58
89
  throw new Error('hops must be between 1 and 2');
59
90
  const limit = boundedLimit(options.limit);
60
- const seen = new Set([boundedId(nodeId)]);
91
+ const start = canonicalInputNodeId(this.store, boundedId(nodeId), projectId);
92
+ const seen = new Set([start]);
61
93
  let frontier = [...seen];
62
94
  const selectedEdges = [];
63
95
  for (let depth = 0; depth < hops; depth += 1) {
@@ -80,8 +112,8 @@ export class MemoryAtlasService {
80
112
  path(from, to, options) {
81
113
  const projectId = requiredProject(options.projectId);
82
114
  const maxHops = Math.max(1, Math.min(options.maxHops ?? 6, 6));
83
- const start = boundedId(from);
84
- const target = boundedId(to);
115
+ const start = canonicalInputNodeId(this.store, boundedId(from), projectId);
116
+ const target = canonicalInputNodeId(this.store, boundedId(to), projectId);
85
117
  const parents = new Map();
86
118
  const best = new Map([[start, 0]]);
87
119
  const queue = [{ id: start, cost: 0, hops: 0 }];
@@ -134,19 +166,22 @@ export class MemoryAtlasService {
134
166
  pathEdges.reverse();
135
167
  }
136
168
  const path = found ? pathIds.map((id) => this.store.getNode(id, projectId)).filter((node) => Boolean(node)) : [];
137
- return { version: 'memory_atlas.v1', projectId, from: start, to: target, path,
169
+ return { version: MEMORY_ATLAS_PROJECTION_NAME, projectId, from: start, to: target, path,
138
170
  edges: found ? this.safeEdges(pathEdges, projectId) : [], truncated: expanded.size >= 2000 };
139
171
  }
140
172
  timeline(query, options) {
141
173
  const projectId = requiredProject(options.projectId);
142
174
  const compiled = compileAtlasQuery(boundedQuery(query), options.now);
143
175
  const limit = boundedLimit(options.limit);
176
+ const facetResult = this.searchFacetCardsWithRelaxation(query, projectId, limit, options);
177
+ const cards = this.attachCardEvidence(facetResult.cards, projectId, options)
178
+ .sort((left, right) => dateKey(left) - dateKey(right) || left.displayTitle.localeCompare(right.displayTitle));
144
179
  const target = this.store.resolveTargetNodeIds(projectId, compiled.text);
145
180
  const nodes = this.store.searchFaceted(query, projectId, limit, {
146
181
  from: compiled.range?.from, to: compiled.range?.to, memoryKinds: compiled.memoryKinds,
147
182
  keywords: target.nodeIds.length ? compiled.keywords : compiled.tokens,
148
183
  targetNodeIds: target.nodeIds.length ? target.nodeIds : undefined,
149
- }).sort((left, right) => Number(right.occurredAt || 0) - Number(left.occurredAt || 0)).map((node) => {
184
+ }).sort((left, right) => Number(left.occurredAt || 0) - Number(right.occurredAt || 0)).map((node) => {
150
185
  const evidence = this.evidence(node.id, projectId, options.evidenceLimit, options.includeEvidence);
151
186
  const evidenceTotal = this.store.evidenceTotal(node.id, projectId);
152
187
  return { ...node, evidenceCount: evidenceTotal, evidenceTotal, evidenceReturned: evidence.length, evidence, neighbors: [] };
@@ -154,8 +189,28 @@ export class MemoryAtlasService {
154
189
  const actions = this.store.listActions(projectId, { target: compiled.target, targetEntityIds: target.entitySourceIds,
155
190
  from: compiled.range?.from, to: compiled.range?.to, limit: boundedLimit(options.limit) })
156
191
  .map((action) => ({ ...action, evidence: this.evidence(action.id, projectId, options.evidenceLimit, options.includeEvidence) }));
157
- return { version: 'memory_atlas.v1', projectId, query, range: compiled.range,
158
- temporalResurrection: Boolean(compiled.range && [...nodes, ...actions].length), nodes, actions, warnings: [] };
192
+ return { version: MEMORY_ATLAS_PROJECTION_NAME, projectId, query, range: compiled.range,
193
+ temporalResurrection: Boolean((compiled.range || facetResult.plan.temporalIntent) && [...nodes, ...actions, ...cards].length),
194
+ nodes, actions, cards,
195
+ groupedByIssue: groupCardsByIssue(cards),
196
+ relaxationTrace: facetResult.relaxationTrace.length ? facetResult.relaxationTrace : undefined,
197
+ facets: facetsForPlan(facetResult.plan),
198
+ matchedFacets: cards.flatMap((card) => card.matchedFacets),
199
+ warnings: [] };
200
+ }
201
+ searchFacetCardsWithRelaxation(query, projectId, limit, options) {
202
+ let plan = this.facetPlanner.plan(boundedQuery(query), { projectId, now: options.now });
203
+ const relaxationTrace = [];
204
+ let cards = plan.facets.length ? this.store.searchCanonicalEpisodeCards(projectId, plan, limit) : [];
205
+ for (let attempt = 0; !cards.length && plan.facets.length && attempt < 3; attempt += 1) {
206
+ const relaxed = relaxFacetPlan(plan);
207
+ if (!relaxed)
208
+ break;
209
+ plan = relaxed.plan;
210
+ cards = this.store.searchCanonicalEpisodeCards(projectId, plan, limit);
211
+ relaxationTrace.push(...relaxed.trace);
212
+ }
213
+ return { plan, cards, relaxationTrace };
159
214
  }
160
215
  attachEvidence(nodes, projectId, options) {
161
216
  return nodes.map((node) => {
@@ -164,6 +219,17 @@ export class MemoryAtlasService {
164
219
  return { ...node, evidenceCount: evidenceTotal, evidenceTotal, evidenceReturned: evidence.length, evidence };
165
220
  });
166
221
  }
222
+ attachCardEvidence(cards, projectId, options) {
223
+ return cards.map((card) => {
224
+ const evidence = this.evidence(card.canonicalId, projectId, options.evidenceLimit, options.includeEvidence);
225
+ return {
226
+ ...card,
227
+ sourceLocator: evidence[0]?.sourceLocator ?? card.sourceLocator,
228
+ evidenceTotal: Math.max(card.evidenceTotal, this.store.evidenceTotal(card.canonicalId, projectId)),
229
+ evidenceReturned: evidence.length,
230
+ };
231
+ });
232
+ }
167
233
  evidence(nodeId, projectId, requested, includeExcerpt) {
168
234
  const limit = Math.max(1, Math.min(requested ?? 2, 10));
169
235
  return this.store.evidenceIds(nodeId, projectId, limit).flatMap((eventId) => {
@@ -203,6 +269,17 @@ export class MemoryAtlasService {
203
269
  .flatMap((chunk) => this.store.findEdgesFromNodesToTarget(projectId, chunk, target)));
204
270
  }
205
271
  }
272
+ function canonicalInputNodeId(store, id, projectId) {
273
+ if (id.startsWith(`entity:${projectId}:`))
274
+ return id;
275
+ if (!id.startsWith('entity:'))
276
+ return id;
277
+ const entityId = id.slice('entity:'.length);
278
+ if (!entityId.startsWith('facet:'))
279
+ return id;
280
+ const scoped = `entity:${projectId}:${entityId}`;
281
+ return store.getNode(scoped, projectId) ? scoped : id;
282
+ }
206
283
  function requiredProject(value) { if (!value?.trim())
207
284
  throw new Error('projectId is required for Memory Atlas queries'); return value.trim(); }
208
285
  function boundedLimit(value) { if (value !== undefined && (!Number.isFinite(value) || value < 1))
@@ -229,7 +306,93 @@ function chunked(values, size) {
229
306
  chunks.push(values.slice(index, index + size));
230
307
  return chunks;
231
308
  }
232
- function slice(projectId, nodes, edges, query) { return { version: 'memory_atlas.v1', projectId, query, nodes, edges, nextActions: nodes.slice(0, 5).map((node) => ({ label: `Inspect ${node.label}`, tool: 'cogmem_graph_node', args: { id: node.id, projectId } })), warnings: [] }; }
309
+ function slice(projectId, nodes, edges, query) { return { version: MEMORY_ATLAS_PROJECTION_NAME, projectId, query, nodes, edges, nextActions: nodes.slice(0, 5).map((node) => ({ label: `Inspect ${node.label}`, tool: 'cogmem_graph_node', args: { id: node.id, projectId } })), warnings: [] }; }
310
+ function facetsForPlan(plan) {
311
+ return {
312
+ planner: {
313
+ intent: plan.intent,
314
+ operator: plan.operator,
315
+ temporalIntent: plan.temporalIntent,
316
+ groupBy: plan.groupBy,
317
+ exactness: plan.exactness,
318
+ keywords: plan.keywords,
319
+ facets: plan.facets.map((facet) => ({
320
+ type: facet.type,
321
+ value: facet.value,
322
+ label: facet.label,
323
+ nodeId: facet.nodeId || `${facet.type}:${facet.value}`,
324
+ relation: facet.relation || 'MATCHES',
325
+ })),
326
+ },
327
+ };
328
+ }
329
+ function dateKey(card) {
330
+ if (card.localDate && /^\d{4}-\d{2}-\d{2}$/u.test(card.localDate)) {
331
+ return Date.parse(`${card.localDate}T00:00:00.000Z`);
332
+ }
333
+ return Number.POSITIVE_INFINITY;
334
+ }
335
+ function groupCardsByIssue(cards) {
336
+ const groups = new Map();
337
+ for (const card of cards) {
338
+ const key = card.issueType || 'unclassified';
339
+ groups.set(key, [...(groups.get(key) || []), card]);
340
+ }
341
+ return Array.from(groups.entries()).map(([issueType, group]) => ({ issueType, cards: group }));
342
+ }
343
+ function relaxFacetPlan(plan) {
344
+ const dayFacet = plan.facets.find((facet) => facet.type === 'time' && facet.granularity === 'day');
345
+ if (dayFacet) {
346
+ const monthValue = dayFacet.value.slice(0, 7);
347
+ return {
348
+ plan: replaceFacet(plan, dayFacet, {
349
+ ...dayFacet,
350
+ value: monthValue,
351
+ label: monthValue,
352
+ nodeId: dayFacet.nodeId?.replace(dayFacet.value, monthValue),
353
+ relation: 'OCCURRED_IN',
354
+ granularity: 'month',
355
+ }),
356
+ trace: [{ from: dayFacet.value, to: monthValue, reason: 'exact day facet had no canonical episode match; relaxed to parent month' }],
357
+ };
358
+ }
359
+ const monthFacet = plan.facets.find((facet) => facet.type === 'time' && facet.granularity === 'month');
360
+ if (monthFacet) {
361
+ const yearValue = monthFacet.value.slice(0, 4);
362
+ return {
363
+ plan: replaceFacet(plan, monthFacet, {
364
+ ...monthFacet,
365
+ value: yearValue,
366
+ label: yearValue,
367
+ nodeId: monthFacet.nodeId?.replace(monthFacet.value, yearValue),
368
+ relation: 'OCCURRED_IN',
369
+ granularity: 'year',
370
+ }),
371
+ trace: [{ from: monthFacet.value, to: yearValue, reason: 'exact month facet had no canonical episode match; relaxed to parent year' }],
372
+ };
373
+ }
374
+ const issueFacet = plan.facets.find((facet) => facet.type === 'issue');
375
+ const topicFacet = plan.facets.find((facet) => facet.type === 'topic');
376
+ if (issueFacet && topicFacet) {
377
+ return {
378
+ plan: {
379
+ ...plan,
380
+ exactness: 'relaxed',
381
+ requiresIntersection: plan.facets.length - 1 > 1,
382
+ facets: plan.facets.filter((facet) => facet !== issueFacet),
383
+ },
384
+ trace: [{ from: issueFacet.value, to: topicFacet.value, reason: 'issue facet had no canonical episode match; relaxed to parent topic' }],
385
+ };
386
+ }
387
+ return null;
388
+ }
389
+ function replaceFacet(plan, from, to) {
390
+ return {
391
+ ...plan,
392
+ exactness: 'relaxed',
393
+ facets: plan.facets.map((facet) => facet === from ? to : facet),
394
+ };
395
+ }
233
396
  function atlasSourceLocator(event, projectId) {
234
397
  const project = projectId || event.projectId;
235
398
  const projectArg = project ? ` --project ${cliArg(project)}` : '';
@@ -1,4 +1,4 @@
1
- export type MemoryAtlasNodeType = 'project' | 'topic' | 'entity' | 'cluster' | 'episode' | 'belief' | 'action' | 'time' | 'event';
1
+ export type MemoryAtlasNodeType = 'project' | 'topic' | 'issue' | 'entity' | 'session' | 'thread' | 'memoryKind' | 'actionKind' | 'cluster' | 'episode' | 'raw_event' | 'belief' | 'action' | 'time' | 'event' | 'decision' | 'correction';
2
2
  export interface MemoryAtlasEvidence {
3
3
  eventId: string;
4
4
  globalSeq?: number;
@@ -39,6 +39,48 @@ export interface MemoryAtlasNode {
39
39
  /** Bounded first-hop raw evidence locators for agent-facing graph search/explore responses. */
40
40
  evidence?: MemoryAtlasEvidence[];
41
41
  }
42
+ export interface MemoryAtlasMatchedFacet {
43
+ type: 'time' | 'topic' | 'issue' | 'entity' | 'session' | 'thread' | 'memoryKind' | 'actionKind';
44
+ value: string;
45
+ label: string;
46
+ nodeId: string;
47
+ relation: string;
48
+ }
49
+ export interface MemoryAtlasMatchedPath {
50
+ facet: MemoryAtlasMatchedFacet;
51
+ via: string[];
52
+ relation: string;
53
+ confidence: number;
54
+ }
55
+ export interface MemoryAtlasRelatedCard {
56
+ canonicalId: string;
57
+ displayTitle: string;
58
+ reason: string;
59
+ matchedFacets?: MemoryAtlasMatchedFacet[];
60
+ }
61
+ export interface MemoryAtlasCard {
62
+ canonicalId: string;
63
+ nodeType: MemoryAtlasNodeType;
64
+ displayTitle: string;
65
+ oneLineSummary?: string;
66
+ matchedFacets: MemoryAtlasMatchedFacet[];
67
+ matchedPaths: MemoryAtlasMatchedPath[];
68
+ parentTopics: string[];
69
+ issueType?: string;
70
+ eventKind?: string;
71
+ localDate?: string;
72
+ whyMatched: string;
73
+ relatedButNotSelected: MemoryAtlasRelatedCard[];
74
+ sourceLocator?: MemoryAtlasEvidence['sourceLocator'];
75
+ evidenceEventIds: string[];
76
+ evidenceTotal: number;
77
+ evidenceReturned: number;
78
+ }
79
+ export interface MemoryAtlasRelaxationStep {
80
+ from: string;
81
+ to: string;
82
+ reason: string;
83
+ }
42
84
  export interface MemoryAtlasEdge {
43
85
  source: string;
44
86
  relation: string;
@@ -52,7 +94,7 @@ export interface MemoryAtlasNextAction {
52
94
  args: Record<string, unknown>;
53
95
  }
54
96
  export interface MemoryAtlasSlice {
55
- version: 'memory_atlas.v1';
97
+ version: 'memory_atlas.v2';
56
98
  projectId: string;
57
99
  query?: string;
58
100
  nodes: MemoryAtlasNode[];
@@ -60,15 +102,29 @@ export interface MemoryAtlasSlice {
60
102
  nextActions: MemoryAtlasNextAction[];
61
103
  warnings: string[];
62
104
  facets?: {
63
- time?: {
64
- from: number;
65
- to: number;
66
- label: string;
105
+ planner?: {
106
+ intent: string;
107
+ operator: string;
108
+ temporalIntent?: string;
109
+ groupBy?: string;
110
+ exactness: string;
111
+ facets: MemoryAtlasMatchedFacet[];
112
+ keywords: string[];
113
+ };
114
+ legacy?: {
115
+ time?: {
116
+ from: number;
117
+ to: number;
118
+ label: string;
119
+ };
120
+ target?: string;
121
+ memoryKinds: string[];
122
+ keywords: string[];
67
123
  };
68
- target?: string;
69
- memoryKinds: string[];
70
- keywords: string[];
71
124
  };
125
+ matchedFacets?: MemoryAtlasMatchedFacet[];
126
+ cards?: MemoryAtlasCard[];
127
+ relaxationTrace?: MemoryAtlasRelaxationStep[];
72
128
  coldMemoryResurrected?: boolean;
73
129
  }
74
130
  export interface MemoryAtlasNodeDetail extends MemoryAtlasNode {
@@ -87,7 +143,7 @@ export interface MemoryAtlasAction {
87
143
  evidence: MemoryAtlasEvidence[];
88
144
  }
89
145
  export interface MemoryAtlasTimelineResult {
90
- version: 'memory_atlas.v1';
146
+ version: 'memory_atlas.v2';
91
147
  projectId: string;
92
148
  query: string;
93
149
  range?: {
@@ -98,10 +154,18 @@ export interface MemoryAtlasTimelineResult {
98
154
  temporalResurrection: boolean;
99
155
  nodes: MemoryAtlasNodeDetail[];
100
156
  actions: MemoryAtlasAction[];
157
+ cards?: MemoryAtlasCard[];
158
+ groupedByIssue?: Array<{
159
+ issueType: string;
160
+ cards: MemoryAtlasCard[];
161
+ }>;
162
+ relaxationTrace?: MemoryAtlasRelaxationStep[];
163
+ facets?: MemoryAtlasSlice['facets'];
164
+ matchedFacets?: MemoryAtlasMatchedFacet[];
101
165
  warnings: string[];
102
166
  }
103
167
  export interface MemoryAtlasPathResult {
104
- version: 'memory_atlas.v1';
168
+ version: 'memory_atlas.v2';
105
169
  projectId: string;
106
170
  from: string;
107
171
  to: string;
@@ -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);
@@ -573,6 +576,9 @@ function runRecall(kernel, args) {
573
576
  decisionTrace: result.decisionTrace,
574
577
  strategyCapsule,
575
578
  narrative: result.narrative,
579
+ atlasCards: result.atlasCards,
580
+ relatedButNotSelected: result.relatedButNotSelected,
581
+ relaxationTrace: result.relaxationTrace,
576
582
  items: result.items,
577
583
  };
578
584
  }
@@ -600,6 +606,13 @@ function runGraphCommand(kernel, args) {
600
606
  evidenceLimit: args.evidenceLimit, now: args.now,
601
607
  refresh: args.refresh ? true : (args.staleOk ? false : undefined),
602
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
+ }
603
616
  if (args.command === 'graph')
604
617
  return kernel.graphOverview(options);
605
618
  if (args.command === 'graph-search') {
@@ -881,7 +894,7 @@ function printHuman(command, payload) {
881
894
  return;
882
895
  }
883
896
  if (command === 'graph' || command.startsWith('graph-')) {
884
- console.log(`memoryAtlas: ${payload.version || 'memory_atlas.v1'} project=${payload.projectId || 'unknown'}`);
897
+ console.log(`memoryAtlas: ${payload.version || 'memory_atlas.v2'} project=${payload.projectId || 'unknown'}`);
885
898
  const rows = Array.isArray(payload.nodes) ? payload.nodes
886
899
  : Array.isArray(payload.path) ? payload.path
887
900
  : Array.isArray(payload.actions) ? payload.actions
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.6.5';
86
+ const CORE_VERSION = '3.7.1';
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() === '')