cogmem 3.7.1 → 3.7.3

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 (87) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/MEMORY_ATLAS.md +2 -2
  3. package/MEMORY_MODEL.md +1 -1
  4. package/README.md +6 -5
  5. package/RELEASE_CHECKLIST.md +5 -5
  6. package/dist/atlas/MemoryAtlasService.d.ts +1 -0
  7. package/dist/atlas/MemoryAtlasService.js +36 -4
  8. package/dist/atlas/MemoryAtlasTypes.d.ts +7 -0
  9. package/dist/belief/BeliefStore.d.ts +3 -1
  10. package/dist/belief/BeliefStore.js +10 -2
  11. package/dist/bin/episode.js +107 -16
  12. package/dist/bin/import-support.js +28 -11
  13. package/dist/config/CogmemConfig.d.ts +1 -0
  14. package/dist/config/CogmemConfig.js +45 -0
  15. package/dist/core/MemoryGraph.js +25 -8
  16. package/dist/dream/DreamScheduler.d.ts +6 -1
  17. package/dist/dream/DreamScheduler.js +36 -6
  18. package/dist/engine/CognitiveGraphCompiler.js +1 -1
  19. package/dist/engine/ConsolidationPipeline.js +4 -4
  20. package/dist/engine/DeepWritePromotionPolicy.d.ts +2 -0
  21. package/dist/engine/DeepWritePromotionPolicy.js +25 -4
  22. package/dist/engine/DreamCuratorWorker.d.ts +3 -0
  23. package/dist/engine/DreamCuratorWorker.js +18 -3
  24. package/dist/engine/FactCompiler.js +2 -2
  25. package/dist/entity/EntityGovernanceService.js +1 -1
  26. package/dist/episode/EpisodeActiveScopeGuard.d.ts +3 -0
  27. package/dist/episode/EpisodeActiveScopeGuard.js +46 -0
  28. package/dist/episode/EpisodeAssembler.d.ts +20 -2
  29. package/dist/episode/EpisodeAssembler.js +458 -98
  30. package/dist/episode/EpisodeBoundaryAuditService.d.ts +70 -0
  31. package/dist/episode/EpisodeBoundaryAuditService.js +276 -0
  32. package/dist/episode/EpisodeBoundaryPolicy.d.ts +75 -0
  33. package/dist/episode/EpisodeBoundaryPolicy.js +189 -0
  34. package/dist/episode/EpisodeBoundaryReplayEngine.d.ts +65 -0
  35. package/dist/episode/EpisodeBoundaryReplayEngine.js +272 -0
  36. package/dist/episode/EpisodeImportIdentity.d.ts +1 -0
  37. package/dist/episode/EpisodeImportIdentity.js +1 -0
  38. package/dist/episode/EpisodeInvariantValidator.d.ts +16 -0
  39. package/dist/episode/EpisodeInvariantValidator.js +99 -0
  40. package/dist/episode/EpisodeSplitPlanner.d.ts +89 -0
  41. package/dist/episode/EpisodeSplitPlanner.js +225 -0
  42. package/dist/episode/EpisodeStore.d.ts +67 -2
  43. package/dist/episode/EpisodeStore.js +443 -59
  44. package/dist/episode/EpisodeTypes.d.ts +2 -2
  45. package/dist/episode/TurnRelationClassifier.d.ts +9 -0
  46. package/dist/episode/TurnRelationClassifier.js +42 -12
  47. package/dist/episode/index.d.ts +3 -0
  48. package/dist/episode/index.js +3 -0
  49. package/dist/factory.d.ts +62 -3
  50. package/dist/factory.js +308 -58
  51. package/dist/mcp/CoreMcpTools.js +103 -8
  52. package/dist/mcp/server.js +1 -1
  53. package/dist/migrations/0028_episode_boundary_guardrails.d.ts +3 -0
  54. package/dist/migrations/0028_episode_boundary_guardrails.js +47 -0
  55. package/dist/migrations/0029_episode_active_scope_guard.d.ts +3 -0
  56. package/dist/migrations/0029_episode_active_scope_guard.js +16 -0
  57. package/dist/migrations/0030_memory_event_local_date_source.d.ts +3 -0
  58. package/dist/migrations/0030_memory_event_local_date_source.js +111 -0
  59. package/dist/migrations/0031_episode_boundary_integrity_repair.d.ts +7 -0
  60. package/dist/migrations/0031_episode_boundary_integrity_repair.js +124 -0
  61. package/dist/migrations/SchemaMigrationRunner.d.ts +2 -0
  62. package/dist/migrations/SchemaMigrationRunner.js +46 -15
  63. package/dist/migrations/index.d.ts +6 -1
  64. package/dist/migrations/index.js +15 -1
  65. package/dist/public.d.ts +1 -1
  66. package/dist/public.js +1 -1
  67. package/dist/recall/BrainRecall.js +1 -1
  68. package/dist/store/DeepWriteCandidateStore.d.ts +21 -3
  69. package/dist/store/DeepWriteCandidateStore.js +118 -11
  70. package/dist/store/EntityStore.d.ts +4 -0
  71. package/dist/store/EntityStore.js +24 -12
  72. package/dist/store/EventStore.d.ts +7 -2
  73. package/dist/store/EventStore.js +144 -23
  74. package/dist/store/FactStore.js +6 -5
  75. package/dist/store/MemoryAtlasStore.d.ts +1 -0
  76. package/dist/store/MemoryAtlasStore.js +54 -0
  77. package/dist/types/ExtensionPoints.d.ts +3 -1
  78. package/dist/types/index.d.ts +1 -0
  79. package/examples/hermes-backend/AGENTS.md +1 -1
  80. package/examples/hermes-backend/README.md +1 -1
  81. package/examples/hermes-backend/SKILL.md +2 -2
  82. package/examples/hermes-backend/references/operations.md +2 -2
  83. package/examples/openclaw-backend/AGENTS.md +1 -1
  84. package/examples/openclaw-backend/README.md +1 -1
  85. package/examples/openclaw-backend/SKILL.md +2 -2
  86. package/examples/openclaw-backend/references/operations.md +2 -2
  87. package/package.json +1 -1
package/dist/factory.js CHANGED
@@ -48,7 +48,7 @@ import { ReEmbeddingPipeline } from './embedding/ReEmbeddingPipeline.js';
48
48
  import { TopicAliasRegistry, TopicGovernance, TopicPathRegistry as UserTopicPathRegistry, TopicRelationGraph } from './topic/index.js';
49
49
  import { CorrectionResolver } from './episode/CorrectionResolver.js';
50
50
  import { CandidateReviewService, MemoryGovernanceExecutor, MemoryGovernanceValidator, PiiRedactor, } from './governance/index.js';
51
- import { migration_0015, migration_0016, migration_0017, migration_0018, migration_0019, migration_0020, migration_0021, migration_0022, migration_0023, migration_0024, migration_0025, migration_0026, migration_0027, SchemaMigrationRunner } from './migrations/index.js';
51
+ import { ALL_MIGRATIONS, KERNEL_MIGRATIONS, SchemaMigrationRunner } from './migrations/index.js';
52
52
  import { EntityGovernanceService } from './entity/index.js';
53
53
  import { TemporalMemoryService } from './temporal/index.js';
54
54
  import { ContextCortex } from './context/index.js';
@@ -56,6 +56,10 @@ import { ProspectiveMemoryService } from './prospective/index.js';
56
56
  import { StrategyCortex } from './strategy/index.js';
57
57
  import { ContextOutcomeStore, MemoryUseJudge } from './eval/strategy/index.js';
58
58
  import { EpisodeAssembler, EpisodeStore } from './episode/index.js';
59
+ import { EpisodeBoundaryAuditService } from './episode/EpisodeBoundaryAuditService.js';
60
+ import { EpisodeBoundaryPolicy, normalizeEpisodeBoundaryConfig } from './episode/EpisodeBoundaryPolicy.js';
61
+ import { logicalTurnsFromPairs } from './episode/EpisodeBoundaryReplayEngine.js';
62
+ import { EpisodeSplitPlanner } from './episode/EpisodeSplitPlanner.js';
59
63
  import { DreamScheduler } from './dream/index.js';
60
64
  import { loadCogmemConfig, resolveCogmemConfigPath, } from './config/CogmemConfig.js';
61
65
  import { ModelRegistry } from './models/ModelRegistry.js';
@@ -83,8 +87,8 @@ import { SqliteVecStore } from './store/SqliteVecStore.js';
83
87
  import { VectorStore } from './store/VectorStore.js';
84
88
  import { config } from './utils/Config.js';
85
89
  import { KernelRunningError, SnapshotExporter, SnapshotImporter, } from './snapshot/index.js';
86
- const CORE_VERSION = '3.7.1';
87
- const LATEST_SCHEMA_VERSION = 27;
90
+ const CORE_VERSION = '3.7.3';
91
+ const LATEST_SCHEMA_VERSION = Math.max(...ALL_MIGRATIONS.map((migration) => Number.parseInt(migration.version, 10)));
88
92
  export class MemoryKernel {
89
93
  options;
90
94
  memoryGraph;
@@ -117,10 +121,14 @@ export class MemoryKernel {
117
121
  pipelineMetrics;
118
122
  episodeStore;
119
123
  episodeAssembler;
124
+ episodeBoundaryPolicy;
125
+ episodeBoundaryAuditService;
126
+ episodeSplitPlanner;
120
127
  userTopicPathRegistry;
121
128
  topicAliasRegistry;
122
129
  topicRelationGraph;
123
130
  topicGovernance;
131
+ configDiagnostics;
124
132
  dbPath;
125
133
  embedder;
126
134
  embeddingProvider;
@@ -160,21 +168,27 @@ export class MemoryKernel {
160
168
  closed = false;
161
169
  constructor(options = {}) {
162
170
  this.options = options;
171
+ this.configDiagnostics = [...(options.configDiagnostics || [])];
172
+ const normalizedBoundary = normalizeEpisodeBoundaryConfig(options.episodeBoundary);
173
+ this.configDiagnostics.push(...normalizedBoundary.diagnostics);
163
174
  this.dbPath = options.dbPath ?? ':memory:';
164
175
  this.encryptionProvider = options.encryptionProvider;
165
176
  this.piiRedactor = options.redactionPolicy === false ? undefined : new PiiRedactor(options.redactionPolicy);
166
177
  this.memoryGraph = new MemoryGraph(this.dbPath);
167
- this.eventStore = new EventStore(this.dbPath, this.encryptionProvider);
168
178
  this.factStore = new FactStore(this.dbPath, this.encryptionProvider);
169
179
  const db = this.factStore.getDatabase();
170
- db.exec('PRAGMA busy_timeout = 5000;');
171
- new SchemaMigrationRunner(db, [migration_0015, migration_0016, migration_0017, migration_0018, migration_0019, migration_0020, migration_0021, migration_0022, migration_0023, migration_0024, migration_0025, migration_0026, migration_0027]).run();
180
+ this.eventStore = new EventStore(db, this.encryptionProvider);
181
+ db.exec('PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 5000;');
182
+ if (db.prepare('PRAGMA foreign_keys').get()?.foreign_keys !== 1) {
183
+ throw new Error('memory_kernel_foreign_keys_disabled');
184
+ }
185
+ new SchemaMigrationRunner(db, KERNEL_MIGRATIONS).run();
172
186
  this.ensureMetaTable(db);
173
187
  this.entityStore = new EntityStore(db);
174
188
  this.ensureGovernanceAuditTable(db);
175
189
  const vectorDimension = options.vectorDimension ?? config.vector.dimension;
176
190
  this.modelRegistry = options.modelRegistry ?? ModelRegistry.defaults();
177
- this.beliefStore = new BeliefStore(this.dbPath, this.eventStore);
191
+ this.beliefStore = new BeliefStore(db, this.eventStore);
178
192
  this.beliefGovernanceService = new BeliefGovernanceService(db, (eventId) => {
179
193
  const event = this.eventStore.getEvent(eventId);
180
194
  return event ? { eventId, projectId: event.projectId, role: event.role } : undefined;
@@ -207,6 +221,9 @@ export class MemoryKernel {
207
221
  this.neuronEmbeddingStore = new NeuronEmbeddingStore(db);
208
222
  this.dreamLedgerStore = new DreamLedgerStore(db);
209
223
  this.episodeStore = new EpisodeStore(db, (eventId) => this.eventStore.getEvent(eventId), { initializeSchemaForTests: false });
224
+ this.episodeBoundaryPolicy = new EpisodeBoundaryPolicy(normalizedBoundary.config);
225
+ this.episodeBoundaryAuditService = new EpisodeBoundaryAuditService(this.episodeStore, (eventId) => this.eventStore.getEvent(eventId), this.episodeBoundaryPolicy.config, this.configDiagnostics);
226
+ this.episodeSplitPlanner = new EpisodeSplitPlanner(this.episodeStore, (eventId) => this.eventStore.getEvent(eventId), this.episodeBoundaryPolicy.config, this.configDiagnostics);
210
227
  this.userTopicPathRegistry = new UserTopicPathRegistry(db);
211
228
  this.topicAliasRegistry = new TopicAliasRegistry(db);
212
229
  this.topicRelationGraph = new TopicRelationGraph(db);
@@ -223,7 +240,7 @@ export class MemoryKernel {
223
240
  topicPathMatch: Boolean(episode?.topicPath && matchedPaths.includes(episode.topicPath)),
224
241
  currentTopicPath: matchedPaths.length === 1 ? matchedPaths[0] : undefined,
225
242
  };
226
- });
243
+ }, this.episodeBoundaryPolicy);
227
244
  this.activationStore = new ActivationStore(db);
228
245
  this.memoryBindingStore = new MemoryBindingStore(db);
229
246
  this.memoryBindingService = new MemoryBindingService(this.memoryBindingStore, this.entityStore);
@@ -306,7 +323,7 @@ export class MemoryKernel {
306
323
  statement: `${belief.subject} ${belief.predicate} ${String(belief.objectValue)}`, projectId: belief.projectId,
307
324
  })))),
308
325
  });
309
- this.dreamScheduler = new DreamScheduler(this.episodeStore, this.dreamCuratorWorker);
326
+ this.dreamScheduler = new DreamScheduler(this.episodeStore, this.dreamCuratorWorker, this.deepWriteCandidateStore);
310
327
  this.topicSummaryBoard = new TopicSummaryBoard(this.memoryGraph, this.summaryStore);
311
328
  this.topicDecayPolicy = new TopicDecayPolicy(this.memoryGraph);
312
329
  this.localSemanticCompiler = new LocalSemanticCompiler();
@@ -579,6 +596,7 @@ export class MemoryKernel {
579
596
  threadId: input.threadId,
580
597
  sessionId: input.sessionId,
581
598
  localDate: input.localDate,
599
+ localDateSource: input.localDateSource ?? (input.localDate ? 'explicit' : 'generated_utc'),
582
600
  turnId: input.turnId,
583
601
  turnSeq: input.turnSeq,
584
602
  eventOrdinal: input.eventOrdinal,
@@ -592,7 +610,8 @@ export class MemoryKernel {
592
610
  charStart: input.charStart,
593
611
  charEnd: input.charEnd,
594
612
  occurredAt,
595
- orderingConfidence: 'high',
613
+ orderingConfidence: input.orderingConfidence
614
+ ?? (input.turnSeq !== undefined || input.eventOrdinal !== undefined || input.sourceOffset !== undefined || input.lineStart !== undefined ? 'high' : 'low'),
596
615
  payload: {
597
616
  text,
598
617
  metadata: input.metadata,
@@ -816,7 +835,7 @@ export class MemoryKernel {
816
835
  const existingEvent = reservedEventId ? this.eventStore.getEvent(reservedEventId) : null;
817
836
  if (existingEvent) {
818
837
  this.assertEpisodeIngestIdentity(existingEvent, input);
819
- return this.resumeEpisodeMessage(existingEvent, input, false);
838
+ return this.finishIngestMessage(existingEvent, input, false, () => this.resumeEpisodeMessage(existingEvent, input, false));
820
839
  }
821
840
  }
822
841
  let event;
@@ -827,6 +846,10 @@ export class MemoryKernel {
827
846
  workspaceId: input.projectId,
828
847
  threadId: input.threadId || input.sessionId,
829
848
  sessionId: input.sessionId,
849
+ turnId: input.turnId,
850
+ turnSeq: input.turnSeq,
851
+ localDate: input.localDate,
852
+ eventOrdinal: input.eventOrdinal,
830
853
  role: input.role,
831
854
  content: input.text,
832
855
  occurredAt: input.timestamp,
@@ -845,19 +868,9 @@ export class MemoryKernel {
845
868
  throw error;
846
869
  }
847
870
  this.assertEpisodeIngestIdentity(concurrent, input);
848
- if (input.externalMessageId)
849
- this.episodeStore.markIngestState({
850
- projectId: input.projectId, sourceAgent: input.sourceAgent, sourceSessionId: input.sessionId,
851
- externalMessageId: input.externalMessageId, state: 'committed',
852
- });
853
- return this.resumeEpisodeMessage(concurrent, input, false);
871
+ return this.finishIngestMessage(concurrent, input, false, () => this.resumeEpisodeMessage(concurrent, input, false));
854
872
  }
855
- if (input.externalMessageId)
856
- this.episodeStore.markIngestState({
857
- projectId: input.projectId, sourceAgent: input.sourceAgent, sourceSessionId: input.sessionId,
858
- externalMessageId: input.externalMessageId, state: 'committed', now: event.occurredAt,
859
- });
860
- return this.resumeEpisodeMessage(event, input, true);
873
+ return this.finishIngestMessage(event, input, true, () => this.resumeEpisodeMessage(event, input, true));
861
874
  }
862
875
  async appendEpisodeMessageAsync(input) {
863
876
  let reservedEventId;
@@ -873,7 +886,7 @@ export class MemoryKernel {
873
886
  const existingEvent = reservedEventId ? this.eventStore.getEvent(reservedEventId) : null;
874
887
  if (existingEvent) {
875
888
  this.assertEpisodeIngestIdentity(existingEvent, input);
876
- return this.resumeEpisodeMessageAsync(existingEvent, input, false);
889
+ return this.finishIngestMessageAsync(existingEvent, input, false, () => this.resumeEpisodeMessageAsync(existingEvent, input, false));
877
890
  }
878
891
  }
879
892
  let event;
@@ -881,6 +894,7 @@ export class MemoryKernel {
881
894
  event = this.recordRawEvent({
882
895
  eventId: reservedEventId, projectId: input.projectId, workspaceId: input.projectId,
883
896
  threadId: input.threadId || input.sessionId, sessionId: input.sessionId, role: input.role,
897
+ turnId: input.turnId, turnSeq: input.turnSeq, localDate: input.localDate, eventOrdinal: input.eventOrdinal,
884
898
  content: input.text, occurredAt: input.timestamp, sourceId: `${input.sourceAgent}:${input.sessionId}`,
885
899
  metadata: { ...input.metadata, externalMessageId: input.externalMessageId, sourceAgent: input.sourceAgent },
886
900
  });
@@ -896,25 +910,54 @@ export class MemoryKernel {
896
910
  throw error;
897
911
  }
898
912
  this.assertEpisodeIngestIdentity(concurrent, input);
913
+ return this.finishIngestMessageAsync(concurrent, input, false, () => this.resumeEpisodeMessageAsync(concurrent, input, false));
914
+ }
915
+ return this.finishIngestMessageAsync(event, input, true, () => this.resumeEpisodeMessageAsync(event, input, true));
916
+ }
917
+ finishIngestMessage(event, input, created, operation) {
918
+ try {
919
+ const result = operation();
899
920
  if (input.externalMessageId)
900
921
  this.episodeStore.markIngestState({
901
922
  projectId: input.projectId, sourceAgent: input.sourceAgent, sourceSessionId: input.sessionId,
902
- externalMessageId: input.externalMessageId, state: 'committed',
923
+ externalMessageId: input.externalMessageId, state: 'committed', now: event.occurredAt,
903
924
  });
904
- return this.resumeEpisodeMessageAsync(concurrent, input, false);
925
+ return result;
926
+ }
927
+ catch (error) {
928
+ if (input.externalMessageId)
929
+ this.episodeStore.markIngestState({
930
+ projectId: input.projectId, sourceAgent: input.sourceAgent, sourceSessionId: input.sessionId,
931
+ externalMessageId: input.externalMessageId, state: 'failed', error: error instanceof Error ? error.message : String(error), now: event.occurredAt,
932
+ });
933
+ throw error;
934
+ }
935
+ }
936
+ async finishIngestMessageAsync(event, input, created, operation) {
937
+ try {
938
+ const result = await operation();
939
+ if (input.externalMessageId)
940
+ this.episodeStore.markIngestState({
941
+ projectId: input.projectId, sourceAgent: input.sourceAgent, sourceSessionId: input.sessionId,
942
+ externalMessageId: input.externalMessageId, state: 'committed', now: event.occurredAt,
943
+ });
944
+ return result;
945
+ }
946
+ catch (error) {
947
+ if (input.externalMessageId)
948
+ this.episodeStore.markIngestState({
949
+ projectId: input.projectId, sourceAgent: input.sourceAgent, sourceSessionId: input.sessionId,
950
+ externalMessageId: input.externalMessageId, state: 'failed', error: error instanceof Error ? error.message : String(error), now: event.occurredAt,
951
+ });
952
+ throw error;
905
953
  }
906
- if (input.externalMessageId)
907
- this.episodeStore.markIngestState({
908
- projectId: input.projectId, sourceAgent: input.sourceAgent, sourceSessionId: input.sessionId,
909
- externalMessageId: input.externalMessageId, state: 'committed', now: event.occurredAt,
910
- });
911
- return this.resumeEpisodeMessageAsync(event, input, true);
912
954
  }
913
955
  resumeEpisodeMessage(event, input, created) {
914
956
  let link = this.episodeStore.getEventLink(event.eventId);
915
957
  let ignored = this.episodeStore.hasEventDisposition(event.eventId);
958
+ let assembly;
916
959
  if (!link && !ignored) {
917
- const assembly = this.assembleEpisodeTurn([event], {
960
+ assembly = this.assembleEpisodeTurn([event], {
918
961
  projectId: input.projectId,
919
962
  sessionId: event.sessionId || input.sessionId,
920
963
  sourceAgent: input.sourceAgent,
@@ -937,13 +980,25 @@ export class MemoryKernel {
937
980
  sealed: episode?.status === 'sealed',
938
981
  dreamRecommended: Boolean(receipt?.dreamRecommended && !receipt.requiresReview && episode?.dreamStatus !== 'processed'),
939
982
  dreamRan: false,
983
+ boundaryTriggered: assembly?.boundaryTriggered,
984
+ boundaryDetected: assembly?.boundaryDetected,
985
+ boundaryApplied: assembly?.boundaryApplied,
986
+ boundaryMode: assembly?.boundaryMode,
987
+ boundaryDecisionId: assembly?.boundaryDecisionId,
988
+ boundaryGuardCodes: assembly?.boundaryGuardCodes,
989
+ boundaryAuditRecorded: assembly?.boundaryAuditRecorded,
990
+ boundaryAuditStatus: assembly?.boundaryAuditStatus,
991
+ previousEpisodeId: assembly?.previousEpisodeId,
992
+ reviewerRawResultStatus: assembly?.reviewerRawResultStatus,
993
+ warnings: assembly?.warnings,
940
994
  };
941
995
  }
942
996
  async resumeEpisodeMessageAsync(event, input, created) {
943
997
  let link = this.episodeStore.getEventLink(event.eventId);
944
998
  let ignored = this.episodeStore.hasEventDisposition(event.eventId);
999
+ let assembly;
945
1000
  if (!link && !ignored) {
946
- const assembly = await this.assembleEpisodeTurnAsync([event], {
1001
+ assembly = await this.assembleEpisodeTurnAsync([event], {
947
1002
  projectId: input.projectId, sessionId: event.sessionId || input.sessionId, sourceAgent: input.sourceAgent,
948
1003
  conversationThreadId: input.threadId || event.threadId || input.sessionId, now: event.occurredAt,
949
1004
  });
@@ -959,6 +1014,17 @@ export class MemoryKernel {
959
1014
  sealed: episode?.status === 'sealed',
960
1015
  dreamRecommended: Boolean(receipt?.dreamRecommended && !receipt.requiresReview && episode?.dreamStatus !== 'processed'),
961
1016
  dreamRan: false,
1017
+ boundaryTriggered: assembly?.boundaryTriggered,
1018
+ boundaryDetected: assembly?.boundaryDetected,
1019
+ boundaryApplied: assembly?.boundaryApplied,
1020
+ boundaryMode: assembly?.boundaryMode,
1021
+ boundaryDecisionId: assembly?.boundaryDecisionId,
1022
+ boundaryGuardCodes: assembly?.boundaryGuardCodes,
1023
+ boundaryAuditRecorded: assembly?.boundaryAuditRecorded,
1024
+ boundaryAuditStatus: assembly?.boundaryAuditStatus,
1025
+ previousEpisodeId: assembly?.previousEpisodeId,
1026
+ reviewerRawResultStatus: assembly?.reviewerRawResultStatus,
1027
+ warnings: assembly?.warnings,
962
1028
  };
963
1029
  }
964
1030
  assertEpisodeIngestIdentity(event, input) {
@@ -966,7 +1032,15 @@ export class MemoryKernel {
966
1032
  const expectedText = this.piiRedactor ? this.piiRedactor.redact(input.text).text : input.text;
967
1033
  if (event.projectId !== input.projectId
968
1034
  || event.sessionId !== input.sessionId
1035
+ || event.threadId !== (input.threadId || input.sessionId)
969
1036
  || event.role !== input.role
1037
+ || !sameProvided(event.turnId, input.turnId)
1038
+ || !sameProvided(event.turnSeq, input.turnSeq)
1039
+ || !sameProvided(event.localDate, input.localDate)
1040
+ || !sameProvided(event.eventOrdinal, input.eventOrdinal)
1041
+ || (input.timestamp !== undefined && event.occurredAt !== input.timestamp)
1042
+ || (input.metadata?.imported !== undefined && payload?.metadata?.imported !== input.metadata.imported)
1043
+ || (input.metadata?.sourceRef !== undefined && JSON.stringify(payload?.metadata?.sourceRef) !== JSON.stringify(input.metadata.sourceRef))
970
1044
  || payload?.text !== expectedText
971
1045
  || payload?.metadata?.sourceAgent !== input.sourceAgent) {
972
1046
  throw new Error(`episode_ingest_identity_conflict:${input.externalMessageId || event.eventId}`);
@@ -1006,6 +1080,17 @@ export class MemoryKernel {
1006
1080
  listEpisodeEventLinks(episodeId) {
1007
1081
  return this.episodeStore.listEventLinks(episodeId);
1008
1082
  }
1083
+ auditEpisodeBoundaries(options) {
1084
+ return this.episodeBoundaryAuditService.audit(options);
1085
+ }
1086
+ listEpisodeBoundaryDecisions(options) {
1087
+ if (!options.projectId)
1088
+ throw new Error('projectId is required');
1089
+ return this.episodeStore.listBoundaryDecisions(options);
1090
+ }
1091
+ planEpisodeSplit(options) {
1092
+ return this.episodeSplitPlanner.plan(options);
1093
+ }
1009
1094
  getEpisodeDreamStatus(projectId) {
1010
1095
  return this.episodeStore.getDreamStatus(projectId);
1011
1096
  }
@@ -1013,18 +1098,24 @@ export class MemoryKernel {
1013
1098
  return this.episodeStore.retryFailed(projectId);
1014
1099
  }
1015
1100
  repairEpisodes(options = {}) {
1016
- const page = this.eventStore.queryEvents(1, Math.max(1, Math.min(options.limit ?? 500, 5000)), {
1017
- projectId: options.projectId ? [options.projectId] : undefined,
1018
- });
1019
- const events = page.records
1020
- .filter((event) => event.eventType === 'RAW_EVENT_RECORDED')
1021
- .filter((event) => options.sinceGlobalSeq === undefined || (event.globalSeq || 0) >= options.sinceGlobalSeq)
1101
+ const limit = Math.max(1, Math.min(Math.trunc(options.limit ?? 500), 5000));
1102
+ let afterGlobalSeq = options.sinceGlobalSeq === undefined ? undefined : options.sinceGlobalSeq - 1;
1103
+ const events = [];
1104
+ while (events.length < limit) {
1105
+ const page = this.eventStore.listRawEventsAfterGlobalSeq({ projectId: options.projectId, afterGlobalSeq, limit: Math.min(500, limit - events.length) });
1106
+ if (!page.length)
1107
+ break;
1108
+ events.push(...page);
1109
+ afterGlobalSeq = page.at(-1)?.globalSeq ?? afterGlobalSeq;
1110
+ if (page.length < Math.min(500, limit - events.length + page.length))
1111
+ break;
1112
+ }
1113
+ const eligibleEvents = events
1022
1114
  .filter((event) => !this.episodeStore.getEventLink(event.eventId))
1023
- .filter((event) => !this.episodeStore.hasEventDisposition(event.eventId))
1024
- .sort((a, b) => (a.globalSeq || 0) - (b.globalSeq || 0));
1115
+ .filter((event) => !this.episodeStore.hasEventDisposition(event.eventId));
1025
1116
  let assigned = 0;
1026
1117
  const unassignedEventIds = [];
1027
- for (const event of events) {
1118
+ for (const event of eligibleEvents) {
1028
1119
  const projectId = event.projectId || options.projectId;
1029
1120
  const sessionId = event.sessionId || event.threadId;
1030
1121
  if (!projectId || !sessionId) {
@@ -1049,9 +1140,13 @@ export class MemoryKernel {
1049
1140
  });
1050
1141
  }
1051
1142
  }
1052
- return { scanned: events.length, assigned, unassigned: unassignedEventIds.length, unassignedEventIds };
1143
+ return { scanned: eligibleEvents.length, assigned, unassigned: unassignedEventIds.length, unassignedEventIds, nextGlobalSeq: afterGlobalSeq, hasMore: events.length >= limit };
1053
1144
  }
1054
1145
  repairEpisode(input) {
1146
+ validateEpisodeRepairInput(input, this.episodeStore, (eventId) => this.eventStore.getEvent(eventId) ?? undefined);
1147
+ return this.episodeStore.transaction(() => this.repairEpisodeInTransaction(input));
1148
+ }
1149
+ repairEpisodeInTransaction(input) {
1055
1150
  const now = input.now ?? Date.now();
1056
1151
  const affected = new Set();
1057
1152
  const before = {};
@@ -1101,6 +1196,8 @@ export class MemoryKernel {
1101
1196
  }
1102
1197
  else if (input.operation === 'split') {
1103
1198
  const source = this.episodeStore.getEpisode(input.episodeId);
1199
+ if (source.status !== 'sealed')
1200
+ throw new Error('episode_split_requires_sealed_source');
1104
1201
  const sourceLinks = this.episodeStore.listEventLinks(input.episodeId);
1105
1202
  const selected = sourceLinks.filter((link) => input.eventIds.includes(link.eventId));
1106
1203
  if (!selected.length || selected.length === sourceLinks.length)
@@ -1157,19 +1254,37 @@ export class MemoryKernel {
1157
1254
  }
1158
1255
  }
1159
1256
  const staleCandidateIds = [];
1160
- for (const candidate of this.deepWriteCandidateStore.listCandidates({ projectId: input.projectId, limit: 5000 })) {
1161
- const content = candidate.content && typeof candidate.content === 'object' ? candidate.content : {};
1162
- if (!affected.has(String(content.sourceEpisodeId || '')) || candidate.status === 'superseded')
1163
- continue;
1164
- this.deepWriteCandidateStore.updateCandidateStatus(candidate.candidateId, 'superseded', {
1165
- type: candidate.candidateType, id: candidate.candidateId, reason: 'episode_repair_invalidated_source',
1166
- });
1167
- staleCandidateIds.push(candidate.candidateId);
1257
+ let candidateCursor;
1258
+ while (true) {
1259
+ const page = this.deepWriteCandidateStore.listCandidates({ projectId: input.projectId, limit: 500, after: candidateCursor });
1260
+ if (!page.length)
1261
+ break;
1262
+ for (const candidate of page) {
1263
+ const content = candidate.content && typeof candidate.content === 'object' ? candidate.content : {};
1264
+ const evidence = Array.isArray(candidate.evidence) ? candidate.evidence : [];
1265
+ const evidenceIds = evidence.flatMap((item) => item && typeof item === 'object' && typeof item.eventId === 'string'
1266
+ ? [String(item.eventId)] : typeof item === 'string' ? [item] : []);
1267
+ const runSourceEpisode = typeof content.sourceEpisodeId === 'string' ? content.sourceEpisodeId : undefined;
1268
+ if (candidate.status !== 'superseded' && ((runSourceEpisode && affected.has(runSourceEpisode))
1269
+ || evidenceIds.some((eventId) => affected.has(this.episodeStore.getEventLink(eventId)?.episodeId || '')))) {
1270
+ this.deepWriteCandidateStore.updateCandidateStatus(candidate.candidateId, 'superseded', {
1271
+ reason: 'episode_repair_invalidated_source',
1272
+ });
1273
+ this.invalidatePromotedCandidate(candidate, now);
1274
+ staleCandidateIds.push(candidate.candidateId);
1275
+ }
1276
+ }
1277
+ const last = page.at(-1);
1278
+ candidateCursor = { createdAt: last.createdAt, candidateId: last.candidateId };
1279
+ if (page.length < 500)
1280
+ break;
1168
1281
  }
1169
1282
  for (const episodeId of affected) {
1170
1283
  const episode = this.episodeStore.getEpisode(episodeId);
1171
- if (episode?.eventCount && episode.status === 'sealed') {
1172
- this.episodeStore.requeueDreamForRepair(episodeId, input.operation === 'invalidate-dream-run' ? input.mode || 'normal' : 'normal', now);
1284
+ if (episode?.eventCount && episode.status === 'sealed'
1285
+ && input.operation !== 'requeue-dream'
1286
+ && input.operation !== 'invalidate-dream-run') {
1287
+ this.episodeStore.requeueDreamForRepair(episodeId, 'normal', now);
1173
1288
  requeuedDream = true;
1174
1289
  }
1175
1290
  }
@@ -1177,7 +1292,7 @@ export class MemoryKernel {
1177
1292
  const repairId = this.episodeStore.recordRepairAudit({ projectId: input.projectId, operation: input.operation, payload: input, before, after, now });
1178
1293
  const affectedEpisodeIds = [...affected];
1179
1294
  const nextCommands = affectedEpisodeIds.length
1180
- ? affectedEpisodeIds.map((episodeId) => `cogmem memory graph-reindex --project ${input.projectId} --episode ${episodeId} --json`)
1295
+ ? affectedEpisodeIds.map((episodeId) => `cogmem memory graph-reindex --project ${shellQuote(input.projectId)} --episode ${shellQuote(episodeId)} --json`)
1181
1296
  : [];
1182
1297
  return {
1183
1298
  repairId,
@@ -1190,11 +1305,53 @@ export class MemoryKernel {
1190
1305
  graphRefreshNeeded: affectedEpisodeIds.length > 0,
1191
1306
  nextCommands: [
1192
1307
  ...nextCommands,
1193
- `cogmem memory graph-explore --project ${input.projectId} --query "<query>" --json`,
1308
+ `cogmem memory graph-explore --project ${shellQuote(input.projectId)} --query '<query>' --json`,
1194
1309
  ],
1195
1310
  note: 'repairId is an audit id, not a dream candidate id; do not run memory dream --promote just because repairId exists.',
1196
1311
  };
1197
1312
  }
1313
+ invalidatePromotedCandidate(candidate, now) {
1314
+ if (candidate.status !== 'promoted' || !candidate.promotionTargetId)
1315
+ return;
1316
+ const db = this.factStore.getDatabase();
1317
+ const targetId = candidate.promotionTargetId;
1318
+ if (candidate.promotionTargetType === 'fact') {
1319
+ this.factStore.updateFactStatus(targetId, 'superseded', undefined, { repairInvalidatedAt: now, sourceCandidateId: candidate.candidateId });
1320
+ }
1321
+ else if (candidate.promotionTargetType === 'belief') {
1322
+ db.prepare(`UPDATE beliefs SET status = 'suspect', updated_at = ? WHERE id = ?`).run(now, targetId);
1323
+ }
1324
+ else if (candidate.promotionTargetType === 'summary') {
1325
+ this.summaryStore.markSuperseded(targetId);
1326
+ }
1327
+ else if (candidate.promotionTargetType === 'entity') {
1328
+ const support = db.prepare(`
1329
+ SELECT (
1330
+ (SELECT COUNT(*) FROM facts WHERE entity_id = ? AND status IN ('provisional', 'provisional_enriched', 'verified', 'active'))
1331
+ + (SELECT COUNT(*) FROM beliefs WHERE subject = ? AND status IN ('active', 'verified'))
1332
+ + (SELECT COUNT(*) FROM entity_mentions WHERE entity_id = ?)
1333
+ + (SELECT COUNT(*) FROM entity_attributes WHERE entity_id = ?)
1334
+ + (SELECT COUNT(*) FROM entity_relations WHERE source_entity_id = ? OR target_entity_id = ?)
1335
+ + ?
1336
+ ) AS count
1337
+ `).get(targetId, targetId, targetId, targetId, targetId, targetId, this.deepWriteCandidateStore.countActivePromotions('entity', targetId, candidate.candidateId));
1338
+ if (Number(support?.count || 0) === 0) {
1339
+ this.entityStore.archiveEntity(targetId, now);
1340
+ }
1341
+ }
1342
+ else if (candidate.promotionTargetType === 'graph_edge') {
1343
+ const relationStore = this.extensions.get('relationStore');
1344
+ if (!relationStore || relationStore.getDatabase() !== this.episodeStore.getDatabase()) {
1345
+ throw new Error(`graph_edge_invalidation_failed:${targetId}`);
1346
+ }
1347
+ const invalidated = relationStore.invalidateEdge(targetId, {
1348
+ repairInvalidatedAt: now,
1349
+ sourceCandidateId: candidate.candidateId,
1350
+ });
1351
+ if (invalidated !== true)
1352
+ throw new Error(`graph_edge_invalidation_failed:${targetId}`);
1353
+ }
1354
+ }
1198
1355
  listDreamCandidates(options = {}) {
1199
1356
  return this.deepWriteCandidateStore.listCandidates(options);
1200
1357
  }
@@ -1380,6 +1537,7 @@ export class MemoryKernel {
1380
1537
  return count;
1381
1538
  }
1382
1539
  promoteDreamCandidates(options = {}) {
1540
+ this.deepWriteCandidateStore.recoverStalePromoting(Date.now() - 5 * 60_000, Date.now());
1383
1541
  const decisions = this.deepWritePromotionPolicy.promotePending(options.limit ?? 100, {
1384
1542
  projectId: options.projectId,
1385
1543
  });
@@ -1603,7 +1761,7 @@ export class MemoryKernel {
1603
1761
  if (!projectId)
1604
1762
  return true;
1605
1763
  return conflict.entityIds.some((entityId) => {
1606
- const entity = this.entityStore.findByEntityId(entityId);
1764
+ const entity = this.entityStore.getByEntityId(entityId);
1607
1765
  return entity?.metadata?.projectId === projectId
1608
1766
  || this.entityStore.listTimeline({ entityId, projectId, limit: 1 }).length > 0;
1609
1767
  });
@@ -1929,6 +2087,12 @@ export class MemoryKernel {
1929
2087
  }
1930
2088
  registerExtension(name, implementation) {
1931
2089
  this.extensions.set(name, implementation);
2090
+ if (name === 'relationStore') {
2091
+ const store = implementation;
2092
+ if (store.getDatabase() !== this.episodeStore.getDatabase())
2093
+ throw new Error('relation_store_database_mismatch');
2094
+ this.deepWritePromotionPolicy.setRelationStore(store);
2095
+ }
1932
2096
  }
1933
2097
  hasExtension(name) {
1934
2098
  return this.extensions.has(name);
@@ -2023,7 +2187,12 @@ export function createMemoryKernelFromConfig(input = {}) {
2023
2187
  if (error)
2024
2188
  throw new Error(`${error.code}: ${error.message}`);
2025
2189
  const { configPath: _configPath, cwd: _cwd, env: _env, ...explicitOptions } = options;
2026
- return createMemoryKernel({ ...loaded.options, ...explicitOptions });
2190
+ return createMemoryKernel({
2191
+ ...loaded.options,
2192
+ ...explicitOptions,
2193
+ episodeBoundary: { ...loaded.options.episodeBoundary, ...explicitOptions.episodeBoundary },
2194
+ configDiagnostics: [...loaded.diagnostics, ...(explicitOptions.configDiagnostics || [])],
2195
+ });
2027
2196
  }
2028
2197
  function changedFieldsForRepair(input) {
2029
2198
  if (input.operation === 'reclassify') {
@@ -2041,6 +2210,84 @@ function changedFieldsForRepair(input) {
2041
2210
  return ['dreamQueue'];
2042
2211
  return [];
2043
2212
  }
2213
+ function validateEpisodeRepairInput(input, store, resolveEvent) {
2214
+ if (!input.projectId?.trim())
2215
+ throw new Error('episode_project_required');
2216
+ if (input.operation === 'reclassify' && input.importance !== undefined
2217
+ && (!Number.isFinite(input.importance) || input.importance < 0 || input.importance > 1)) {
2218
+ throw new Error('episode_importance_out_of_range');
2219
+ }
2220
+ if (input.operation === 'split') {
2221
+ if (!input.eventIds.length)
2222
+ throw new Error('episode_split_requires_event_ids');
2223
+ if (new Set(input.eventIds).size !== input.eventIds.length)
2224
+ throw new Error('episode_split_duplicate_event_ids');
2225
+ const source = store.getEpisode(input.episodeId);
2226
+ if (!source || source.projectId !== input.projectId)
2227
+ throw new Error(`episode_project_mismatch:${input.episodeId}`);
2228
+ if (source.status !== 'sealed')
2229
+ throw new Error('episode_split_requires_sealed_source');
2230
+ const sourceIds = new Set(store.listEventLinks(input.episodeId).map((link) => link.eventId));
2231
+ if (input.eventIds.some((eventId) => !sourceIds.has(eventId)))
2232
+ throw new Error('episode_split_event_not_in_source');
2233
+ if (input.eventIds.length >= sourceIds.size)
2234
+ throw new Error('episode_split_requires_proper_subset');
2235
+ const links = store.listEventLinks(input.episodeId);
2236
+ const selected = new Set(input.eventIds);
2237
+ const indices = links.map((link, index) => selected.has(link.eventId) ? index : -1).filter((index) => index >= 0);
2238
+ const first = indices[0];
2239
+ const last = indices.at(-1);
2240
+ if (first === undefined || last === undefined || last - first + 1 !== indices.length) {
2241
+ throw new Error('episode_split_requires_contiguous_range');
2242
+ }
2243
+ const pairs = links.map((link) => ({ link, event: resolveEvent(link.eventId) }));
2244
+ const turns = logicalTurnsFromPairs(pairs);
2245
+ if (turns.some((turn) => {
2246
+ const included = turn.filter((pair) => selected.has(pair.link.eventId)).length;
2247
+ return included > 0 && included !== turn.length;
2248
+ }))
2249
+ throw new Error('episode_split_requires_complete_logical_turns');
2250
+ for (const eventId of selected) {
2251
+ const event = resolveEvent(eventId);
2252
+ if (event?.parentEventId && sourceIds.has(event.parentEventId) && !selected.has(event.parentEventId)) {
2253
+ throw new Error('episode_split_requires_complete_tool_chain');
2254
+ }
2255
+ const childInSource = links.some((link) => resolveEvent(link.eventId)?.parentEventId === eventId);
2256
+ if (childInSource && !links.filter((link) => resolveEvent(link.eventId)?.parentEventId === eventId).every((link) => selected.has(link.eventId))) {
2257
+ throw new Error('episode_split_requires_complete_tool_chain');
2258
+ }
2259
+ }
2260
+ if (!turns.some((turn) => turn.every((pair) => selected.has(pair.link.eventId)) && turn.some((pair) => pair.event?.role === 'user'))) {
2261
+ throw new Error('episode_split_requires_primary_user_turn');
2262
+ }
2263
+ }
2264
+ if (input.operation === 'move-event') {
2265
+ const link = store.getEventLink(input.eventId);
2266
+ if (link?.episodeId === input.targetEpisodeId)
2267
+ throw new Error('episode_move_source_equals_target');
2268
+ const source = link ? store.getEpisode(link.episodeId) : undefined;
2269
+ const target = store.getEpisode(input.targetEpisodeId);
2270
+ if (!source || !target || source.projectId !== input.projectId || target.projectId !== input.projectId)
2271
+ throw new Error('episode_project_mismatch');
2272
+ if (source.sessionId !== target.sessionId || source.sourceAgent !== target.sourceAgent || source.conversationThreadId !== target.conversationThreadId) {
2273
+ throw new Error('episode_move_scope_mismatch');
2274
+ }
2275
+ }
2276
+ if (input.operation === 'merge') {
2277
+ if (input.sourceEpisodeId === input.targetEpisodeId)
2278
+ throw new Error('episode_merge_source_equals_target');
2279
+ const source = store.getEpisode(input.sourceEpisodeId);
2280
+ const target = store.getEpisode(input.targetEpisodeId);
2281
+ if (!source || !target || source.projectId !== input.projectId || target.projectId !== input.projectId)
2282
+ throw new Error('episode_project_mismatch');
2283
+ if (source.sessionId !== target.sessionId || source.sourceAgent !== target.sourceAgent || source.conversationThreadId !== target.conversationThreadId) {
2284
+ throw new Error('episode_merge_scope_mismatch');
2285
+ }
2286
+ }
2287
+ }
2288
+ function shellQuote(value) {
2289
+ return `'${String(value).replace(/'/gu, `'\\''`)}'`;
2290
+ }
2044
2291
  function requiredGovernancePayloadString(payload, field) {
2045
2292
  const value = payload[field];
2046
2293
  if (typeof value !== 'string' || value.trim() === '')
@@ -2125,6 +2372,9 @@ function optionalGovernancePayloadNumber(payload, field) {
2125
2372
  function uniqueStrings(values) {
2126
2373
  return Array.from(new Set(values.filter(Boolean)));
2127
2374
  }
2375
+ function sameProvided(left, right) {
2376
+ return right === undefined || (left ?? undefined) === right;
2377
+ }
2128
2378
  function extractNavigationTerms(query) {
2129
2379
  return uniqueStrings(query
2130
2380
  .toLowerCase()