cogmem 3.6.4 → 3.6.5

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.
@@ -55,6 +55,8 @@ function readArgs(argv) {
55
55
  before: numberArg(values, 'before'),
56
56
  after: numberArg(values, 'after'),
57
57
  sinceGlobalSeq: numberArg(values, 'since') ?? numberArg(values, 'since-global-seq'),
58
+ untilGlobalSeq: numberArg(values, 'until') ?? numberArg(values, 'until-global-seq'),
59
+ order: orderArg(values, 'order'),
58
60
  intervalMs: numberArg(values, 'interval-ms'),
59
61
  maxRuns: numberArg(values, 'max-runs'),
60
62
  promoteLimit: numberArg(values, 'promote-limit'),
@@ -75,6 +77,7 @@ function usage() {
75
77
  '',
76
78
  'Commands:',
77
79
  ' status summarize raw ledger, vector, and dream backlog state',
80
+ ' plan summarize status as agent-safe next actions',
78
81
  ' list list raw ledger events with source anchors',
79
82
  ' search --query <q> search raw ledger text without requiring hot vectors',
80
83
  ' recall --query <q> run agent-facing governed recall with source context',
@@ -101,7 +104,9 @@ function usage() {
101
104
  ' --thread <id> scope to one thread',
102
105
  ' --session <id> scope to one session',
103
106
  ' --limit <n> result limit, default 20',
104
- ' --since <globalSeq> for bind, scan raw events at or after a global sequence',
107
+ ' --since <globalSeq> list/bind events at or after a global sequence',
108
+ ' --until <globalSeq> list events at or before a global sequence',
109
+ ' --order <asc|desc> list raw ledger order, default desc',
105
110
  ' --status <status> candidate queue status, default candidate',
106
111
  ' --id <candidate> candidate id for review',
107
112
  ' --action <action> review action: approve, reject, defer, supersede, or relink',
@@ -117,7 +122,7 @@ function usage() {
117
122
  ' --interval-ms <n> watch sleep interval, default 300000',
118
123
  ' --max-runs <n> stop watch after n iterations; omit for long-running worker',
119
124
  ' --agent <id> agent id for governed recall, default openclaw',
120
- ' --intent <intent> memory_recall, previous_session_summary, or forensic_quote',
125
+ ' --intent <intent> memory_recall, previous_session_summary, forensic_quote, or historical_discussion',
121
126
  ' --db <memory.db> open an explicit database path',
122
127
  ' --config <toml> open a cogmem TOML config',
123
128
  ' --include-evidence include bounded raw excerpts; event ids are always returned',
@@ -135,6 +140,7 @@ function usage() {
135
140
  }
136
141
  function isMemoryCommand(value) {
137
142
  return value === 'status'
143
+ || value === 'plan'
138
144
  || value === 'list'
139
145
  || value === 'search'
140
146
  || value === 'recall'
@@ -166,9 +172,17 @@ function recallIntentArg(values, key) {
166
172
  const raw = stringArg(values, key);
167
173
  if (!raw)
168
174
  return undefined;
169
- if (raw === 'memory_recall' || raw === 'previous_session_summary' || raw === 'forensic_quote')
175
+ if (raw === 'memory_recall' || raw === 'previous_session_summary' || raw === 'forensic_quote' || raw === 'historical_discussion')
170
176
  return raw;
171
- throw new Error(`--${key} must be one of memory_recall, previous_session_summary, forensic_quote`);
177
+ throw new Error(`--${key} must be one of memory_recall, previous_session_summary, forensic_quote, historical_discussion`);
178
+ }
179
+ function orderArg(values, key) {
180
+ const raw = stringArg(values, key);
181
+ if (!raw)
182
+ return undefined;
183
+ if (raw === 'asc' || raw === 'desc')
184
+ return raw;
185
+ throw new Error(`--${key} must be one of asc, desc`);
172
186
  }
173
187
  function stringArg(values, key) {
174
188
  const value = values[key];
@@ -219,30 +233,32 @@ function inspectionDbPath(args) {
219
233
  function runReadOnlyInspection(args) {
220
234
  const inspection = new MemoryInspectionStore(inspectionDbPath(args));
221
235
  try {
222
- if (args.command === 'status') {
236
+ if (args.command === 'status' || args.command === 'plan') {
223
237
  const payload = inspection.status({
224
238
  projectId: args.projectId, workspaceId: args.workspaceId,
225
239
  threadId: args.threadId, sessionId: args.sessionId,
226
240
  });
241
+ const queueSummary = buildCandidateQueueSummary(inspection, args.projectId, args.limit || 5000);
242
+ const plan = buildMemoryPlan(payload, queueSummary, args.projectId);
243
+ const enrichedPayload = args.command === 'plan'
244
+ ? plan
245
+ : { ...payload, queueSummary, nextActions: plan.nextActions, blocking: plan.blocking, nonBlocking: plan.nonBlocking };
227
246
  if (args.json) {
228
- printCliJson('memory.status', payload, {
247
+ printCliJson(`memory.${args.command}`, enrichedPayload, {
229
248
  queue: payload.dreamCandidateQueue,
230
249
  beliefs: payload.activeBeliefs,
231
250
  });
232
251
  }
233
252
  else
234
- printHuman('status', payload);
253
+ printHuman(args.command, enrichedPayload);
235
254
  return;
236
255
  }
237
- const status = args.status || 'candidate';
238
- const candidates = inspection.listCandidates({
239
- projectId: args.projectId,
240
- status,
241
- limit: args.limit || 50,
242
- });
243
- const payload = { total: candidates.length, status, candidates: candidates.map(candidateToJson) };
256
+ const queueSummary = buildCandidateQueueSummary(inspection, args.projectId, args.limit || 50);
257
+ const payload = args.status
258
+ ? buildSpecificCandidatePayload(inspection, args.projectId, args.status, args.limit || 50, queueSummary)
259
+ : buildGroupedCandidatePayload(queueSummary);
244
260
  if (args.json)
245
- printCliJson('memory.candidates', payload);
261
+ printCliJson('memory.candidates', payload, { queue: queueSummary });
246
262
  else
247
263
  printHuman('candidates', payload);
248
264
  }
@@ -262,6 +278,7 @@ function eventText(event) {
262
278
  }
263
279
  function eventToJson(event) {
264
280
  const text = eventText(event);
281
+ const locator = eventSourceLocator(event, 2, 2);
265
282
  return {
266
283
  eventId: event.eventId,
267
284
  label: memoryEventLabel(event),
@@ -294,6 +311,21 @@ function eventToJson(event) {
294
311
  causalityType: event.causalityType,
295
312
  orderingConfidence: event.orderingConfidence,
296
313
  },
314
+ sourceLocator: locator,
315
+ };
316
+ }
317
+ function eventSourceLocator(event, before, after) {
318
+ const projectArg = event.projectId ? ` --project ${cliArg(event.projectId)}` : '';
319
+ const base = `cogmem memory show --event ${cliArg(event.eventId)}${projectArg}`;
320
+ return {
321
+ eventId: event.eventId,
322
+ globalSeq: event.globalSeq,
323
+ projectId: event.projectId,
324
+ threadId: event.threadId,
325
+ sessionId: event.sessionId,
326
+ localDate: event.localDate,
327
+ command: `${base} --before ${before} --after ${after} --json`,
328
+ contextCommand: `${base} --before 3 --after 3 --json`,
297
329
  };
298
330
  }
299
331
  function candidateToJson(candidate) {
@@ -313,6 +345,141 @@ function candidateToJson(candidate) {
313
345
  updatedAt: candidate.updatedAt,
314
346
  };
315
347
  }
348
+ function buildCandidateQueueSummary(inspection, projectId, limit) {
349
+ const status = inspection.status({ projectId });
350
+ const queue = status.dreamCandidateQueue;
351
+ const candidateRows = inspection.listCandidates({ projectId, status: 'candidate', limit });
352
+ const needsRows = inspection.listCandidates({ projectId, status: 'needs_confirmation', limit: Math.max(limit, 5000) });
353
+ const now = Date.now();
354
+ const deferredRows = needsRows.filter((candidate) => candidate.reviewAfter !== undefined && candidate.reviewAfter > now);
355
+ const activeNeedsRows = needsRows.filter((candidate) => !(candidate.reviewAfter !== undefined && candidate.reviewAfter > now));
356
+ return {
357
+ candidate: queue.candidate,
358
+ needs_confirmation: queue.needsConfirmation,
359
+ activeNeedsConfirmation: Math.max(0, queue.needsConfirmation - deferredRows.length),
360
+ deferredNeedsConfirmation: deferredRows.length,
361
+ promoted: queue.promoted,
362
+ rejected: queue.rejected,
363
+ superseded: queue.superseded,
364
+ shadow: queue.shadow,
365
+ groups: {
366
+ candidate: candidateRows.slice(0, limit).map(candidateToJson),
367
+ needs_confirmation: activeNeedsRows.slice(0, limit).map(candidateToJson),
368
+ deferred: deferredRows.slice(0, limit).map(candidateToJson),
369
+ },
370
+ };
371
+ }
372
+ function buildSpecificCandidatePayload(inspection, projectId, status, limit, queueSummary) {
373
+ const candidates = inspection.listCandidates({ projectId, status, limit });
374
+ const payload = {
375
+ total: candidates.length,
376
+ status,
377
+ candidates: candidates.map(candidateToJson),
378
+ queueSummary,
379
+ };
380
+ if (status === 'candidate') {
381
+ payload.warning = 'This lists only status=candidate. Use memory candidates without --status for grouped candidate and needs_confirmation queues.';
382
+ }
383
+ else if (status === 'needs_confirmation') {
384
+ payload.warning = 'memory govern does not process needs_confirmation. Use memory review with explicit user evidence, or defer with review_after.';
385
+ }
386
+ return payload;
387
+ }
388
+ function buildGroupedCandidatePayload(queueSummary) {
389
+ return {
390
+ total: queueSummary.groups.candidate.length + queueSummary.groups.needs_confirmation.length + queueSummary.groups.deferred.length,
391
+ status: 'grouped',
392
+ queueSummary,
393
+ groups: queueSummary.groups,
394
+ nextActions: candidateNextActions(queueSummary, undefined),
395
+ };
396
+ }
397
+ function buildMemoryPlan(status, queueSummary, projectId) {
398
+ const nextActions = [];
399
+ const blocking = [];
400
+ const nonBlocking = [];
401
+ const projectArg = projectId ? ` --project ${cliArg(projectId)}` : '';
402
+ const episodeDream = status.episodeDream;
403
+ const pendingEpisodes = asCount(episodeDream.pending) + asCount(episodeDream.retryScheduled);
404
+ const undreamedRawCount = asCount(status.undreamedRawCount);
405
+ if (pendingEpisodes > 0) {
406
+ nextActions.push({
407
+ priority: 'high',
408
+ type: 'dream_tick',
409
+ safeForAutomation: true,
410
+ reason: `${pendingEpisodes} sealed episode Dream jobs can be processed`,
411
+ command: `cogmem dream tick${projectArg} --mode auto --max-episodes 20 --json`,
412
+ });
413
+ }
414
+ if (undreamedRawCount > 0) {
415
+ nonBlocking.push({
416
+ type: 'raw_dream_ledger_lag',
417
+ count: undreamedRawCount,
418
+ episodeDreamPending: pendingEpisodes,
419
+ resolvableByDreamTick: false,
420
+ safeForAutomation: false,
421
+ reason: pendingEpisodes > 0
422
+ ? 'Raw ledger dream coverage is tracked separately from sealed episode Dream jobs; dream tick may process episodes but does not guarantee this counter will clear.'
423
+ : 'Raw ledger dream coverage is behind, but there are no sealed episode Dream jobs. Do not run dream tick for this signal alone.',
424
+ command: `cogmem memory list${projectArg} --order asc --limit 20 --json`,
425
+ });
426
+ }
427
+ nextActions.push(...candidateNextActions(queueSummary, projectId));
428
+ if (queueSummary.activeNeedsConfirmation > 0) {
429
+ blocking.push({
430
+ type: 'needs_confirmation',
431
+ count: queueSummary.activeNeedsConfirmation,
432
+ reason: 'Manual review requires explicit user evidence; memory govern will not process these candidates.',
433
+ command: `cogmem memory candidates${projectArg} --status needs_confirmation --json`,
434
+ });
435
+ }
436
+ if (queueSummary.deferredNeedsConfirmation > 0) {
437
+ nonBlocking.push({
438
+ type: 'deferred_confirmation',
439
+ count: queueSummary.deferredNeedsConfirmation,
440
+ reason: 'Deferred needs_confirmation remains in the review queue until review_after.',
441
+ command: `cogmem memory candidates${projectArg} --json`,
442
+ });
443
+ }
444
+ return {
445
+ healthy: nextActions.length === 0 && blocking.length === 0,
446
+ projectId,
447
+ queueSummary,
448
+ dreamBacklog: status.dreamBacklog,
449
+ episodeDream: status.episodeDream,
450
+ vectorState: status.vectorState,
451
+ nextActions,
452
+ blocking,
453
+ nonBlocking,
454
+ };
455
+ }
456
+ function candidateNextActions(queueSummary, projectId) {
457
+ const projectArg = projectId ? ` --project ${cliArg(projectId)}` : '';
458
+ const actions = [];
459
+ if (queueSummary.candidate > 0) {
460
+ actions.push({
461
+ priority: 'high',
462
+ type: 'govern',
463
+ safeForAutomation: true,
464
+ reason: `${queueSummary.candidate} ordinary candidates are ready for deterministic governance`,
465
+ command: `cogmem memory govern${projectArg} --limit 100 --json`,
466
+ });
467
+ }
468
+ if (queueSummary.activeNeedsConfirmation > 0) {
469
+ actions.push({
470
+ priority: 'medium',
471
+ type: 'review_needs_confirmation',
472
+ safeForAutomation: false,
473
+ reason: `${queueSummary.activeNeedsConfirmation} candidates need explicit operator/user confirmation`,
474
+ command: `cogmem memory candidates${projectArg} --status needs_confirmation --json`,
475
+ reviewCommand: `cogmem memory review${projectArg} --id <candidate-id> --action <approve|reject|defer|supersede|relink> --actor <operator> --reason <reason> --json`,
476
+ });
477
+ }
478
+ return actions;
479
+ }
480
+ function asCount(value) {
481
+ return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : 0;
482
+ }
316
483
  function runStatus(kernel, args) {
317
484
  const page = kernel.eventStore.queryEvents(1, 1, {
318
485
  projectId: args.projectId ? [args.projectId] : undefined,
@@ -344,9 +511,15 @@ function runList(kernel, args) {
344
511
  workspaceId: args.workspaceId ? [args.workspaceId] : undefined,
345
512
  threadId: args.threadId ? [args.threadId] : undefined,
346
513
  sessionId: args.sessionId ? [args.sessionId] : undefined,
514
+ sinceGlobalSeq: args.sinceGlobalSeq,
515
+ untilGlobalSeq: args.untilGlobalSeq,
516
+ order: args.order,
347
517
  });
348
518
  return {
349
519
  total: page.total,
520
+ sinceGlobalSeq: args.sinceGlobalSeq,
521
+ untilGlobalSeq: args.untilGlobalSeq,
522
+ order: args.order || 'desc',
350
523
  events: page.records.map(eventToJson),
351
524
  };
352
525
  }
@@ -607,12 +780,24 @@ function runCandidates(kernel, args) {
607
780
  };
608
781
  }
609
782
  function printHuman(command, payload) {
610
- if (command === 'status') {
783
+ if (command === 'status' || command === 'plan') {
784
+ if (command === 'plan') {
785
+ console.log(`healthy: ${payload.healthy}`);
786
+ console.log(`queueSummary: ${JSON.stringify(payload.queueSummary)}`);
787
+ console.log(`nextActions: ${JSON.stringify(payload.nextActions)}`);
788
+ console.log(`blocking: ${JSON.stringify(payload.blocking)}`);
789
+ console.log(`nonBlocking: ${JSON.stringify(payload.nonBlocking)}`);
790
+ return;
791
+ }
611
792
  console.log(`rawEvents: ${payload.rawEventCount}`);
612
793
  console.log(`vectors: ${payload.vectorCount}`);
613
794
  console.log(`dreamBacklog: ${JSON.stringify(payload.dreamBacklog)}`);
614
795
  console.log(`episodeDream: ${JSON.stringify(payload.episodeDream)}`);
615
796
  console.log(`dreamCandidateQueue: ${JSON.stringify(payload.dreamCandidateQueue)}`);
797
+ if (payload.queueSummary)
798
+ console.log(`queueSummary: ${JSON.stringify(payload.queueSummary)}`);
799
+ if (payload.nextActions)
800
+ console.log(`nextActions: ${JSON.stringify(payload.nextActions)}`);
616
801
  return;
617
802
  }
618
803
  if (command === 'dream') {
@@ -646,6 +831,18 @@ function printHuman(command, payload) {
646
831
  return;
647
832
  }
648
833
  if (command === 'candidates') {
834
+ if (payload.queueSummary)
835
+ console.log(`queueSummary: ${JSON.stringify(payload.queueSummary)}`);
836
+ const groups = payload.groups;
837
+ if (groups) {
838
+ for (const [group, values] of Object.entries(groups)) {
839
+ console.log(`${group}: ${values.length}`);
840
+ for (const candidate of values) {
841
+ console.log(`- ${candidate.candidateId} ${candidate.candidateType} ${candidate.status} confidence=${candidate.confidence}`);
842
+ }
843
+ }
844
+ return;
845
+ }
649
846
  const candidates = Array.isArray(payload.candidates) ? payload.candidates : [];
650
847
  for (const candidate of candidates) {
651
848
  console.log(`- ${candidate.candidateId} ${candidate.candidateType} ${candidate.status} confidence=${candidate.confidence}`);
@@ -741,7 +938,7 @@ async function main() {
741
938
  console.log(usage());
742
939
  return;
743
940
  }
744
- if (args.command === 'status' || args.command === 'candidates') {
941
+ if (args.command === 'status' || args.command === 'plan' || args.command === 'candidates') {
745
942
  runReadOnlyInspection(args);
746
943
  return;
747
944
  }
@@ -801,3 +998,6 @@ main().catch((error) => {
801
998
  console.error(error instanceof Error ? error.message : String(error));
802
999
  process.exit(1);
803
1000
  });
1001
+ function cliArg(value) {
1002
+ return /^[A-Za-z0-9._:/=@+-]+$/u.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
1003
+ }
package/dist/factory.d.ts CHANGED
@@ -138,6 +138,8 @@ export interface MemoryBindingBackfillOptions {
138
138
  export interface MemoryBindingBackfillResult {
139
139
  projectId?: string;
140
140
  sinceGlobalSeq?: number;
141
+ nextGlobalSeq?: number;
142
+ hasMore: boolean;
141
143
  scannedEvents: number;
142
144
  bindableEvents: number;
143
145
  boundEvents: number;
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.4';
86
+ const CORE_VERSION = '3.6.5';
87
87
  const LATEST_SCHEMA_VERSION = 27;
88
88
  export class MemoryKernel {
89
89
  options;
@@ -1195,18 +1195,22 @@ export class MemoryKernel {
1195
1195
  }
1196
1196
  bindRawEvents(options = {}) {
1197
1197
  const limit = Math.max(1, Math.min(options.limit ?? 500, 5000));
1198
- const page = this.eventStore.queryEvents(1, limit, {
1199
- projectId: options.projectId ? [options.projectId] : undefined,
1200
- workspaceId: options.workspaceId ? [options.workspaceId] : undefined,
1201
- threadId: options.threadId ? [options.threadId] : undefined,
1202
- sessionId: options.sessionId ? [options.sessionId] : undefined,
1198
+ const afterGlobalSeq = options.sinceGlobalSeq === undefined
1199
+ ? undefined
1200
+ : Math.max(-1, options.sinceGlobalSeq - 1);
1201
+ const records = this.eventStore.listRawEventsAfterGlobalSeq({
1202
+ projectId: options.projectId,
1203
+ workspaceId: options.workspaceId,
1204
+ threadId: options.threadId,
1205
+ sessionId: options.sessionId,
1206
+ afterGlobalSeq,
1207
+ limit,
1203
1208
  });
1204
- const records = page.records
1205
- .filter((event) => options.sinceGlobalSeq === undefined || (event.globalSeq || 0) >= options.sinceGlobalSeq)
1206
- .sort((a, b) => (a.globalSeq || 0) - (b.globalSeq || 0));
1207
1209
  const result = {
1208
1210
  projectId: options.projectId,
1209
1211
  sinceGlobalSeq: options.sinceGlobalSeq,
1212
+ nextGlobalSeq: records.at(-1)?.globalSeq,
1213
+ hasMore: records.length >= limit,
1210
1214
  scannedEvents: records.length,
1211
1215
  bindableEvents: 0,
1212
1216
  boundEvents: 0,
@@ -1325,16 +1329,30 @@ export class MemoryKernel {
1325
1329
  return { touched: this.memoryAtlasStore.recordAccess(input.projectId, valid, input.reason, input.query, input.now) };
1326
1330
  }
1327
1331
  countUnboundBindableRawEvents(projectId, limit = 1000) {
1328
- const page = this.eventStore.queryEvents(1, Math.max(1, limit), {
1329
- projectId: projectId ? [projectId] : undefined,
1330
- });
1332
+ const max = Math.max(1, limit);
1333
+ let afterGlobalSeq;
1331
1334
  let count = 0;
1332
- for (const event of page.records) {
1333
- if (!this.memoryBindingService.isBindableRawEvent(event))
1334
- continue;
1335
- if (this.memoryBindingStore.listBindings({ eventId: event.eventId, limit: 1 }).length > 0)
1336
- continue;
1337
- count += 1;
1335
+ while (count < max) {
1336
+ const batchLimit = Math.min(500, max);
1337
+ const events = this.eventStore.listRawEventsAfterGlobalSeq({
1338
+ projectId,
1339
+ afterGlobalSeq,
1340
+ limit: batchLimit,
1341
+ });
1342
+ if (events.length === 0)
1343
+ break;
1344
+ afterGlobalSeq = events.at(-1)?.globalSeq ?? afterGlobalSeq;
1345
+ for (const event of events) {
1346
+ if (!this.memoryBindingService.isBindableRawEvent(event))
1347
+ continue;
1348
+ if (this.memoryBindingStore.listBindings({ eventId: event.eventId, limit: 1 }).length > 0)
1349
+ continue;
1350
+ count += 1;
1351
+ if (count >= max)
1352
+ break;
1353
+ }
1354
+ if (events.length < batchLimit)
1355
+ break;
1338
1356
  }
1339
1357
  return count;
1340
1358
  }
@@ -26,7 +26,7 @@ export function installOpenClawAutoMemoryPlugin(options) {
26
26
  ? { kind: 'toml', path: resolve(options.configPath) }
27
27
  : resolveCogmemConfigPath({ cwd: workspaceRoot });
28
28
  if (configResolution.kind !== 'toml') {
29
- throw new Error(`Missing cogmem config at ${configResolution.path}. Run cogmem-init --agent openclaw --scope project first.`);
29
+ throw new Error(`Missing cogmem config at ${configResolution.path}. Ask an operator to run the interactive setup: cogmem init --agent openclaw --scope project.`);
30
30
  }
31
31
  const configPath = configResolution.path;
32
32
  const pluginDir = resolve(options.pluginDir || defaultOpenClawAutoMemoryPluginDir(workspaceRoot));
@@ -663,6 +663,9 @@ function classifyRecallIntent(query) {
663
663
  if (/原话|怎么说的|完整对话|上一句|下一句|exact quote|verbatim/.test(text)) {
664
664
  return 'forensic_quote';
665
665
  }
666
+ 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
+ return 'historical_discussion';
668
+ }
666
669
  return 'memory_recall';
667
670
  }
668
671
 
@@ -1754,7 +1757,8 @@ function formatRecallContext(result, config) {
1754
1757
  if (item.sourceContext.locator && item.sourceContext.locator.command) {
1755
1758
  lines.push(' sourceLocator=' + item.sourceContext.locator.command);
1756
1759
  } else if (item.sourceContext.event.eventId) {
1757
- lines.push(' sourceLocator=cogmem memory show --event ' + item.sourceContext.event.eventId + ' --before 2 --after 2');
1760
+ const project = item.sourceContext.event.projectId ? ' --project ' + item.sourceContext.event.projectId : '';
1761
+ lines.push(' sourceLocator=cogmem memory show --event ' + item.sourceContext.event.eventId + project + ' --before 2 --after 2 --json');
1758
1762
  }
1759
1763
  const seenEventIds = new Set([anchorEvent.eventId].filter(Boolean));
1760
1764
  const before = uniqueWindowEvents(Array.isArray(item.sourceContext.before) ? item.sourceContext.before : [], seenEventIds).slice(-2);
@@ -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.6.1',
8
+ version: '3.6.5',
9
9
  }, {
10
10
  capabilities: {
11
11
  tools: {},
@@ -61,6 +61,9 @@ export declare class EventStore {
61
61
  getLatestEvent(): MemoryEvent | null;
62
62
  listRawEventsAfterGlobalSeq(options?: {
63
63
  projectId?: string;
64
+ workspaceId?: string;
65
+ threadId?: string;
66
+ sessionId?: string;
64
67
  afterGlobalSeq?: number;
65
68
  limit?: number;
66
69
  }): MemoryEvent[];
@@ -78,6 +81,9 @@ export declare class EventStore {
78
81
  sessionId?: string[];
79
82
  startTime?: number;
80
83
  endTime?: number;
84
+ sinceGlobalSeq?: number;
85
+ untilGlobalSeq?: number;
86
+ order?: 'asc' | 'desc';
81
87
  }): EventAuditPage;
82
88
  getEvent(eventId: string): MemoryEvent | null;
83
89
  getThreadEvents(threadId: string, options?: {
@@ -270,6 +270,18 @@ export class EventStore {
270
270
  conditions.push('project_id = ?');
271
271
  params.push(options.projectId);
272
272
  }
273
+ if (options.workspaceId) {
274
+ conditions.push('workspace_id = ?');
275
+ params.push(options.workspaceId);
276
+ }
277
+ if (options.threadId) {
278
+ conditions.push('thread_id = ?');
279
+ params.push(options.threadId);
280
+ }
281
+ if (options.sessionId) {
282
+ conditions.push('session_id = ?');
283
+ params.push(options.sessionId);
284
+ }
273
285
  if (options.afterGlobalSeq !== undefined) {
274
286
  conditions.push('COALESCE(global_seq, 0) > ?');
275
287
  params.push(options.afterGlobalSeq);
@@ -347,7 +359,18 @@ export class EventStore {
347
359
  conditions.push('occurred_at <= ?');
348
360
  params.push(filters.endTime);
349
361
  }
362
+ if (filters?.sinceGlobalSeq !== undefined) {
363
+ conditions.push('COALESCE(global_seq, 0) >= ?');
364
+ params.push(filters.sinceGlobalSeq);
365
+ }
366
+ if (filters?.untilGlobalSeq !== undefined) {
367
+ conditions.push('COALESCE(global_seq, 0) <= ?');
368
+ params.push(filters.untilGlobalSeq);
369
+ }
350
370
  const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
371
+ const orderSql = filters?.order === 'asc'
372
+ ? 'COALESCE(global_seq, 0) ASC, occurred_at ASC, event_id ASC'
373
+ : 'COALESCE(global_seq, 0) DESC, occurred_at DESC, event_id DESC';
351
374
  const totalRow = this.db.prepare(`
352
375
  SELECT COUNT(*) AS count FROM memory_events ${where}
353
376
  `).get(...params);
@@ -355,7 +378,7 @@ export class EventStore {
355
378
  SELECT ${MEMORY_EVENT_COLUMNS}
356
379
  FROM memory_events
357
380
  ${where}
358
- ORDER BY COALESCE(global_seq, 0) DESC, occurred_at DESC, event_id DESC
381
+ ORDER BY ${orderSql}
359
382
  LIMIT ? OFFSET ?
360
383
  `).all(...params, safePageSize, offset);
361
384
  return {
@@ -375,7 +398,10 @@ export class EventStore {
375
398
  threadId: filters?.threadId,
376
399
  sessionId: filters?.sessionId,
377
400
  startTime: filters?.startTime,
378
- endTime: filters?.endTime
401
+ endTime: filters?.endTime,
402
+ sinceGlobalSeq: filters?.sinceGlobalSeq,
403
+ untilGlobalSeq: filters?.untilGlobalSeq,
404
+ order: filters?.order,
379
405
  }
380
406
  };
381
407
  }
@@ -850,6 +850,9 @@ export interface EventAuditPage {
850
850
  sessionId?: string[];
851
851
  startTime?: number;
852
852
  endTime?: number;
853
+ sinceGlobalSeq?: number;
854
+ untilGlobalSeq?: number;
855
+ order?: 'asc' | 'desc';
853
856
  };
854
857
  }
855
858
  //# sourceMappingURL=index.d.ts.map
@@ -122,12 +122,14 @@ cogmem normalize-transcript --input ./hermes-sessions.jsonl --output ./hermes.no
122
122
  cogmem import-hermes --workspace . --project hermes --session ./hermes.normalized.md
123
123
  ```
124
124
 
125
- Cogmem 3.6.4 skips import batch sealing for empty episode boundaries and skips legacy empty Dream jobs so one bad imported episode cannot block the queue.
125
+ Cogmem 3.6.4 and later skip import batch sealing for empty episode boundaries and skip legacy empty Dream jobs so one bad imported episode cannot block the queue.
126
126
 
127
127
  After import, use this order:
128
128
 
129
129
  ```bash
130
+ cogmem memory plan --project hermes --json
130
131
  cogmem memory status --project hermes --json
132
+ cogmem memory candidates --project hermes --json
131
133
  cogmem episode status --project hermes --json
132
134
  cogmem dream status --project hermes --json
133
135
  cogmem dream tick --project hermes --mode auto --max-episodes 20 --json
@@ -135,9 +137,10 @@ cogmem memory candidates --project hermes --status candidate --json
135
137
  cogmem memory govern --project hermes --limit 100 --json
136
138
  cogmem memory candidates --project hermes --status needs_confirmation --json
137
139
  cogmem memory review --project hermes --id <candidate-id> --action approve --actor <operator> --reason "confirmed by user" --confirmation-event <user-event-id> --json
140
+ cogmem memory recall --query "<verification question>" --project hermes --agent hermes --json
138
141
  ```
139
142
 
140
- `needs_confirmation` is not the Dream backlog. `memory govern` does not approve it; use `memory review` with explicit evidence.
143
+ `memory plan` is the first agent-safe health and next-action command. Only run `dream_tick` when it appears in `nextActions`; if `nonBlocking` contains `raw_dream_ledger_lag` with `resolvableByDreamTick:false`, inspect raw sources or episode/import boundaries instead of retrying `dream tick`. Default `memory candidates --json` groups ordinary, `needs_confirmation`, and deferred review queues; use `--status` only when a human asks for one queue. `needs_confirmation` is not the Dream backlog. `memory govern` does not approve it; use `memory review` with explicit evidence.
141
144
 
142
145
  ## Active Memory Search
143
146
 
@@ -153,15 +156,18 @@ For inventory or product questions, use recall first and raw search as a forensi
153
156
  cogmem memory recall --query "我们记录过哪些库存" --project hermes --agent hermes --json
154
157
  cogmem memory search --query "エルビ 库存" --project hermes --json
155
158
  cogmem memory show --event <event-id> --before 2 --after 2 --json
159
+ cogmem memory recall --query "几个月前我们是不是讨论过 Cogmem 的记忆黑盒" --intent historical_discussion --project hermes --agent hermes --json
160
+ cogmem memory list --project hermes --since <globalSeq> --order asc --json
156
161
  ```
157
162
 
158
163
  `vectors: 0` does not mean Cogmem has no memory. It means the dense vector index has no hot vectors yet. `memory recall` still falls back to governed raw ledger search and returns `sourceContext` locators. Broad inventory questions are expanded into structured cues such as `库存管理`, `在库`, `产品コード`, and `数量`; if compiled-memory candidates miss those cues, raw ledger evidence is preferred.
159
164
 
160
- `sourceContext` and `memory show --json` now share the same replay contract: each event has a `label`, optional `charRange` / `sourceRange`, and `sourceContext.window` / `window` metadata with requested counts, actual counts, `excludesAnchor`, `ordering`, `roleFilter`, and `overlapHandling`. Use those fields before quoting exact wording or explaining what happened before/after a recalled point.
165
+ `sourceContext` and `memory show --json` now share the same replay contract: each event has a `label`, optional `charRange` / `sourceRange`, and `sourceContext.window` / `window` metadata with requested counts, actual counts, `excludesAnchor`, `ordering`, `roleFilter`, and `overlapHandling`. Use those fields before quoting exact wording or explaining what happened before/after a recalled point. In 3.6.5, Raw Ledger list rows and Atlas search/explore evidence include `sourceLocator.command` and `sourceLocator.contextCommand`; run those commands before saying the source event is unavailable.
161
166
 
162
167
  Check status with:
163
168
 
164
169
  ```bash
170
+ cogmem memory plan --project hermes --json
165
171
  cogmem memory status --project hermes --json
166
172
  ```
167
173
 
@@ -221,12 +227,12 @@ Hermes has no automatic observation path in this integration. Use `cogmem_episod
221
227
 
222
228
  Use `cogmem_topic_operate` with actor `user_explicit` only for explicit user naming/organization instructions. Model suggestions use `model_candidate` and remain reviewable; alias collisions fail closed. Use `cogmem_episode_repair` for split/merge/move/reclassify/requeue work so receipts, stale candidates, cross-references, Dream jobs, and audit records stay synchronized. Do not hand-edit the database.
223
229
 
224
- Use `cogmem memory map --project hermes --json` or MCP `cogmem_memory_map` to inspect Memory Binding and Graph Recall counters. Bindings attach high-value user raw events to stable topic/entity paths, fuse same-claim evidence into claim-key clusters, and create graph anchors for source drill-down only; they are not verified facts, user preferences, or instructions. Correction bindings expose review flags and correction edges. If `cogmem memory tick --project hermes --json` suggests `bind_raw_events`, run `cogmem memory bind --project hermes --json`. Re-run `cogmem connect hermes --workspace . --auto` after upgrades to patch existing MCP allow-lists with new Cogmem tools.
230
+ Use `cogmem memory map --project hermes --json` or MCP `cogmem_memory_map` to inspect Memory Binding and Graph Recall counters. Bindings attach high-value user raw events to stable topic/entity paths, fuse same-claim evidence into claim-key clusters, and create graph anchors for source drill-down only; they are not verified facts, user preferences, or instructions. Correction bindings expose review flags and correction edges. If `cogmem memory tick --project hermes --json` suggests `bind_raw_events`, run `cogmem memory bind --project hermes --json`; in 3.6.5 it scans the historical ledger by cursor and can resume with `--since <globalSeq>`. Re-run `cogmem connect hermes --workspace . --auto --force --json` after upgrades to patch existing MCP allow-lists with new Cogmem tools. Follow only `nextCommands` for unattended agent work; operator/host actions are in `nextSteps` with `safeForAutomation=false`.
225
231
 
226
232
  Dream correction records require explicit user clarification; assistant self-correction and questions containing `是不是` are not user-owned contradictions. Invalid provider output is a rejected diagnostic. Maintenance ticks supersede `needs_confirmation` entries older than the default 30-day TTL and retain their evidence rows.
227
233
 
228
234
  ## Memory Atlas navigation
229
235
 
230
- For broad inventory or historical questions call `cogmem_graph_explore`. Use `cogmem_graph_search` and `cogmem_graph_node` for known concepts, `cogmem_graph_neighbors`/`cogmem_graph_path` for relations, and `cogmem_graph_timeline` for ordered reconstruction. Use `cogmem_recall` for a direct fact and follow event IDs to `cogmem memory show` for exact wording.
236
+ For broad inventory or historical questions call `cogmem_graph_explore`. Use `cogmem_graph_search` and `cogmem_graph_node` for known concepts, `cogmem_graph_neighbors`/`cogmem_graph_path` for relations, and `cogmem_graph_timeline` for ordered reconstruction. Use `cogmem_recall` for a direct fact and follow event IDs or returned `sourceLocator` commands to `cogmem memory show` for exact wording.
231
237
 
232
238
  Combine the filters present in the user's message, including project, time, topic, entity/target, memory kind, and ordinary cues. Do not force an entity + time + action tuple. A cold result is newly visible, not newly verified or promoted.