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
@@ -23,13 +23,18 @@ import { migration_0024 } from './0024_episode_ontology_reliability.js';
23
23
  import { migration_0025 } from './0025_memory_atlas.js';
24
24
  import { migration_0026 } from './0026_runtime_governance_atlas_reliability.js';
25
25
  import { migration_0027 } from './0027_openclaw_upgrade_hotfix.js';
26
+ import { migration_0028 } from './0028_episode_boundary_guardrails.js';
27
+ import { migration_0029 } from './0029_episode_active_scope_guard.js';
28
+ import { migration_0030 } from './0030_memory_event_local_date_source.js';
29
+ import { migration_0031 } from './0031_episode_boundary_integrity_repair.js';
26
30
  /**
27
31
  * Ordered list of all schema migrations.
28
32
  * Add new migrations here in ascending version order.
29
33
  * MigrationRunner.up() applies only the pending ones.
30
34
  */
31
35
  export declare const ALL_MIGRATIONS: Migration[];
32
- export { migration_0001, migration_0002, migration_0003, migration_0004, migration_0005, migration_0006, migration_0007, migration_0009, migration_0010, migration_0011, migration_0012, 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 };
36
+ export declare const KERNEL_MIGRATIONS: Migration[];
37
+ export { migration_0001, migration_0002, migration_0003, migration_0004, migration_0005, migration_0006, migration_0007, migration_0009, migration_0010, migration_0011, migration_0012, 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, migration_0028, migration_0029, migration_0030, migration_0031 };
33
38
  export { SchemaMigrationRunner } from './SchemaMigrationRunner.js';
34
39
  export type { SchemaMigrationResult, SchemaMigrationRunOptions } from './SchemaMigrationRunner.js';
35
40
  export type { Migration };
@@ -22,6 +22,10 @@ import { migration_0024 } from './0024_episode_ontology_reliability.js';
22
22
  import { migration_0025 } from './0025_memory_atlas.js';
23
23
  import { migration_0026 } from './0026_runtime_governance_atlas_reliability.js';
24
24
  import { migration_0027 } from './0027_openclaw_upgrade_hotfix.js';
25
+ import { migration_0028 } from './0028_episode_boundary_guardrails.js';
26
+ import { migration_0029 } from './0029_episode_active_scope_guard.js';
27
+ import { migration_0030 } from './0030_memory_event_local_date_source.js';
28
+ import { migration_0031 } from './0031_episode_boundary_integrity_repair.js';
25
29
  /**
26
30
  * Ordered list of all schema migrations.
27
31
  * Add new migrations here in ascending version order.
@@ -52,6 +56,16 @@ export const ALL_MIGRATIONS = [
52
56
  migration_0025,
53
57
  migration_0026,
54
58
  migration_0027,
59
+ migration_0028,
60
+ migration_0029,
61
+ migration_0030,
62
+ migration_0031,
55
63
  ];
56
- export { migration_0001, migration_0002, migration_0003, migration_0004, migration_0005, migration_0006, migration_0007, migration_0009, migration_0010, migration_0011, migration_0012, 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 };
64
+ // Core stores bootstrap the pre-0015 tables themselves. Running the early
65
+ // historical migrations against a fresh kernel would try to ALTER optional
66
+ // legacy tables that do not exist yet. The kernel still runs every migration
67
+ // from the first schema that owns the runtime tables through the latest one;
68
+ // the CLI remains able to replay the complete historical chain.
69
+ export const KERNEL_MIGRATIONS = ALL_MIGRATIONS.filter((migration) => Number.parseInt(migration.version, 10) >= 15);
70
+ export { migration_0001, migration_0002, migration_0003, migration_0004, migration_0005, migration_0006, migration_0007, migration_0009, migration_0010, migration_0011, migration_0012, 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, migration_0028, migration_0029, migration_0030, migration_0031 };
57
71
  export { SchemaMigrationRunner } from './SchemaMigrationRunner.js';
package/dist/public.d.ts CHANGED
@@ -16,7 +16,7 @@
16
16
  */
17
17
  export { MemoryKernel, createMemoryKernel, createMemoryKernelFromConfig, } from './factory.js';
18
18
  export type { ForgetUserResult, GovernanceAuditRecord, DreamCandidateListOptions, DreamCandidateRecord, DreamCandidateStatus, DreamCuratorRunOptions, DreamCuratorRunResult, DreamTickOptions, DreamTickResult, EpisodeClosureReceipt, EpisodeDreamStatus, EpisodeListOptions, EpisodeMessageInput, EpisodeMessageResult, EpisodeRepairInput, EpisodeRepairResult, MemoryEpisode, MaintenanceSuggestedAction, MaintenanceTickOptions, MaintenanceTickResult, MemoryDataLane, MemoryBindingBackfillOptions, MemoryBindingBackfillResult, MemoryKernelFromConfigOptions, MemoryKernelNavigationOptions, MemoryKernelNavigationResult, MemoryKernelOptions, MemoryMapOptions, MemoryMapSection, MemorySelfMap, RawEventSearchOptions, RawMemoryEventInput, TaskMemoryEventInput, ToolCallMemoryEventInput, ToolResultMemoryEventInput, } from './factory.js';
19
- export { EpisodeAssembler, EpisodeStore, classifyAssistantRelation, classifyTurnRelation, classifyTurnRelationHybrid, createStableImportIdentityFactory, summarizeEpisode, type EpisodeCandidateType, type EpisodeClosureMode, type EpisodeClosureReasonCode, type EpisodeDreamState, type EpisodeDreamStatusValue, type EpisodeEventLink, type EpisodeSemanticSummary, type EpisodeStatus, type EpisodeType, type StableImportIdentityInput, type TurnClassificationContext, type TurnRelation, type TurnRelationAdvisoryReviewer, type TurnRelationDecision, } from './episode/index.js';
19
+ export { EpisodeAssembler, EpisodeStore, classifyAssistantRelation, classifyTurnRelation, classifyTurnRelationHybrid, classifyTurnRelationHybridTrace, createStableImportIdentityFactory, summarizeEpisode, type EpisodeCandidateType, type EpisodeClosureMode, type EpisodeClosureReasonCode, type EpisodeDreamState, type EpisodeDreamStatusValue, type EpisodeEventLink, type EpisodeSemanticSummary, type EpisodeStatus, type EpisodeType, type EpisodeBoundaryAuditItem, type EpisodeBoundaryAuditResult, type EpisodeBoundaryConfig, type EpisodeBoundaryConfigDiagnostic, type EpisodeBoundaryDecisionRecord, type EpisodeBoundaryGuardCode, type EpisodeBoundaryGuardResult, type EpisodeBoundaryMetrics, type EpisodeBoundaryMode, type EpisodeBoundaryWarning, type EpisodeSplitImpactInventory, type EpisodeSplitPlan, type EpisodeSplitPlanSegment, type EpisodeSplitProposedBoundary, type StableImportIdentityInput, type TurnClassificationContext, type TurnRelation, type TurnRelationAdvisoryReviewer, type TurnRelationDecision, type TurnRelationHybridTrace, type TurnRelationReviewStatus, } from './episode/index.js';
20
20
  export { DreamScheduler, type DreamTickMode, type SelectedDreamMode } from './dream/index.js';
21
21
  export { MEMORY_ONTOLOGY_CLASSES, isMemoryOntologyClass, type MemoryOntologyClass } from './ontology/MemoryOntology.js';
22
22
  export { TopicAliasRegistry, TopicGovernance, TopicPathRegistry as UserTopicPathRegistry, TopicRelationGraph, type TopicAliasRecord, type TopicCreatedBy, type TopicNode, type TopicOperationInput, type TopicOperationRecord, type TopicOperationType, type TopicRelationRecord, type TopicStatus, } from './topic/index.js';
package/dist/public.js CHANGED
@@ -16,7 +16,7 @@
16
16
  * @stable @since 1.0.0
17
17
  */
18
18
  export { MemoryKernel, createMemoryKernel, createMemoryKernelFromConfig, } from './factory.js';
19
- export { EpisodeAssembler, EpisodeStore, classifyAssistantRelation, classifyTurnRelation, classifyTurnRelationHybrid, createStableImportIdentityFactory, summarizeEpisode, } from './episode/index.js';
19
+ export { EpisodeAssembler, EpisodeStore, classifyAssistantRelation, classifyTurnRelation, classifyTurnRelationHybrid, classifyTurnRelationHybridTrace, createStableImportIdentityFactory, summarizeEpisode, } from './episode/index.js';
20
20
  export { DreamScheduler } from './dream/index.js';
21
21
  export { MEMORY_ONTOLOGY_CLASSES, isMemoryOntologyClass } from './ontology/MemoryOntology.js';
22
22
  export { TopicAliasRegistry, TopicGovernance, TopicPathRegistry as UserTopicPathRegistry, TopicRelationGraph, } from './topic/index.js';
@@ -280,7 +280,7 @@ export class BrainRecall {
280
280
  if (!edge.fromNodeId || !edge.toNodeId || edge.fromNodeId === edge.toNodeId) {
281
281
  return 'ambiguous_edge_payload';
282
282
  }
283
- if (!this.deps.entityStore.findByEntityId(edge.fromNodeId) || !this.deps.entityStore.findByEntityId(edge.toNodeId)) {
283
+ if (!this.deps.entityStore.findActiveByEntityId(edge.fromNodeId) || !this.deps.entityStore.findActiveByEntityId(edge.toNodeId)) {
284
284
  return 'ambiguous_unknown_target_entity';
285
285
  }
286
286
  if (resolvedEntityIds.includes(edge.fromNodeId)) {
@@ -1,6 +1,6 @@
1
1
  import type Database from 'bun:sqlite';
2
- export type DeepWriteRunStatus = 'succeeded' | 'failed' | 'skipped';
3
- export type DeepWriteCandidateStatus = 'shadow' | 'candidate' | 'promoted' | 'rejected' | 'needs_confirmation' | 'superseded';
2
+ export type DeepWriteRunStatus = 'running' | 'staged' | 'succeeded' | 'failed' | 'skipped' | 'abandoned';
3
+ export type DeepWriteCandidateStatus = 'staged' | 'shadow' | 'candidate' | 'promoting' | 'promoted' | 'rejected' | 'needs_confirmation' | 'superseded';
4
4
  export interface DeepWriteRunInput {
5
5
  runId?: string;
6
6
  projectId?: string;
@@ -14,12 +14,18 @@ export interface DeepWriteRunInput {
14
14
  status: DeepWriteRunStatus;
15
15
  error?: string;
16
16
  createdAt?: number;
17
+ sourceEpisodeId?: string;
18
+ dreamJobLeaseId?: string;
19
+ leaseUntil?: number;
20
+ attemptGeneration?: number;
21
+ updatedAt?: number;
17
22
  }
18
23
  export interface DeepWriteCandidateInput {
19
24
  candidateId?: string;
20
25
  runId: string;
21
26
  candidateType: string;
22
27
  status: DeepWriteCandidateStatus;
28
+ publishStatus?: Exclude<DeepWriteCandidateStatus, 'staged'>;
23
29
  confidence: number;
24
30
  content: unknown;
25
31
  evidence: unknown;
@@ -44,16 +50,24 @@ export interface DeepWriteCandidateListOptions {
44
50
  projectId?: string;
45
51
  runId?: string;
46
52
  limit?: number;
53
+ after?: {
54
+ createdAt: number;
55
+ candidateId: string;
56
+ };
47
57
  }
48
58
  export declare class DeepWriteCandidateStore {
49
59
  private readonly db;
50
60
  constructor(db: Database);
61
+ getDatabase(): Database;
62
+ countActivePromotions(targetType: string, targetId: string, excludingCandidateId?: string): number;
63
+ recoverStalePromoting(before: number, updatedAt: number): number;
51
64
  initSchema(): void;
52
65
  insertRun(input: DeepWriteRunInput): DeepWriteRunRecord;
53
66
  insertCandidates(inputs: DeepWriteCandidateInput[]): DeepWriteCandidateRecord[];
54
67
  getRun(runId: string): DeepWriteRunRecord | null;
55
68
  listCandidatesByRun(runId: string): DeepWriteCandidateRecord[];
56
69
  getCandidate(candidateId: string): DeepWriteCandidateRecord | null;
70
+ claimCandidate(candidateId: string, updatedAt?: number): boolean;
57
71
  listCandidatesByStatus(statuses: DeepWriteCandidateStatus[], options?: {
58
72
  candidateTypes?: string[];
59
73
  limit?: number;
@@ -66,7 +80,11 @@ export declare class DeepWriteCandidateStore {
66
80
  reason?: string;
67
81
  reviewAfter?: number | null;
68
82
  updatedAt?: number;
69
- }): void;
83
+ }, expectedStatus?: DeepWriteCandidateStatus): void;
84
+ publishStagedCandidates(runId: string, candidateIds: string[], updatedAt: number): void;
85
+ failStagedRun(runId: string, now: number, reason: string): void;
86
+ updateRunStatus(runId: string, expected: DeepWriteRunStatus, next: DeepWriteRunStatus): void;
87
+ abandonStaleStagedRuns(before: number, updatedAt: number, projectId?: string): number;
70
88
  updateCandidateReviewData(candidateId: string, input: {
71
89
  content: unknown;
72
90
  evidence: unknown;
@@ -5,6 +5,28 @@ export class DeepWriteCandidateStore {
5
5
  this.db = db;
6
6
  this.initSchema();
7
7
  }
8
+ getDatabase() {
9
+ return this.db;
10
+ }
11
+ countActivePromotions(targetType, targetId, excludingCandidateId) {
12
+ const row = this.db.prepare(`
13
+ SELECT COUNT(*) AS count
14
+ FROM deep_write_candidates
15
+ WHERE promotion_target_type = ?
16
+ AND promotion_target_id = ?
17
+ AND status IN ('staged', 'candidate', 'promoted', 'needs_confirmation')
18
+ AND (? IS NULL OR candidate_id <> ?)
19
+ `).get(targetType, targetId, excludingCandidateId || null, excludingCandidateId || null);
20
+ return Number(row?.count || 0);
21
+ }
22
+ recoverStalePromoting(before, updatedAt) {
23
+ const result = this.db.prepare(`
24
+ UPDATE deep_write_candidates
25
+ SET status = 'candidate', status_reason = 'promotion_timeout_recovered', updated_at = ?
26
+ WHERE status = 'promoting' AND updated_at < ?
27
+ `).run(updatedAt, before);
28
+ return Number(result.changes || 0);
29
+ }
8
30
  initSchema() {
9
31
  this.db.exec(`
10
32
  CREATE TABLE IF NOT EXISTS deep_write_runs (
@@ -19,7 +41,12 @@ export class DeepWriteCandidateStore {
19
41
  output_hash TEXT NOT NULL,
20
42
  status TEXT NOT NULL,
21
43
  error TEXT,
22
- created_at INTEGER NOT NULL
44
+ created_at INTEGER NOT NULL,
45
+ source_episode_id TEXT,
46
+ dream_job_lease_id TEXT,
47
+ lease_until INTEGER,
48
+ attempt_generation INTEGER,
49
+ updated_at INTEGER
23
50
  );
24
51
 
25
52
  CREATE TABLE IF NOT EXISTS deep_write_candidates (
@@ -49,8 +76,14 @@ export class DeepWriteCandidateStore {
49
76
  ON deep_write_candidates(status, candidate_type);
50
77
  `);
51
78
  this.ensureColumn('deep_write_candidates', 'status_reason', 'TEXT');
79
+ this.ensureColumn('deep_write_candidates', 'publish_status', 'TEXT');
52
80
  this.ensureColumn('deep_write_candidates', 'review_after', 'INTEGER');
53
81
  this.ensureColumn('deep_write_candidates', 'updated_at', 'INTEGER');
82
+ this.ensureColumn('deep_write_runs', 'source_episode_id', 'TEXT');
83
+ this.ensureColumn('deep_write_runs', 'dream_job_lease_id', 'TEXT');
84
+ this.ensureColumn('deep_write_runs', 'lease_until', 'INTEGER');
85
+ this.ensureColumn('deep_write_runs', 'attempt_generation', 'INTEGER');
86
+ this.ensureColumn('deep_write_runs', 'updated_at', 'INTEGER');
54
87
  this.db.exec(`
55
88
  UPDATE deep_write_candidates
56
89
  SET updated_at = created_at
@@ -67,8 +100,9 @@ export class DeepWriteCandidateStore {
67
100
  INSERT INTO deep_write_runs (
68
101
  run_id, project_id, session_id, source_neuron_ids_json, model_provider,
69
102
  model_name, mode, prompt_hash, output_hash, status, error, created_at
70
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
71
- `).run(record.runId, record.projectId || null, record.sessionId || null, JSON.stringify(record.sourceNeuronIds), record.modelProvider || null, record.modelName || null, record.mode, record.promptHash, record.outputHash, record.status, record.error || null, record.createdAt);
103
+ , source_episode_id, dream_job_lease_id, lease_until, attempt_generation, updated_at
104
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
105
+ `).run(record.runId, record.projectId || null, record.sessionId || null, JSON.stringify(record.sourceNeuronIds), record.modelProvider || null, record.modelName || null, record.mode, record.promptHash, record.outputHash, record.status, record.error || null, record.createdAt, record.sourceEpisodeId || null, record.dreamJobLeaseId || null, record.leaseUntil ?? null, record.attemptGeneration ?? null, record.updatedAt ?? record.createdAt);
72
106
  return record;
73
107
  }
74
108
  insertCandidates(inputs) {
@@ -82,12 +116,12 @@ export class DeepWriteCandidateStore {
82
116
  INSERT INTO deep_write_candidates (
83
117
  candidate_id, run_id, candidate_type, status, confidence, content_json,
84
118
  evidence_json, promotion_target_type, promotion_target_id, status_reason,
85
- review_after, created_at, updated_at
86
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
119
+ publish_status, review_after, created_at, updated_at
120
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
87
121
  `);
88
122
  this.db.transaction(() => {
89
123
  for (const record of records) {
90
- stmt.run(record.candidateId, record.runId, record.candidateType, record.status, record.confidence, JSON.stringify(record.content), JSON.stringify(record.evidence), record.promotionTargetType || null, record.promotionTargetId || null, record.statusReason || null, record.reviewAfter ?? null, record.createdAt, record.updatedAt);
124
+ stmt.run(record.candidateId, record.runId, record.candidateType, record.status, record.confidence, JSON.stringify(record.content), JSON.stringify(record.evidence), record.promotionTargetType || null, record.promotionTargetId || null, record.statusReason || null, record.publishStatus || null, record.reviewAfter ?? null, record.createdAt, record.updatedAt);
91
125
  }
92
126
  })();
93
127
  return records;
@@ -117,6 +151,14 @@ export class DeepWriteCandidateStore {
117
151
  `).get(candidateId);
118
152
  return row ? this.mapCandidate(row) : null;
119
153
  }
154
+ claimCandidate(candidateId, updatedAt = Date.now()) {
155
+ const result = this.db.prepare(`
156
+ UPDATE deep_write_candidates
157
+ SET status = 'promoting', updated_at = ?
158
+ WHERE candidate_id = ? AND status = 'candidate'
159
+ `).run(updatedAt, candidateId);
160
+ return Number(result.changes || 0) === 1;
161
+ }
120
162
  listCandidatesByStatus(statuses, options) {
121
163
  if (statuses.length === 0)
122
164
  return [];
@@ -137,7 +179,7 @@ export class DeepWriteCandidateStore {
137
179
  }
138
180
  listCandidates(options = {}) {
139
181
  const params = [];
140
- const conditions = [];
182
+ const conditions = options.statuses?.some((status) => status === 'staged' || status === 'promoting') ? [] : ["c.status NOT IN ('staged', 'promoting')"];
141
183
  let sql = `
142
184
  SELECT c.*
143
185
  FROM deep_write_candidates c
@@ -159,6 +201,10 @@ export class DeepWriteCandidateStore {
159
201
  conditions.push('c.run_id = ?');
160
202
  params.push(options.runId);
161
203
  }
204
+ if (options.after) {
205
+ conditions.push('(c.created_at > ? OR (c.created_at = ? AND c.candidate_id > ?))');
206
+ params.push(options.after.createdAt, options.after.createdAt, options.after.candidateId);
207
+ }
162
208
  if (conditions.length)
163
209
  sql += ` WHERE ${conditions.join(' AND ')}`;
164
210
  sql += ` ORDER BY c.created_at ASC, c.candidate_id ASC LIMIT ?`;
@@ -168,7 +214,7 @@ export class DeepWriteCandidateStore {
168
214
  }
169
215
  countCandidates(options = {}) {
170
216
  const params = [];
171
- const conditions = [];
217
+ const conditions = options.statuses?.some((status) => status === 'staged' || status === 'promoting') ? [] : ["c.status NOT IN ('staged', 'promoting')"];
172
218
  let sql = `
173
219
  SELECT COUNT(*) AS count
174
220
  FROM deep_write_candidates c
@@ -195,7 +241,7 @@ export class DeepWriteCandidateStore {
195
241
  const row = this.db.prepare(sql).get(...params);
196
242
  return row?.count || 0;
197
243
  }
198
- updateCandidateStatus(candidateId, status, promotionTarget) {
244
+ updateCandidateStatus(candidateId, status, promotionTarget, expectedStatus) {
199
245
  this.db.prepare(`
200
246
  UPDATE deep_write_candidates
201
247
  SET status = ?,
@@ -204,8 +250,63 @@ export class DeepWriteCandidateStore {
204
250
  status_reason = COALESCE(?, status_reason),
205
251
  review_after = COALESCE(?, review_after),
206
252
  updated_at = ?
207
- WHERE candidate_id = ?
208
- `).run(status, promotionTarget?.type || null, promotionTarget?.id || null, promotionTarget?.reason || null, promotionTarget?.reviewAfter ?? null, promotionTarget?.updatedAt ?? Date.now(), candidateId);
253
+ WHERE candidate_id = ? AND (? IS NULL OR status = ?)
254
+ `).run(status, promotionTarget?.type || null, promotionTarget?.id || null, promotionTarget?.reason || null, promotionTarget?.reviewAfter ?? null, promotionTarget?.updatedAt ?? Date.now(), candidateId, expectedStatus || null, expectedStatus || null);
255
+ }
256
+ publishStagedCandidates(runId, candidateIds, updatedAt) {
257
+ const statement = this.db.prepare(`UPDATE deep_write_candidates SET status = COALESCE(publish_status, 'candidate'), updated_at = ? WHERE candidate_id = ? AND run_id = ? AND status = 'staged'`);
258
+ for (const candidateId of candidateIds) {
259
+ const staged = Number(statement.run(updatedAt, candidateId, runId).changes || 0);
260
+ if (staged === 1)
261
+ continue;
262
+ const visible = this.db.prepare(`SELECT status, run_id FROM deep_write_candidates WHERE candidate_id = ?`).get(candidateId);
263
+ if (visible?.run_id !== runId || visible.status !== 'shadow')
264
+ throw new Error(`staged_candidate_publish_conflict:${candidateId}`);
265
+ }
266
+ }
267
+ failStagedRun(runId, now, reason) {
268
+ this.db.transaction(() => {
269
+ this.db.prepare(`UPDATE deep_write_candidates SET status = 'superseded', status_reason = ?, updated_at = ? WHERE run_id = ? AND status IN ('staged', 'shadow')`).run(reason, now, runId);
270
+ this.db.prepare(`UPDATE deep_write_runs SET status = 'failed', error = ? WHERE run_id = ? AND status = 'staged'`).run(reason, runId);
271
+ })();
272
+ }
273
+ updateRunStatus(runId, expected, next) {
274
+ const result = this.db.prepare(`UPDATE deep_write_runs SET status = ? WHERE run_id = ? AND status = ?`).run(next, runId, expected);
275
+ if (Number(result.changes || 0) !== 1)
276
+ throw new Error(`dream_run_status_conflict:${runId}`);
277
+ }
278
+ abandonStaleStagedRuns(before, updatedAt, projectId) {
279
+ const transaction = this.db.transaction(() => {
280
+ const candidates = this.db.prepare(`
281
+ SELECT r.run_id
282
+ FROM deep_write_runs r
283
+ LEFT JOIN episode_dream_jobs j ON j.episode_id = r.source_episode_id
284
+ WHERE r.status = 'staged' AND r.created_at < ?
285
+ AND (? IS NULL OR r.project_id = ?)
286
+ AND (
287
+ j.episode_id IS NULL
288
+ OR j.state <> 'processing'
289
+ OR j.lease_id <> r.dream_job_lease_id
290
+ OR j.lease_until IS NULL
291
+ OR j.lease_until < ?
292
+ )
293
+ `).all(before, projectId || null, projectId || null, updatedAt);
294
+ let abandoned = 0;
295
+ for (const row of candidates) {
296
+ this.db.prepare(`
297
+ UPDATE deep_write_candidates
298
+ SET status = 'superseded', status_reason = 'staged_run_abandoned', updated_at = ?
299
+ WHERE run_id = ? AND status IN ('staged', 'shadow')
300
+ `).run(updatedAt, row.run_id);
301
+ const result = this.db.prepare(`
302
+ UPDATE deep_write_runs SET status = 'abandoned', error = COALESCE(error, 'staged_run_abandoned')
303
+ WHERE run_id = ? AND status = 'staged'
304
+ `).run(row.run_id);
305
+ abandoned += Number(result.changes || 0);
306
+ }
307
+ return abandoned;
308
+ });
309
+ return transaction();
209
310
  }
210
311
  updateCandidateReviewData(candidateId, input) {
211
312
  this.db.prepare(`
@@ -261,6 +362,11 @@ export class DeepWriteCandidateStore {
261
362
  outputHash: row.output_hash,
262
363
  status: row.status,
263
364
  error: row.error || undefined,
365
+ sourceEpisodeId: row.source_episode_id || undefined,
366
+ dreamJobLeaseId: row.dream_job_lease_id || undefined,
367
+ leaseUntil: row.lease_until ?? undefined,
368
+ attemptGeneration: row.attempt_generation ?? undefined,
369
+ updatedAt: row.updated_at ?? row.created_at,
264
370
  createdAt: row.created_at
265
371
  };
266
372
  }
@@ -270,6 +376,7 @@ export class DeepWriteCandidateStore {
270
376
  runId: row.run_id,
271
377
  candidateType: row.candidate_type,
272
378
  status: row.status,
379
+ publishStatus: row.publish_status || undefined,
273
380
  confidence: row.confidence,
274
381
  content: JSON.parse(row.content_json || '{}'),
275
382
  evidence: JSON.parse(row.evidence_json || '[]'),
@@ -106,6 +106,8 @@ export declare class EntityStore {
106
106
  listByAlias(aliasText: string, type?: string): EntityRecord[];
107
107
  findByCanonicalName(canonicalName: string, type?: string): EntityRecord | null;
108
108
  findByEntityId(entityId: string): EntityRecord | null;
109
+ findActiveByEntityId(entityId: string): EntityRecord | null;
110
+ getByEntityId(entityId: string): EntityRecord | null;
109
111
  findLatestByType(type: string): EntityRecord | null;
110
112
  listRecentByType(type: string, limit?: number): EntityRecord[];
111
113
  private listByCreationOrder;
@@ -125,12 +127,14 @@ export declare class EntityStore {
125
127
  type?: string;
126
128
  projectId?: string;
127
129
  limit?: number;
130
+ includeInactive?: boolean;
128
131
  }): EntityMentionRecord[];
129
132
  getEntityTimeline(input: {
130
133
  type?: string;
131
134
  projectId?: string;
132
135
  entityIds?: string[];
133
136
  limit?: number;
137
+ includeInactive?: boolean;
134
138
  }): EntityTimelineItem[];
135
139
  listEntitiesUpdatedInRange(startTime: number, endTime: number, type?: string): EntityRecord[];
136
140
  archiveEntity(entityId: string, updatedAt?: number): void;
@@ -183,7 +183,7 @@ export class EntityStore {
183
183
  WHERE instance_id = ?
184
184
  `).run(JSON.stringify(mergedAliases), input.status || existing.status, JSON.stringify(mergedMetadata), now, existing.entityId);
185
185
  this.upsertAliases(existing.entityId, input.type, mergedAliases, now);
186
- return this.findByEntityId(existing.entityId);
186
+ return this.getByEntityId(existing.entityId);
187
187
  }
188
188
  const record = {
189
189
  entityId: `entity-${randomUUID()}`,
@@ -216,7 +216,7 @@ export class EntityStore {
216
216
  SELECT e.*
217
217
  FROM entity_aliases ea
218
218
  JOIN entity_instances e ON e.instance_id = ea.entity_id
219
- WHERE ea.normalized_alias = ? AND e.type = ?
219
+ WHERE ea.normalized_alias = ? AND e.type = ? AND e.status = 'active'
220
220
  ORDER BY ea.updated_at DESC
221
221
  LIMIT 12
222
222
  `).all(normalizedAlias, type)
@@ -224,7 +224,7 @@ export class EntityStore {
224
224
  SELECT e.*
225
225
  FROM entity_aliases ea
226
226
  JOIN entity_instances e ON e.instance_id = ea.entity_id
227
- WHERE ea.normalized_alias = ?
227
+ WHERE ea.normalized_alias = ? AND e.status = 'active'
228
228
  ORDER BY ea.updated_at DESC
229
229
  LIMIT 12
230
230
  `).all(normalizedAlias);
@@ -233,29 +233,37 @@ export class EntityStore {
233
233
  findByCanonicalName(canonicalName, type) {
234
234
  const row = type
235
235
  ? this.db.prepare(`
236
- SELECT * FROM entity_instances WHERE canonical_name = ? AND type = ? ORDER BY updated_at DESC LIMIT 1
236
+ SELECT * FROM entity_instances WHERE canonical_name = ? AND type = ? AND status = 'active' ORDER BY updated_at DESC LIMIT 1
237
237
  `).get(canonicalName, type)
238
238
  : this.db.prepare(`
239
- SELECT * FROM entity_instances WHERE canonical_name = ? ORDER BY updated_at DESC LIMIT 1
239
+ SELECT * FROM entity_instances WHERE canonical_name = ? AND status = 'active' ORDER BY updated_at DESC LIMIT 1
240
240
  `).get(canonicalName);
241
241
  return row ? this.mapRow(row) : null;
242
242
  }
243
243
  findByEntityId(entityId) {
244
+ const row = this.db.prepare(`SELECT * FROM entity_instances WHERE instance_id = ?`).get(entityId);
245
+ return row ? this.mapRow(row) : null;
246
+ }
247
+ findActiveByEntityId(entityId) {
244
248
  const row = this.db.prepare(`
245
- SELECT * FROM entity_instances WHERE instance_id = ?
249
+ SELECT * FROM entity_instances WHERE instance_id = ? AND status = 'active'
246
250
  `).get(entityId);
247
251
  return row ? this.mapRow(row) : null;
248
252
  }
253
+ getByEntityId(entityId) {
254
+ const row = this.db.prepare(`SELECT * FROM entity_instances WHERE instance_id = ?`).get(entityId);
255
+ return row ? this.mapRow(row) : null;
256
+ }
249
257
  findLatestByType(type) {
250
258
  const row = this.db.prepare(`
251
- SELECT * FROM entity_instances WHERE type = ? ORDER BY updated_at DESC, created_at DESC LIMIT 1
259
+ SELECT * FROM entity_instances WHERE type = ? AND status = 'active' ORDER BY updated_at DESC, created_at DESC LIMIT 1
252
260
  `).get(type);
253
261
  return row ? this.mapRow(row) : null;
254
262
  }
255
263
  listRecentByType(type, limit = 8) {
256
264
  const rows = this.db.prepare(`
257
265
  SELECT * FROM entity_instances
258
- WHERE type = ?
266
+ WHERE type = ? AND status = 'active'
259
267
  ORDER BY updated_at DESC, created_at DESC
260
268
  LIMIT ?
261
269
  `).all(type, limit);
@@ -264,7 +272,7 @@ export class EntityStore {
264
272
  listByCreationOrder(type, limit = 8) {
265
273
  const rows = this.db.prepare(`
266
274
  SELECT * FROM entity_instances
267
- WHERE type = ?
275
+ WHERE type = ? AND status = 'active'
268
276
  ORDER BY created_at DESC, updated_at DESC
269
277
  LIMIT ?
270
278
  `).all(type, limit);
@@ -411,7 +419,7 @@ export class EntityStore {
411
419
  ) VALUES (?, ?, ?, ?, ?, ?)
412
420
  `).run(record.mentionId, record.entityId, record.neuronId || null, record.projectId || null, record.mentionType, record.createdAt);
413
421
  this.touchEntity(record.entityId, record.createdAt);
414
- const entity = this.findByEntityId(record.entityId);
422
+ const entity = this.getByEntityId(record.entityId);
415
423
  if (entity) {
416
424
  for (const alias of [entity.canonicalName, ...(entity.aliases || [])]) {
417
425
  this.refreshAliasConflict(this.normalizeAlias(alias), entity.type, record.createdAt);
@@ -440,6 +448,8 @@ export class EntityStore {
440
448
  sql += ` AND em.project_id = ?`;
441
449
  params.push(input.projectId);
442
450
  }
451
+ if (!input.includeInactive)
452
+ sql += ` AND e.status = 'active'`;
443
453
  sql += ` ORDER BY em.created_at DESC LIMIT ?`;
444
454
  params.push(limit);
445
455
  const rows = this.db.prepare(sql).all(...params);
@@ -469,6 +479,8 @@ export class EntityStore {
469
479
  sql += ` AND em.project_id = ?`;
470
480
  params.push(input.projectId);
471
481
  }
482
+ if (!input.includeInactive)
483
+ sql += ` AND e.status = 'active'`;
472
484
  if (input.entityIds?.length) {
473
485
  sql += ` AND em.entity_id IN (${input.entityIds.map(() => '?').join(', ')})`;
474
486
  params.push(...input.entityIds);
@@ -662,7 +674,7 @@ export class EntityStore {
662
674
  this.db.close();
663
675
  }
664
676
  addAlias(entityId, alias, updatedAt = Date.now()) {
665
- const entity = this.findByEntityId(entityId);
677
+ const entity = this.getByEntityId(entityId);
666
678
  if (!entity || !alias.trim())
667
679
  return;
668
680
  const aliases = Array.from(new Set([...entity.aliases, alias.trim()]));
@@ -671,7 +683,7 @@ export class EntityStore {
671
683
  this.upsertAliases(entityId, entity.type, [alias], updatedAt);
672
684
  }
673
685
  removeAlias(entityId, alias, updatedAt = Date.now()) {
674
- const entity = this.findByEntityId(entityId);
686
+ const entity = this.getByEntityId(entityId);
675
687
  if (!entity || entity.canonicalName === alias)
676
688
  return;
677
689
  const normalized = this.normalizeAlias(alias);
@@ -1,3 +1,4 @@
1
+ import Database from 'bun:sqlite';
1
2
  import type { EncryptionProvider } from '../encryption/index.js';
2
3
  import type { EventAuditPage, MemoryEvent, MemoryEventCausalityType, MemoryEventContext, MemoryRawEventType, MemoryEventRole, MemoryEventType, OrderingConfidence, StreamType } from '../types/index.js';
3
4
  export interface ProjectionCheckpoint {
@@ -28,6 +29,7 @@ export interface AppendEventInput<TPayload = Record<string, unknown>> {
28
29
  threadId?: string;
29
30
  sessionId?: string;
30
31
  localDate?: string;
32
+ localDateSource?: 'explicit' | 'generated_utc' | 'legacy_unknown';
31
33
  threadSeq?: number;
32
34
  turnId?: string;
33
35
  turnSeq?: number;
@@ -49,15 +51,18 @@ export interface AppendEventInput<TPayload = Record<string, unknown>> {
49
51
  export declare class EventStore {
50
52
  private readonly encryptionProvider?;
51
53
  private db;
52
- constructor(dbPath?: string, encryptionProvider?: EncryptionProvider | undefined);
54
+ private ownsDb;
55
+ constructor(dbPath?: string | Database, encryptionProvider?: EncryptionProvider | undefined);
53
56
  private initializeSchema;
54
57
  private ensureCompatibilityColumns;
55
- append<TPayload = Record<string, unknown>>(input: AppendEventInput<TPayload>): MemoryEvent<TPayload>;
58
+ append<TPayload = Record<string, unknown>>(input: AppendEventInput<TPayload>, retry?: number): MemoryEvent<TPayload>;
59
+ private upsertImportAnchor;
56
60
  getNextGlobalSeq(): number;
57
61
  getNextEventVersion(streamId: string): number;
58
62
  getNextThreadSeq(threadId: string): number;
59
63
  getNextTurnSeq(threadId: string): number;
60
64
  getEventsAfter(lastEventTime?: number): MemoryEvent[];
65
+ findImportedEventAnchor(projectId: string, sourceId: string, importAnchor: string): MemoryEvent | null;
61
66
  getLatestEvent(): MemoryEvent | null;
62
67
  listRawEventsAfterGlobalSeq(options?: {
63
68
  projectId?: string;