cogmem 3.6.3 → 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.
@@ -54,6 +54,7 @@ export declare class EpisodeStore {
54
54
  }): EpisodeEventLink;
55
55
  getEventLink(eventId: string): EpisodeEventLink | undefined;
56
56
  listEventLinks(episodeId: string): EpisodeEventLink[];
57
+ isEpisodeEmpty(episodeId: string): boolean;
57
58
  addCrossReference(input: {
58
59
  projectId: string;
59
60
  episodeId: string;
@@ -120,6 +121,10 @@ export declare class EpisodeStore {
120
121
  maxAttempts: number;
121
122
  runId?: string;
122
123
  }): ClaimedEpisodeDreamJob[];
124
+ skipEmptyDreamJobs(input: {
125
+ projectId?: string;
126
+ now?: number;
127
+ }): number;
123
128
  completeDreamJob(episodeId: string, leaseId: string, candidateIds: string[], now: number): void;
124
129
  failDreamJob(episodeId: string, leaseId: string, error: string, input: {
125
130
  now: number;
@@ -128,6 +133,8 @@ export declare class EpisodeStore {
128
133
  retryAfter?: number;
129
134
  }): void;
130
135
  retryFailed(projectId?: string): number;
136
+ private markEmptyEpisodeDreamSkipped;
137
+ private markEmptyEpisodeDreamSkippedMany;
131
138
  getDreamStatus(projectId?: string): EpisodeDreamStatus;
132
139
  countUnassignedRawEvents(projectId?: string): number;
133
140
  markEventDisposition(input: {
@@ -142,6 +142,12 @@ export class EpisodeStore {
142
142
  SELECT * FROM memory_episode_events WHERE episode_id = ? ORDER BY position
143
143
  `).all(episodeId).map(mapEventLink);
144
144
  }
145
+ isEpisodeEmpty(episodeId) {
146
+ const episode = this.getEpisode(episodeId);
147
+ if (!episode)
148
+ throw new Error(`episode_not_found:${episodeId}`);
149
+ return this.listEventLinks(episodeId).length === 0;
150
+ }
145
151
  addCrossReference(input) {
146
152
  const episode = this.getEpisode(input.episodeId);
147
153
  if (!episode)
@@ -334,9 +340,18 @@ export class EpisodeStore {
334
340
  const now = input.now ?? Date.now();
335
341
  const episodes = this.listEpisodes({ projectId: input.projectId, statuses: ['soft_sealed'], limit: 1000 })
336
342
  .filter((episode) => (episode.sealedAt || episode.updatedAt) <= input.sealedBefore);
337
- for (const episode of episodes)
343
+ let sealed = 0;
344
+ for (const episode of episodes) {
345
+ if (this.isEpisodeEmpty(episode.episodeId)) {
346
+ if (episode.dreamError !== 'episode_empty_soft_seal_not_promoted') {
347
+ this.markEmptyEpisodeDreamSkipped(episode.episodeId, now, 'episode_empty_soft_seal_not_promoted');
348
+ }
349
+ continue;
350
+ }
338
351
  this.sealEpisode(episode.episodeId, { mode: 'hard', reason: 'soft_seal_stabilized', now });
339
- return episodes.length;
352
+ sealed += 1;
353
+ }
354
+ return sealed;
340
355
  }
341
356
  claimDreamJobs(input) {
342
357
  this.db.prepare(`
@@ -372,15 +387,21 @@ export class EpisodeStore {
372
387
  UPDATE memory_episodes SET dream_status = 'queued', dream_error = NULL
373
388
  WHERE episode_id IN (SELECT episode_id FROM episode_dream_jobs WHERE state = 'retry_scheduled' AND updated_at = ?)
374
389
  `).run(input.now);
375
- const where = [`(state = 'pending' OR (state = 'retry_scheduled' AND attempts < ?))`];
390
+ this.skipEmptyDreamJobs({ projectId: input.projectId, now: input.now });
391
+ const where = [`(j.state = 'pending' OR (j.state = 'retry_scheduled' AND j.attempts < ?))`];
376
392
  const params = [input.maxAttempts];
377
393
  if (input.projectId) {
378
- where.push('project_id = ?');
394
+ where.push('j.project_id = ?');
379
395
  params.push(input.projectId);
380
396
  }
381
397
  const rows = this.db.prepare(`
382
- SELECT episode_id, project_id, mode_hint, attempts, created_at FROM episode_dream_jobs
383
- WHERE ${where.join(' AND ')} ORDER BY priority DESC, created_at LIMIT ?
398
+ SELECT j.episode_id, j.project_id, j.mode_hint, j.attempts, j.created_at
399
+ FROM episode_dream_jobs j
400
+ JOIN memory_episodes e ON e.episode_id = j.episode_id
401
+ WHERE ${where.join(' AND ')}
402
+ AND e.event_count > 0
403
+ AND EXISTS (SELECT 1 FROM memory_episode_events ee WHERE ee.episode_id = j.episode_id)
404
+ ORDER BY j.priority DESC, j.created_at LIMIT ?
384
405
  `).all(...params, Math.max(1, Math.min(Math.trunc(input.limit), 100)));
385
406
  const claimed = [];
386
407
  for (const row of rows) {
@@ -403,6 +424,32 @@ export class EpisodeStore {
403
424
  }
404
425
  return claimed;
405
426
  }
427
+ skipEmptyDreamJobs(input) {
428
+ const now = input.now ?? Date.now();
429
+ const params = [];
430
+ const where = [
431
+ `j.state IN ('pending', 'failed_retryable', 'retry_scheduled')`,
432
+ `(COALESCE(e.event_count, 0) = 0 OR NOT EXISTS (
433
+ SELECT 1 FROM memory_episode_events ee WHERE ee.episode_id = j.episode_id
434
+ ))`,
435
+ ];
436
+ if (input.projectId) {
437
+ where.push(`j.project_id = ?`);
438
+ params.push(input.projectId);
439
+ }
440
+ const rows = this.db.prepare(`
441
+ SELECT j.episode_id FROM episode_dream_jobs j
442
+ LEFT JOIN memory_episodes e ON e.episode_id = j.episode_id
443
+ WHERE ${where.join(' AND ')}
444
+ ORDER BY j.updated_at DESC
445
+ LIMIT 1000
446
+ `).all(...params);
447
+ const episodeIds = rows.map((row) => row.episode_id);
448
+ if (!episodeIds.length)
449
+ return 0;
450
+ this.markEmptyEpisodeDreamSkippedMany(episodeIds, now, 'episode_empty_skipped_no_raw_evidence');
451
+ return episodeIds.length;
452
+ }
406
453
  completeDreamJob(episodeId, leaseId, candidateIds, now) {
407
454
  const result = this.db.prepare(`
408
455
  UPDATE episode_dream_jobs SET state = 'processed', candidate_ids_json = ?, lease_id = NULL,
@@ -441,6 +488,28 @@ export class EpisodeStore {
441
488
  }
442
489
  return Number(result.changes || 0);
443
490
  }
491
+ markEmptyEpisodeDreamSkipped(episodeId, now, reason) {
492
+ this.markEmptyEpisodeDreamSkippedMany([episodeId], now, reason);
493
+ }
494
+ markEmptyEpisodeDreamSkippedMany(episodeIds, now, reason) {
495
+ if (!episodeIds.length)
496
+ return;
497
+ const placeholders = episodeIds.map(() => '?').join(', ');
498
+ this.db.transaction(() => {
499
+ this.db.prepare(`
500
+ UPDATE episode_dream_jobs SET state = 'skipped', lease_id = NULL, lease_until = NULL,
501
+ retry_after = NULL, failure_category = 'episode_empty', last_error = ?, candidate_ids_json = '[]',
502
+ updated_at = ?
503
+ WHERE episode_id IN (${placeholders})
504
+ AND state IN ('pending', 'processing', 'failed_retryable', 'retry_scheduled')
505
+ `).run(reason, now, ...episodeIds);
506
+ this.db.prepare(`
507
+ UPDATE memory_episodes SET dream_status = 'failed', dream_error = ?, last_dream_run_id = NULL,
508
+ dream_candidate_count = 0, updated_at = ?
509
+ WHERE episode_id IN (${placeholders})
510
+ `).run(reason, now, ...episodeIds);
511
+ })();
512
+ }
444
513
  getDreamStatus(projectId) {
445
514
  const rows = (projectId
446
515
  ? this.db.prepare(`SELECT state, COUNT(*) AS count FROM episode_dream_jobs WHERE project_id = ? GROUP BY state`).all(projectId)
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.3';
86
+ const CORE_VERSION = '3.6.5';
87
87
  const LATEST_SCHEMA_VERSION = 27;
88
88
  export class MemoryKernel {
89
89
  options;
@@ -983,6 +983,8 @@ export class MemoryKernel {
983
983
  }
984
984
  sealImportedEpisode(episodeId, input) {
985
985
  const links = this.episodeStore.listEventLinks(episodeId);
986
+ if (!links.length)
987
+ throw new Error(`episode_empty:${episodeId}`);
986
988
  const averageConfidence = links.length
987
989
  ? links.reduce((total, link) => total + link.confidence, 0) / links.length
988
990
  : 0;
@@ -1193,18 +1195,22 @@ export class MemoryKernel {
1193
1195
  }
1194
1196
  bindRawEvents(options = {}) {
1195
1197
  const limit = Math.max(1, Math.min(options.limit ?? 500, 5000));
1196
- const page = this.eventStore.queryEvents(1, limit, {
1197
- projectId: options.projectId ? [options.projectId] : undefined,
1198
- workspaceId: options.workspaceId ? [options.workspaceId] : undefined,
1199
- threadId: options.threadId ? [options.threadId] : undefined,
1200
- 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,
1201
1208
  });
1202
- const records = page.records
1203
- .filter((event) => options.sinceGlobalSeq === undefined || (event.globalSeq || 0) >= options.sinceGlobalSeq)
1204
- .sort((a, b) => (a.globalSeq || 0) - (b.globalSeq || 0));
1205
1209
  const result = {
1206
1210
  projectId: options.projectId,
1207
1211
  sinceGlobalSeq: options.sinceGlobalSeq,
1212
+ nextGlobalSeq: records.at(-1)?.globalSeq,
1213
+ hasMore: records.length >= limit,
1208
1214
  scannedEvents: records.length,
1209
1215
  bindableEvents: 0,
1210
1216
  boundEvents: 0,
@@ -1323,16 +1329,30 @@ export class MemoryKernel {
1323
1329
  return { touched: this.memoryAtlasStore.recordAccess(input.projectId, valid, input.reason, input.query, input.now) };
1324
1330
  }
1325
1331
  countUnboundBindableRawEvents(projectId, limit = 1000) {
1326
- const page = this.eventStore.queryEvents(1, Math.max(1, limit), {
1327
- projectId: projectId ? [projectId] : undefined,
1328
- });
1332
+ const max = Math.max(1, limit);
1333
+ let afterGlobalSeq;
1329
1334
  let count = 0;
1330
- for (const event of page.records) {
1331
- if (!this.memoryBindingService.isBindableRawEvent(event))
1332
- continue;
1333
- if (this.memoryBindingStore.listBindings({ eventId: event.eventId, limit: 1 }).length > 0)
1334
- continue;
1335
- 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;
1336
1356
  }
1337
1357
  return count;
1338
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
@@ -15,19 +15,24 @@ This writes `~/.hermes/skills/cogmem-memory/SKILL.md`, which Hermes discovers as
15
15
  Run from the Hermes workspace root:
16
16
 
17
17
  ```bash
18
- COGMEM_SKIP_INIT=1 curl -fsSL https://raw.githubusercontent.com/liuqin164/cogmem/main/install.sh | bash
19
- cogmem init --yes --agent hermes
20
- cogmem doctor --fix --agent hermes --workspace .
21
- cogmem connect hermes --workspace . --auto
22
- cogmem connect hermes --workspace .
18
+ npm install cogmem@latest --save
19
+ COGMEM="./node_modules/.bin/cogmem"
20
+ "$COGMEM" doctor
21
+ "$COGMEM" connect hermes --workspace . --auto --force --json
22
+ ```
23
+
24
+ Do not run `cogmem init` as an unattended agent action. It is an interactive wizard. Use it only when an operator is present:
25
+
26
+ ```bash
27
+ "$COGMEM" init --agent hermes --scope project
23
28
  ```
24
29
 
25
- The default install creates:
30
+ The workspace install creates:
26
31
 
27
32
  ```text
28
- ~/.cogmem/config.toml
29
- ~/.cogmem/memory.db
30
- ~/.cogmem/snapshots/
33
+ .cogmem/config.toml
34
+ .cogmem/memory.db
35
+ .cogmem/snapshots/
31
36
  ```
32
37
 
33
38
  Use `~/.cogmem/config.toml` or a project `.cogmem/config.toml` as the stable configuration source. Do not create `.cogmem.env` files or pass `--env-path` for normal installs. Environment variables are only for explicit process-level overrides documented by the CLI, not for hidden workspace configuration.
@@ -44,7 +49,7 @@ Use MCP `cogmem_strategy_plan` when the agent needs to inspect the selected memo
44
49
 
45
50
  Prospective Memory is not executable instruction. Only a user-confirmed candidate may appear as due, and even then the agent must obtain normal host authorization before acting. Use `cogmem prospective` for state transitions and `cogmem brain-eval` for release validation.
46
51
 
47
- Use `cogmem init --yes --agent hermes --scope project` only when this workspace needs its own `.cogmem/` directory.
52
+ Use project-local config when this workspace owns its memory; use global config only when the operator intentionally shares one Cogmem backend across workspaces.
48
53
 
49
54
  To embed imported memories with a local quantized model, run Ollama locally and configure the kernel before importing:
50
55
 
@@ -117,14 +122,26 @@ cogmem normalize-transcript --input ./hermes-sessions.jsonl --output ./hermes.no
117
122
  cogmem import-hermes --workspace . --project hermes --session ./hermes.normalized.md
118
123
  ```
119
124
 
120
- After import, inspect the batch-sealed episodes, run one conditional curation tick, then invoke CPU governance separately:
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
+
127
+ After import, use this order:
121
128
 
122
129
  ```bash
130
+ cogmem memory plan --project hermes --json
131
+ cogmem memory status --project hermes --json
132
+ cogmem memory candidates --project hermes --json
123
133
  cogmem episode status --project hermes --json
124
- cogmem dream tick --project hermes --mode auto --json
125
- cogmem memory govern --project hermes --json
134
+ cogmem dream status --project hermes --json
135
+ cogmem dream tick --project hermes --mode auto --max-episodes 20 --json
136
+ cogmem memory candidates --project hermes --status candidate --json
137
+ cogmem memory govern --project hermes --limit 100 --json
138
+ cogmem memory candidates --project hermes --status needs_confirmation --json
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
126
141
  ```
127
142
 
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.
144
+
128
145
  ## Active Memory Search
129
146
 
130
147
  If the current prompt does not include enough Cogmem memory context, query Cogmem directly before searching legacy files:
@@ -139,15 +156,18 @@ For inventory or product questions, use recall first and raw search as a forensi
139
156
  cogmem memory recall --query "我们记录过哪些库存" --project hermes --agent hermes --json
140
157
  cogmem memory search --query "エルビ 库存" --project hermes --json
141
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
142
161
  ```
143
162
 
144
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.
145
164
 
146
- `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.
147
166
 
148
167
  Check status with:
149
168
 
150
169
  ```bash
170
+ cogmem memory plan --project hermes --json
151
171
  cogmem memory status --project hermes --json
152
172
  ```
153
173
 
@@ -207,12 +227,12 @@ Hermes has no automatic observation path in this integration. Use `cogmem_episod
207
227
 
208
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.
209
229
 
210
- 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`.
211
231
 
212
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.
213
233
 
214
234
  ## Memory Atlas navigation
215
235
 
216
- 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.
217
237
 
218
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.
@@ -14,19 +14,16 @@ Belief Graph writes keep ownership and evidence roles. Hermes may record assista
14
14
  ## Install
15
15
 
16
16
  ```bash
17
- COGMEM_SKIP_INIT=1 curl -fsSL https://raw.githubusercontent.com/liuqin164/cogmem/main/install.sh | bash
18
- cogmem init --yes --agent hermes
19
- cogmem doctor --fix --agent hermes --workspace .
20
- cogmem connect hermes --workspace . --auto
21
- cogmem connect hermes --workspace .
17
+ npm install cogmem@latest --save
18
+ COGMEM="./node_modules/.bin/cogmem"
19
+ "$COGMEM" doctor
20
+ "$COGMEM" connect hermes --workspace . --auto --force --json
22
21
  ```
23
22
 
24
- If Bun is already installed, npm global install is also supported:
23
+ Use `cogmem init` only as an interactive operator wizard, not as an unattended agent install step:
25
24
 
26
25
  ```bash
27
- npm install -g cogmem@latest
28
- cogmem init --yes --agent hermes
29
- cogmem connect hermes --workspace . --auto --force
26
+ "$COGMEM" init --agent hermes --scope project
30
27
  ```
31
28
 
32
29
  Hermes integration is currently a skill plus MCP bridge. It does not replace a native Hermes memory provider and it does not patch Hermes runtime internals.
@@ -75,6 +72,21 @@ Import:
75
72
  cogmem import-hermes --workspace . --project hermes
76
73
  ```
77
74
 
75
+ After import:
76
+
77
+ ```bash
78
+ cogmem memory plan --project hermes --json
79
+ cogmem memory status --project hermes --json
80
+ cogmem memory candidates --project hermes --json
81
+ cogmem episode status --project hermes --json
82
+ cogmem dream status --project hermes --json
83
+ cogmem dream tick --project hermes --mode auto --max-episodes 20 --json
84
+ cogmem memory candidates --project hermes --status candidate --json
85
+ cogmem memory govern --project hermes --limit 100 --json
86
+ cogmem memory candidates --project hermes --status needs_confirmation --json
87
+ cogmem memory review --project hermes --id <candidate-id> --action approve --actor <operator> --reason "confirmed by user" --confirmation-event <user-event-id> --json
88
+ ```
89
+
78
90
  If Hermes stores memory somewhere else:
79
91
 
80
92
  ```bash
@@ -91,13 +103,13 @@ cogmem import-hermes --workspace . --project hermes --session ./one.md --session
91
103
  The import command is idempotent. Re-running it against the same database skips records already processed by the cursor store. Imported raw records enter the same Episode Assembler used by live turns and are sealed at the explicit import batch boundary.
92
104
  Imported records are embedded through the configured kernel embedder during import.
93
105
 
94
- MCP recall JSON includes `decisionTrace`. Check its selected lane, reason, and candidate counts before concluding that a memory is absent, and use `sourceContext.locator.command` for exact wording. Raw text fallback searches the fully scoped ledger and prefers original user anchors over later assistant retellings when cue scores tie.
106
+ MCP recall JSON includes `decisionTrace`. Check its selected lane, reason, and candidate counts before concluding that a memory is absent, and use `sourceContext.locator.command` for exact wording. Raw text fallback searches the fully scoped ledger and prefers original user anchors over later assistant retellings when cue scores tie. For old-discussion questions, run `cogmem memory recall --query "<past discussion>" --intent historical_discussion --project hermes --agent hermes --json`, then follow `sourceLocator` or inspect `cogmem memory list --project hermes --since <globalSeq> --order asc --json`.
95
107
 
96
108
  Dream stores explicit user clarification as organizational correction evidence rather than an automatic contradiction. Assistant self-correction and negative-form questions do not create user-owned corrections. Resolve `needs_confirmation` with `cogmem_candidate_review` or `cogmem memory review`; maintenance only supersedes entries left stale past the default 30-day TTL.
97
109
 
98
- After upgrades, reload MCP. Rerun `cogmem connect hermes --workspace . --auto --force` when MCP wiring, allow-listed tools, or the installed skill bundle changed.
110
+ After upgrades, reload MCP. Rerun `cogmem connect hermes --workspace . --auto --force --json` when MCP wiring, allow-listed tools, or the installed skill bundle changed. Follow only JSON `nextCommands` for unattended agent work; operator/host actions are in `nextSteps` with `safeForAutomation=false`.
99
111
 
100
- Cogmem 3.6.3 exposes seven read-only/idempotent Memory Atlas query tools plus explicit `cogmem_graph_touch`, and installs from npm by default. Use explore for broad memory inventory/history, search and node for a known concept, path/neighbors for relations, timeline for ordered reconstruction, and normal recall for a direct fact. Query facets combine the user's actual project, time, topic, entity/target, memory-kind, action, and keyword conditions like table filters, so cold memory can be revived without requiring an entity-time-action tuple. Touch only nodes actually used, and follow returned event IDs to raw evidence before quoting exact wording.
112
+ Cogmem 3.6.5 exposes seven read-only/idempotent Memory Atlas query tools plus explicit `cogmem_graph_touch`, installs from npm by default, prevents empty imported episodes from blocking Dream, and adds an agent-safe operations protocol. Use `memory plan` for the next safe command, grouped `memory candidates --json` for queue state, `historical_discussion` recall for old-discussion questions, and returned `sourceLocator` commands for exact evidence. Only run `dream_tick` when it appears in `memory plan.nextActions`; `raw_dream_ledger_lag` in `nonBlocking` has `resolvableByDreamTick:false` and is not fixed by looping `dream tick`. Use explore for broad memory inventory/history, search and node for a known concept, path/neighbors for relations, timeline for ordered reconstruction, and normal recall for a direct fact. Query facets combine the user's actual project, time, topic, entity/target, memory-kind, action, and keyword conditions like table filters, so cold memory can be revived without requiring an entity-time-action tuple. Touch only nodes actually used, and follow returned event IDs or `sourceLocator` commands to raw evidence before quoting exact wording.
101
113
 
102
114
  ## Runtime
103
115
 
@@ -155,7 +167,7 @@ cogmem memory map --project hermes --json
155
167
  cogmem memory tick --project hermes --json
156
168
  cogmem memory bind --project hermes --json
157
169
  cogmem episode status --project hermes --json
158
- cogmem dream tick --project hermes --mode auto --json
170
+ cogmem dream tick --project hermes --mode auto --max-episodes 20 --json
159
171
  ```
160
172
 
161
173
  `memory map` includes Memory Binding and Graph Recall counters. Bindings attach valuable user raw events to stable topic/entity paths, fuse same-claim evidence into claim-key clusters, and create graph anchors for raw-ledger drill-down; they are not verified long-term facts. If `memory tick` suggests `bind_raw_events`, run `memory bind` to backfill imported Hermes raw user events into the binding graph.