cogmem 3.6.4 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/MEMORY_ATLAS.md +47 -10
  3. package/README.md +36 -13
  4. package/RELEASE_CHECKLIST.md +7 -4
  5. package/dist/agent/AgentMemoryBackend.d.ts +19 -2
  6. package/dist/agent/AgentMemoryBackend.js +134 -3
  7. package/dist/agent/AgentRecallQueryCompiler.d.ts +1 -1
  8. package/dist/agent/AgentRecallQueryCompiler.js +12 -0
  9. package/dist/atlas/EpisodeTitleGenerator.d.ts +31 -0
  10. package/dist/atlas/EpisodeTitleGenerator.js +152 -0
  11. package/dist/atlas/FacetQueryPlanner.d.ts +31 -0
  12. package/dist/atlas/FacetQueryPlanner.js +188 -0
  13. package/dist/atlas/GraphCurator.d.ts +27 -0
  14. package/dist/atlas/GraphCurator.js +397 -0
  15. package/dist/atlas/MemoryAtlasIndexer.d.ts +3 -0
  16. package/dist/atlas/MemoryAtlasIndexer.js +19 -6
  17. package/dist/atlas/MemoryAtlasService.d.ts +5 -1
  18. package/dist/atlas/MemoryAtlasService.js +165 -12
  19. package/dist/atlas/MemoryAtlasTypes.d.ts +89 -11
  20. package/dist/bin/connect.js +85 -13
  21. package/dist/bin/memory.js +221 -18
  22. package/dist/factory.d.ts +2 -0
  23. package/dist/factory.js +36 -18
  24. package/dist/host/openclaw/AutoMemoryPluginInstaller.js +83 -10
  25. package/dist/mcp/server.js +1 -1
  26. package/dist/store/EventStore.d.ts +6 -0
  27. package/dist/store/EventStore.js +28 -2
  28. package/dist/store/MemoryAtlasStore.d.ts +10 -1
  29. package/dist/store/MemoryAtlasStore.js +228 -14
  30. package/dist/types/index.d.ts +3 -0
  31. package/examples/hermes-backend/AGENTS.md +11 -5
  32. package/examples/hermes-backend/README.md +5 -3
  33. package/examples/hermes-backend/SKILL.md +28 -5
  34. package/examples/hermes-backend/references/operations.md +16 -6
  35. package/examples/openclaw-backend/AGENTS.md +9 -3
  36. package/examples/openclaw-backend/README.md +9 -3
  37. package/examples/openclaw-backend/SKILL.md +23 -10
  38. package/examples/openclaw-backend/references/operations.md +21 -8
  39. package/package.json +1 -1
@@ -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,12 +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));
18
- return slice(projectId, nodes, this.edgesFor(nodes, projectId), query);
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
+ }
28
+ const withEvidence = this.attachEvidence(nodes, projectId, options);
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;
19
37
  }
20
38
  explore(query, options) {
21
39
  const projectId = requiredProject(options.projectId);
22
40
  const limit = boundedLimit(options.limit);
41
+ const facetResult = this.searchFacetCardsWithRelaxation(query, projectId, limit, options);
42
+ const cards = facetResult.cards;
23
43
  const compiled = compileAtlasQuery(boundedQuery(query), options.now);
24
44
  const target = this.store.resolveTargetNodeIds(projectId, compiled.text);
25
45
  let nodes = this.store.searchFaceted(query, projectId, limit, {
@@ -27,14 +47,27 @@ export class MemoryAtlasService {
27
47
  keywords: target.nodeIds.length ? compiled.keywords : compiled.tokens,
28
48
  targetNodeIds: target.nodeIds.length ? target.nodeIds : undefined,
29
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
+ }
30
54
  if (compiled.actionIntent) {
31
55
  const actions = this.store.listActions(projectId, { target: compiled.target, targetEntityIds: target.entitySourceIds,
32
56
  from: compiled.range?.from, to: compiled.range?.to, limit });
33
57
  nodes = uniqueNodes([...actions.map((action) => this.store.getNode(action.id, projectId)).filter((node) => Boolean(node)), ...nodes]).slice(0, limit);
34
58
  }
35
- const edges = this.edgesFor(nodes, projectId);
36
- const result = slice(projectId, nodes, edges, query);
37
- result.facets = { time: compiled.range, target: target.labels.join(', ') || compiled.target, memoryKinds: compiled.memoryKinds, keywords: compiled.keywords };
59
+ const nodesWithEvidence = this.attachEvidence(nodes, projectId, options);
60
+ const edges = this.edgesFor(nodesWithEvidence, projectId);
61
+ const result = slice(projectId, nodesWithEvidence, edges, query);
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;
38
71
  const hasFacet = Boolean(compiled.range || compiled.target || compiled.memoryKinds.length || compiled.tokens.length);
39
72
  result.coldMemoryResurrected = hasFacet && nodes.some((node) => node.activation <= 0.1);
40
73
  return result;
@@ -132,19 +165,22 @@ export class MemoryAtlasService {
132
165
  pathEdges.reverse();
133
166
  }
134
167
  const path = found ? pathIds.map((id) => this.store.getNode(id, projectId)).filter((node) => Boolean(node)) : [];
135
- return { version: 'memory_atlas.v1', projectId, from: start, to: target, path,
168
+ return { version: MEMORY_ATLAS_PROJECTION_NAME, projectId, from: start, to: target, path,
136
169
  edges: found ? this.safeEdges(pathEdges, projectId) : [], truncated: expanded.size >= 2000 };
137
170
  }
138
171
  timeline(query, options) {
139
172
  const projectId = requiredProject(options.projectId);
140
173
  const compiled = compileAtlasQuery(boundedQuery(query), options.now);
141
174
  const limit = boundedLimit(options.limit);
175
+ const facetResult = this.searchFacetCardsWithRelaxation(query, projectId, limit, options);
176
+ const cards = this.attachCardEvidence(facetResult.cards, projectId, options)
177
+ .sort((left, right) => dateKey(left) - dateKey(right) || left.displayTitle.localeCompare(right.displayTitle));
142
178
  const target = this.store.resolveTargetNodeIds(projectId, compiled.text);
143
179
  const nodes = this.store.searchFaceted(query, projectId, limit, {
144
180
  from: compiled.range?.from, to: compiled.range?.to, memoryKinds: compiled.memoryKinds,
145
181
  keywords: target.nodeIds.length ? compiled.keywords : compiled.tokens,
146
182
  targetNodeIds: target.nodeIds.length ? target.nodeIds : undefined,
147
- }).sort((left, right) => Number(right.occurredAt || 0) - Number(left.occurredAt || 0)).map((node) => {
183
+ }).sort((left, right) => Number(left.occurredAt || 0) - Number(right.occurredAt || 0)).map((node) => {
148
184
  const evidence = this.evidence(node.id, projectId, options.evidenceLimit, options.includeEvidence);
149
185
  const evidenceTotal = this.store.evidenceTotal(node.id, projectId);
150
186
  return { ...node, evidenceCount: evidenceTotal, evidenceTotal, evidenceReturned: evidence.length, evidence, neighbors: [] };
@@ -152,8 +188,46 @@ export class MemoryAtlasService {
152
188
  const actions = this.store.listActions(projectId, { target: compiled.target, targetEntityIds: target.entitySourceIds,
153
189
  from: compiled.range?.from, to: compiled.range?.to, limit: boundedLimit(options.limit) })
154
190
  .map((action) => ({ ...action, evidence: this.evidence(action.id, projectId, options.evidenceLimit, options.includeEvidence) }));
155
- return { version: 'memory_atlas.v1', projectId, query, range: compiled.range,
156
- temporalResurrection: Boolean(compiled.range && [...nodes, ...actions].length), nodes, actions, warnings: [] };
191
+ return { version: MEMORY_ATLAS_PROJECTION_NAME, projectId, query, range: compiled.range,
192
+ temporalResurrection: Boolean((compiled.range || facetResult.plan.temporalIntent) && [...nodes, ...actions, ...cards].length),
193
+ nodes, actions, cards,
194
+ groupedByIssue: groupCardsByIssue(cards),
195
+ relaxationTrace: facetResult.relaxationTrace.length ? facetResult.relaxationTrace : undefined,
196
+ facets: facetsForPlan(facetResult.plan),
197
+ matchedFacets: cards.flatMap((card) => card.matchedFacets),
198
+ warnings: [] };
199
+ }
200
+ searchFacetCardsWithRelaxation(query, projectId, limit, options) {
201
+ let plan = this.facetPlanner.plan(boundedQuery(query), { projectId, now: options.now });
202
+ const relaxationTrace = [];
203
+ let cards = plan.facets.length ? this.store.searchCanonicalEpisodeCards(projectId, plan, limit) : [];
204
+ if (!cards.length && plan.facets.length) {
205
+ const relaxed = relaxFacetPlan(plan);
206
+ if (relaxed) {
207
+ plan = relaxed.plan;
208
+ cards = this.store.searchCanonicalEpisodeCards(projectId, relaxed.plan, limit);
209
+ relaxationTrace.push(...relaxed.trace);
210
+ }
211
+ }
212
+ return { plan, cards, relaxationTrace };
213
+ }
214
+ attachEvidence(nodes, projectId, options) {
215
+ return nodes.map((node) => {
216
+ const evidence = this.evidence(node.id, projectId, options.evidenceLimit, options.includeEvidence);
217
+ const evidenceTotal = this.store.evidenceTotal(node.id, projectId);
218
+ return { ...node, evidenceCount: evidenceTotal, evidenceTotal, evidenceReturned: evidence.length, evidence };
219
+ });
220
+ }
221
+ attachCardEvidence(cards, projectId, options) {
222
+ return cards.map((card) => {
223
+ const evidence = this.evidence(card.canonicalId, projectId, options.evidenceLimit, options.includeEvidence);
224
+ return {
225
+ ...card,
226
+ sourceLocator: evidence[0]?.sourceLocator ?? card.sourceLocator,
227
+ evidenceTotal: Math.max(card.evidenceTotal, this.store.evidenceTotal(card.canonicalId, projectId)),
228
+ evidenceReturned: evidence.length,
229
+ };
230
+ });
157
231
  }
158
232
  evidence(nodeId, projectId, requested, includeExcerpt) {
159
233
  const limit = Math.max(1, Math.min(requested ?? 2, 10));
@@ -161,8 +235,15 @@ export class MemoryAtlasService {
161
235
  const event = this.eventStore.getEvent(eventId);
162
236
  if (!event || event.projectId !== projectId)
163
237
  return [];
164
- return [{ eventId, drilldown: `cogmem memory show --event ${eventId} --project ${projectId} --json`,
165
- excerpt: includeExcerpt ? eventTextForMemory(event).slice(0, 500) : undefined }];
238
+ const sourceLocator = atlasSourceLocator(event, projectId);
239
+ return [{
240
+ eventId,
241
+ globalSeq: event.globalSeq,
242
+ projectId,
243
+ drilldown: sourceLocator.command,
244
+ sourceLocator,
245
+ excerpt: includeExcerpt ? eventTextForMemory(event).slice(0, 500) : undefined,
246
+ }];
166
247
  });
167
248
  }
168
249
  edgesFor(nodes, projectId) {
@@ -213,4 +294,76 @@ function chunked(values, size) {
213
294
  chunks.push(values.slice(index, index + size));
214
295
  return chunks;
215
296
  }
216
- 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: [] }; }
297
+ 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: [] }; }
298
+ function facetsForPlan(plan) {
299
+ return {
300
+ planner: {
301
+ intent: plan.intent,
302
+ operator: plan.operator,
303
+ temporalIntent: plan.temporalIntent,
304
+ groupBy: plan.groupBy,
305
+ exactness: plan.exactness,
306
+ keywords: plan.keywords,
307
+ facets: plan.facets.map((facet) => ({
308
+ type: facet.type,
309
+ value: facet.value,
310
+ label: facet.label,
311
+ nodeId: facet.nodeId || `${facet.type}:${facet.value}`,
312
+ relation: facet.relation || 'MATCHES',
313
+ })),
314
+ },
315
+ };
316
+ }
317
+ function dateKey(card) {
318
+ if (card.localDate && /^\d{4}-\d{2}-\d{2}$/u.test(card.localDate)) {
319
+ return Date.parse(`${card.localDate}T00:00:00.000Z`);
320
+ }
321
+ return Number.POSITIVE_INFINITY;
322
+ }
323
+ function groupCardsByIssue(cards) {
324
+ const groups = new Map();
325
+ for (const card of cards) {
326
+ const key = card.issueType || 'unclassified';
327
+ groups.set(key, [...(groups.get(key) || []), card]);
328
+ }
329
+ return Array.from(groups.entries()).map(([issueType, group]) => ({ issueType, cards: group }));
330
+ }
331
+ function relaxFacetPlan(plan) {
332
+ const dayFacet = plan.facets.find((facet) => facet.type === 'time' && facet.granularity === 'day');
333
+ if (!dayFacet)
334
+ return null;
335
+ const monthValue = dayFacet.value.slice(0, 7);
336
+ return {
337
+ plan: {
338
+ ...plan,
339
+ exactness: 'relaxed',
340
+ facets: plan.facets.map((facet) => facet === dayFacet ? {
341
+ ...facet,
342
+ value: monthValue,
343
+ label: monthValue,
344
+ nodeId: facet.nodeId?.replace(dayFacet.value, monthValue),
345
+ relation: 'OCCURRED_IN',
346
+ granularity: 'month',
347
+ } : facet),
348
+ },
349
+ trace: [{ from: dayFacet.value, to: monthValue, reason: 'exact day facet had no canonical episode match; relaxed to parent month' }],
350
+ };
351
+ }
352
+ function atlasSourceLocator(event, projectId) {
353
+ const project = projectId || event.projectId;
354
+ const projectArg = project ? ` --project ${cliArg(project)}` : '';
355
+ const base = `cogmem memory show --event ${cliArg(event.eventId)}${projectArg}`;
356
+ return {
357
+ eventId: event.eventId,
358
+ globalSeq: event.globalSeq,
359
+ projectId: project,
360
+ threadId: event.threadId,
361
+ sessionId: event.sessionId,
362
+ localDate: event.localDate,
363
+ command: `${base} --before 2 --after 2 --json`,
364
+ contextCommand: `${base} --before 3 --after 3 --json`,
365
+ };
366
+ }
367
+ function cliArg(value) {
368
+ return /^[A-Za-z0-9._:/=@+-]+$/u.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
369
+ }
@@ -1,7 +1,19 @@
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
+ globalSeq?: number;
5
+ projectId?: string;
4
6
  drilldown: string;
7
+ sourceLocator?: {
8
+ eventId: string;
9
+ globalSeq?: number;
10
+ projectId?: string;
11
+ threadId?: string;
12
+ sessionId?: string;
13
+ localDate?: string;
14
+ command: string;
15
+ contextCommand: string;
16
+ };
5
17
  excerpt?: string;
6
18
  }
7
19
  export interface MemoryAtlasNode {
@@ -24,6 +36,50 @@ export interface MemoryAtlasNode {
24
36
  evidenceTotal: number;
25
37
  /** Number of evidence records returned in this response. */
26
38
  evidenceReturned?: number;
39
+ /** Bounded first-hop raw evidence locators for agent-facing graph search/explore responses. */
40
+ evidence?: MemoryAtlasEvidence[];
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;
27
83
  }
28
84
  export interface MemoryAtlasEdge {
29
85
  source: string;
@@ -38,7 +94,7 @@ export interface MemoryAtlasNextAction {
38
94
  args: Record<string, unknown>;
39
95
  }
40
96
  export interface MemoryAtlasSlice {
41
- version: 'memory_atlas.v1';
97
+ version: 'memory_atlas.v2';
42
98
  projectId: string;
43
99
  query?: string;
44
100
  nodes: MemoryAtlasNode[];
@@ -46,15 +102,29 @@ export interface MemoryAtlasSlice {
46
102
  nextActions: MemoryAtlasNextAction[];
47
103
  warnings: string[];
48
104
  facets?: {
49
- time?: {
50
- from: number;
51
- to: number;
52
- 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[];
53
123
  };
54
- target?: string;
55
- memoryKinds: string[];
56
- keywords: string[];
57
124
  };
125
+ matchedFacets?: MemoryAtlasMatchedFacet[];
126
+ cards?: MemoryAtlasCard[];
127
+ relaxationTrace?: MemoryAtlasRelaxationStep[];
58
128
  coldMemoryResurrected?: boolean;
59
129
  }
60
130
  export interface MemoryAtlasNodeDetail extends MemoryAtlasNode {
@@ -73,7 +143,7 @@ export interface MemoryAtlasAction {
73
143
  evidence: MemoryAtlasEvidence[];
74
144
  }
75
145
  export interface MemoryAtlasTimelineResult {
76
- version: 'memory_atlas.v1';
146
+ version: 'memory_atlas.v2';
77
147
  projectId: string;
78
148
  query: string;
79
149
  range?: {
@@ -84,10 +154,18 @@ export interface MemoryAtlasTimelineResult {
84
154
  temporalResurrection: boolean;
85
155
  nodes: MemoryAtlasNodeDetail[];
86
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[];
87
165
  warnings: string[];
88
166
  }
89
167
  export interface MemoryAtlasPathResult {
90
- version: 'memory_atlas.v1';
168
+ version: 'memory_atlas.v2';
91
169
  projectId: string;
92
170
  from: string;
93
171
  to: string;
@@ -90,21 +90,92 @@ function defaultSkillPath(agent, workspaceRoot) {
90
90
  return join(process.env.HOME || homedir(), '.hermes', 'skills', 'cogmem-memory', 'SKILL.md');
91
91
  }
92
92
  function nextCommands(agent) {
93
+ return nextSteps(agent)
94
+ .filter((step) => step.safeForAutomation && step.actor === 'agent')
95
+ .map((step) => step.command);
96
+ }
97
+ function nextSteps(agent) {
98
+ const project = agent === 'openclaw' ? 'openclaw' : 'hermes';
99
+ const importCommand = agent === 'openclaw' ? 'import-openclaw' : 'import-hermes';
100
+ const steps = [
101
+ {
102
+ id: 'doctor',
103
+ actor: 'agent',
104
+ safeForAutomation: true,
105
+ command: './node_modules/.bin/cogmem doctor',
106
+ when: 'after installation or update',
107
+ purpose: 'verify package, config, and database access',
108
+ },
109
+ {
110
+ id: 'import_dry_run',
111
+ actor: 'agent',
112
+ safeForAutomation: true,
113
+ command: `./node_modules/.bin/cogmem ${importCommand} --workspace . --project ${project} --dry-run --json`,
114
+ when: 'before importing legacy memory',
115
+ purpose: 'preview source discovery and import counts',
116
+ },
117
+ {
118
+ id: 'import_apply',
119
+ actor: 'agent',
120
+ safeForAutomation: true,
121
+ command: `./node_modules/.bin/cogmem ${importCommand} --workspace . --project ${project} --json`,
122
+ when: 'after dry-run looks correct',
123
+ purpose: 'idempotently import legacy memory into Cogmem',
124
+ },
125
+ {
126
+ id: 'memory_plan',
127
+ actor: 'agent',
128
+ safeForAutomation: true,
129
+ command: `./node_modules/.bin/cogmem memory plan --project ${project} --json`,
130
+ when: 'after import, update, or user asks for governance progress',
131
+ purpose: 'read agent-safe next actions instead of guessing from raw counters',
132
+ },
133
+ {
134
+ id: 'memory_status',
135
+ actor: 'agent',
136
+ safeForAutomation: true,
137
+ command: `./node_modules/.bin/cogmem memory status --project ${project} --json`,
138
+ when: 'for read-only health checks',
139
+ purpose: 'inspect raw ledger, vector fallback, dream backlog, and queue summary',
140
+ },
141
+ {
142
+ id: 'interactive_init',
143
+ actor: 'operator',
144
+ safeForAutomation: false,
145
+ command: `./node_modules/.bin/cogmem init --agent ${agent} --scope project`,
146
+ when: 'only if no Cogmem config exists and a human operator is present',
147
+ purpose: 'interactive first-time config wizard; agents must not run this unattended',
148
+ },
149
+ ];
93
150
  if (agent === 'openclaw') {
94
- return [
95
- './node_modules/.bin/cogmem doctor',
96
- './node_modules/.bin/cogmem openclaw diagnose --workspace . --json',
97
- './node_modules/.bin/cogmem import-openclaw --workspace . --project openclaw --dry-run',
98
- './node_modules/.bin/cogmem import-openclaw --workspace . --project openclaw',
99
- './node_modules/.bin/cogmem memory status --project openclaw --json',
100
- ];
151
+ steps.splice(1, 0, {
152
+ id: 'openclaw_diagnose',
153
+ actor: 'agent',
154
+ safeForAutomation: true,
155
+ command: './node_modules/.bin/cogmem openclaw diagnose --workspace . --json',
156
+ when: 'after connect --auto --force or before investigating injection failures',
157
+ purpose: 'verify generated plugin files, audit log, queue lock, and hook diagnostics',
158
+ });
159
+ steps.push({
160
+ id: 'restart_gateway',
161
+ actor: 'host',
162
+ safeForAutomation: false,
163
+ command: 'openclaw gateway restart',
164
+ when: 'after plugin files or OpenClaw config changed',
165
+ purpose: 'reload OpenClaw hooks so the refreshed Cogmem plugin is active',
166
+ });
101
167
  }
102
- return [
103
- './node_modules/.bin/cogmem doctor',
104
- './node_modules/.bin/cogmem import-hermes --workspace . --project hermes --dry-run',
105
- './node_modules/.bin/cogmem import-hermes --workspace . --project hermes',
106
- './node_modules/.bin/cogmem memory status --project hermes --json',
107
- ];
168
+ else {
169
+ steps.push({
170
+ id: 'reload_hermes',
171
+ actor: 'host',
172
+ safeForAutomation: false,
173
+ command: 'restart or reload Hermes MCP server',
174
+ when: 'after Hermes MCP config changed',
175
+ purpose: 'reload the cogmem MCP server entry',
176
+ });
177
+ }
178
+ return steps;
108
179
  }
109
180
  function usage() {
110
181
  return [
@@ -238,6 +309,7 @@ function installSkill(args) {
238
309
  installed: !args.dryRun && !alreadyCurrent,
239
310
  alreadyCurrent,
240
311
  nextCommands: nextCommands(args.agent),
312
+ nextSteps: nextSteps(args.agent),
241
313
  hostConfigSnippet: hostConfigSnippet(args.agent, args.workspaceRoot, args.auto),
242
314
  autoMemory,
243
315
  hermesMcp,