@devflow-tools/memory-engine 0.17.8 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/LICENSE +21 -0
  2. package/dist/consolidator.d.ts.map +1 -1
  3. package/dist/consolidator.js +2 -0
  4. package/dist/consolidator.js.map +1 -1
  5. package/dist/embedding-model-store.d.ts +1 -4
  6. package/dist/embedding-model-store.d.ts.map +1 -1
  7. package/dist/embedding-model-store.js +3 -75
  8. package/dist/embedding-model-store.js.map +1 -1
  9. package/dist/embedding-provider.d.ts +3 -2
  10. package/dist/embedding-provider.d.ts.map +1 -1
  11. package/dist/embedding-provider.js +151 -43
  12. package/dist/embedding-provider.js.map +1 -1
  13. package/dist/hybrid-search.d.ts +5 -1
  14. package/dist/hybrid-search.d.ts.map +1 -1
  15. package/dist/hybrid-search.js +92 -56
  16. package/dist/hybrid-search.js.map +1 -1
  17. package/dist/index.d.ts +16 -3
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +8 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/lease-store.d.ts.map +1 -1
  22. package/dist/lease-store.js +30 -0
  23. package/dist/lease-store.js.map +1 -1
  24. package/dist/memory-command-service.d.ts +73 -0
  25. package/dist/memory-command-service.d.ts.map +1 -0
  26. package/dist/memory-command-service.js +283 -0
  27. package/dist/memory-command-service.js.map +1 -0
  28. package/dist/memory-engine.d.ts +19 -15
  29. package/dist/memory-engine.d.ts.map +1 -1
  30. package/dist/memory-engine.js +207 -158
  31. package/dist/memory-engine.js.map +1 -1
  32. package/dist/memory-episode-repository.d.ts +77 -0
  33. package/dist/memory-episode-repository.d.ts.map +1 -0
  34. package/dist/memory-episode-repository.js +262 -0
  35. package/dist/memory-episode-repository.js.map +1 -0
  36. package/dist/memory-fact-provider-port.d.ts +83 -0
  37. package/dist/memory-fact-provider-port.d.ts.map +1 -0
  38. package/dist/memory-fact-provider-port.js +22 -0
  39. package/dist/memory-fact-provider-port.js.map +1 -0
  40. package/dist/memory-fact-validator.d.ts +16 -0
  41. package/dist/memory-fact-validator.d.ts.map +1 -0
  42. package/dist/memory-fact-validator.js +52 -0
  43. package/dist/memory-fact-validator.js.map +1 -0
  44. package/dist/memory-gate.d.ts +14 -17
  45. package/dist/memory-gate.d.ts.map +1 -1
  46. package/dist/memory-gate.js +369 -329
  47. package/dist/memory-gate.js.map +1 -1
  48. package/dist/memory-identity-compiler.d.ts +18 -0
  49. package/dist/memory-identity-compiler.d.ts.map +1 -0
  50. package/dist/memory-identity-compiler.js +26 -0
  51. package/dist/memory-identity-compiler.js.map +1 -0
  52. package/dist/memory-index-projector.d.ts +22 -0
  53. package/dist/memory-index-projector.d.ts.map +1 -0
  54. package/dist/memory-index-projector.js +11 -0
  55. package/dist/memory-index-projector.js.map +1 -0
  56. package/dist/memory-lifecycle-reducer.d.ts +21 -0
  57. package/dist/memory-lifecycle-reducer.d.ts.map +1 -0
  58. package/dist/memory-lifecycle-reducer.js +34 -0
  59. package/dist/memory-lifecycle-reducer.js.map +1 -0
  60. package/dist/memory-maintenance.d.ts +1 -4
  61. package/dist/memory-maintenance.d.ts.map +1 -1
  62. package/dist/memory-maintenance.js +3 -20
  63. package/dist/memory-maintenance.js.map +1 -1
  64. package/dist/memory-query-service.d.ts +24 -0
  65. package/dist/memory-query-service.d.ts.map +1 -0
  66. package/dist/memory-query-service.js +180 -0
  67. package/dist/memory-query-service.js.map +1 -0
  68. package/dist/memory-relevance.d.ts +16 -1
  69. package/dist/memory-relevance.d.ts.map +1 -1
  70. package/dist/memory-relevance.js +83 -139
  71. package/dist/memory-relevance.js.map +1 -1
  72. package/dist/memory-store.d.ts +52 -36
  73. package/dist/memory-store.d.ts.map +1 -1
  74. package/dist/memory-store.js +406 -704
  75. package/dist/memory-store.js.map +1 -1
  76. package/dist/semantic-memory-compiler.d.ts +0 -2
  77. package/dist/semantic-memory-compiler.d.ts.map +1 -1
  78. package/dist/semantic-memory-compiler.js +4 -4
  79. package/dist/semantic-memory-compiler.js.map +1 -1
  80. package/dist/session-summarizer.d.ts +3 -0
  81. package/dist/session-summarizer.d.ts.map +1 -1
  82. package/dist/session-summarizer.js.map +1 -1
  83. package/dist/signal-analyzer.d.ts +2 -2
  84. package/dist/signal-analyzer.d.ts.map +1 -1
  85. package/dist/signal-analyzer.js +10 -16
  86. package/dist/signal-analyzer.js.map +1 -1
  87. package/package.json +14 -5
  88. package/dist/lease-store.test.d.ts +0 -2
  89. package/dist/lease-store.test.d.ts.map +0 -1
  90. package/dist/lease-store.test.js +0 -182
  91. package/dist/lease-store.test.js.map +0 -1
  92. package/dist/migrations/v10-to-v11.d.ts +0 -3
  93. package/dist/migrations/v10-to-v11.d.ts.map +0 -1
  94. package/dist/migrations/v10-to-v11.js +0 -21
  95. package/dist/migrations/v10-to-v11.js.map +0 -1
  96. package/dist/migrations/v11-to-v12.d.ts +0 -66
  97. package/dist/migrations/v11-to-v12.d.ts.map +0 -1
  98. package/dist/migrations/v11-to-v12.js +0 -577
  99. package/dist/migrations/v11-to-v12.js.map +0 -1
  100. package/dist/migrations/v12-to-v13.d.ts +0 -3
  101. package/dist/migrations/v12-to-v13.d.ts.map +0 -1
  102. package/dist/migrations/v12-to-v13.js +0 -50
  103. package/dist/migrations/v12-to-v13.js.map +0 -1
  104. package/dist/migrations/v2-to-v3.d.ts +0 -3
  105. package/dist/migrations/v2-to-v3.d.ts.map +0 -1
  106. package/dist/migrations/v2-to-v3.js +0 -122
  107. package/dist/migrations/v2-to-v3.js.map +0 -1
  108. package/dist/migrations/v2-to-v3.test.d.ts +0 -2
  109. package/dist/migrations/v2-to-v3.test.d.ts.map +0 -1
  110. package/dist/migrations/v2-to-v3.test.js +0 -88
  111. package/dist/migrations/v2-to-v3.test.js.map +0 -1
  112. package/dist/migrations/v3-to-v4.d.ts +0 -3
  113. package/dist/migrations/v3-to-v4.d.ts.map +0 -1
  114. package/dist/migrations/v3-to-v4.js +0 -97
  115. package/dist/migrations/v3-to-v4.js.map +0 -1
  116. package/dist/migrations/v4-to-v5.d.ts +0 -3
  117. package/dist/migrations/v4-to-v5.d.ts.map +0 -1
  118. package/dist/migrations/v4-to-v5.js +0 -34
  119. package/dist/migrations/v4-to-v5.js.map +0 -1
  120. package/dist/migrations/v4-to-v5.test.d.ts +0 -2
  121. package/dist/migrations/v4-to-v5.test.d.ts.map +0 -1
  122. package/dist/migrations/v4-to-v5.test.js +0 -32
  123. package/dist/migrations/v4-to-v5.test.js.map +0 -1
  124. package/dist/migrations/v6-to-v7.d.ts +0 -3
  125. package/dist/migrations/v6-to-v7.d.ts.map +0 -1
  126. package/dist/migrations/v6-to-v7.js +0 -18
  127. package/dist/migrations/v6-to-v7.js.map +0 -1
  128. package/dist/migrations/v6-to-v7.test.d.ts +0 -2
  129. package/dist/migrations/v6-to-v7.test.d.ts.map +0 -1
  130. package/dist/migrations/v6-to-v7.test.js +0 -33
  131. package/dist/migrations/v6-to-v7.test.js.map +0 -1
  132. package/dist/migrations/v7-to-v8.d.ts +0 -3
  133. package/dist/migrations/v7-to-v8.d.ts.map +0 -1
  134. package/dist/migrations/v7-to-v8.js +0 -20
  135. package/dist/migrations/v7-to-v8.js.map +0 -1
  136. package/dist/migrations/v8-to-v9.d.ts +0 -3
  137. package/dist/migrations/v8-to-v9.d.ts.map +0 -1
  138. package/dist/migrations/v8-to-v9.js +0 -18
  139. package/dist/migrations/v8-to-v9.js.map +0 -1
  140. package/dist/migrations/v9-to-v10.d.ts +0 -3
  141. package/dist/migrations/v9-to-v10.d.ts.map +0 -1
  142. package/dist/migrations/v9-to-v10.js +0 -45
  143. package/dist/migrations/v9-to-v10.js.map +0 -1
  144. package/dist/migrations/v9-to-v10.test.d.ts +0 -2
  145. package/dist/migrations/v9-to-v10.test.d.ts.map +0 -1
  146. package/dist/migrations/v9-to-v10.test.js +0 -40
  147. package/dist/migrations/v9-to-v10.test.js.map +0 -1
  148. package/dist/preference-conflicts.test.d.ts +0 -2
  149. package/dist/preference-conflicts.test.d.ts.map +0 -1
  150. package/dist/preference-conflicts.test.js +0 -80
  151. package/dist/preference-conflicts.test.js.map +0 -1
  152. package/dist/semantic-memory.test.d.ts +0 -2
  153. package/dist/semantic-memory.test.d.ts.map +0 -1
  154. package/dist/semantic-memory.test.js +0 -61
  155. package/dist/semantic-memory.test.js.map +0 -1
@@ -1,3 +1,4 @@
1
+ import { getDevFlowStateRoot, resolveStableProjectIdentity, stableRuntimeHash } from '@devflow-tools/sdk';
1
2
  import { SignalBuffer } from './signal-buffer.js';
2
3
  import { validateProjectRoot, hasForceInitSentinel, writeForceInitSentinel } from './project-validator.js';
3
4
  import { MemoryEngine } from './memory-engine.js';
@@ -7,13 +8,14 @@ import { groupEventsBySession, triageGroupsWithDisposition } from './event-group
7
8
  import { SUMMARY_SYSTEM, buildSummaryPrompt } from './session-summarizer.js';
8
9
  import { LeaseStore } from './lease-store.js';
9
10
  import { appendFileSync, mkdirSync } from 'node:fs';
10
- import { homedir } from 'node:os';
11
11
  import { join } from 'node:path';
12
- import { isObservationType, observationDedupKey, } from './observation-types.js';
12
+ import { isObservationType, } from './observation-types.js';
13
13
  import { evaluateObservationQuality } from './observation-quality.js';
14
14
  import { mergeSemanticEntities, normalizeEntityKey, normalizeSemanticMemory, } from './semantic-memory.js';
15
15
  import { validateEvidenceSpans, validateMemoryCapsules } from './memory-capsule.js';
16
16
  import { emitSemanticControlLog } from '@devflow-tools/telemetry';
17
+ import { createMemoryEpisodeV1, createMemoryFactProposalV1 } from './memory-fact-provider-port.js';
18
+ import { enrichExplicitMemory } from './explicit-memory-enricher.js';
17
19
  const BATCH_CONCEPT_STOP_WORDS = new Set([
18
20
  'a', 'an', 'the', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
19
21
  'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'should',
@@ -56,7 +58,7 @@ function validateObservationBatch(observations) {
56
58
  }
57
59
  function logInvalidObservationBatch(projectRoot, batchId) {
58
60
  try {
59
- const errorDir = join(homedir(), '.devflow', 'errors');
61
+ const errorDir = join(getDevFlowStateRoot(), 'errors');
60
62
  mkdirSync(errorDir, { recursive: true });
61
63
  appendFileSync(join(errorDir, 'memory-errors.log'), `${new Date().toISOString()} invalid_observation_batch project=${JSON.stringify(projectRoot)} batch=${JSON.stringify(batchId)}\n`, { mode: 0o600 });
62
64
  }
@@ -106,6 +108,7 @@ export class MemoryGate {
106
108
  projectValid;
107
109
  initPromise = null;
108
110
  projectRoot;
111
+ projectIdentity;
109
112
  embedder;
110
113
  needsColdFlush = true;
111
114
  analyzer = new SignalAnalyzer();
@@ -116,6 +119,7 @@ export class MemoryGate {
116
119
  ruleExtractionQueue = Promise.resolve();
117
120
  constructor(projectRoot, embedder) {
118
121
  this.projectRoot = projectRoot;
122
+ this.projectIdentity = resolveStableProjectIdentity(projectRoot);
119
123
  this.projectValid = hasForceInitSentinel(projectRoot);
120
124
  this.embedder = embedder ?? new EmbeddingProvider();
121
125
  }
@@ -124,8 +128,8 @@ export class MemoryGate {
124
128
  if (!this.engine)
125
129
  await this.forceWarmUp();
126
130
  const engine = this.requireEngine();
127
- // Reserve the canonical event before running any signal analysis. API delivery
128
- // and the local fallback can therefore race without duplicating signals.
131
+ // Reserve the canonical event before running any signal analysis so duplicate
132
+ // delivery cannot create duplicate signals.
129
133
  const inserted = engine.store.recordEvent({
130
134
  ...event,
131
135
  project: this.projectRoot,
@@ -154,7 +158,7 @@ export class MemoryGate {
154
158
  this.recentEvents.push(toolEvent);
155
159
  if (this.recentEvents.length > 20)
156
160
  this.recentEvents.shift();
157
- // Aggregate signal strength as legacy score for buffer
161
+ // Aggregate signal strength for the event buffer.
158
162
  const maxStrength = signals.reduce((max, s) => Math.max(max, s.strength), 0);
159
163
  const aggregateScore = Math.min(1, signals.reduce((sum, s) => sum + s.strength * 0.3, maxStrength * 0.7));
160
164
  engine.store.updateEventScores(event.id, {
@@ -396,73 +400,92 @@ export class MemoryGate {
396
400
  logInvalidObservationBatch(this.projectRoot, batchId);
397
401
  throw new Error(`Distill batch ${batchId} failed semantic capsule validation: ${JSON.stringify(capsules.rejected)}`);
398
402
  }
399
- let committedObservationCount = 0;
400
- const acceptedSourceEventIds = new Set();
401
- const transaction = this.getStoreDb().transaction(() => {
402
- for (let index = 0; index < capsules.accepted.length; index++) {
403
- const acceptedCapsule = capsules.accepted[index];
404
- const obs = acceptedCapsule.observation;
405
- const sourceEventIds = acceptedCapsule.capsule.sourceEvidenceIds;
406
- const quality = evaluateObservationQuality({
407
- ...obs,
408
- source: 'distill',
409
- sourceEventIds,
410
- }, sourceEvents.filter(event => sourceEventIds.includes(event.id)));
411
- const exact = engine.store.findObservationByDedupKey(observationDedupKey(obs.type, obs.title));
412
- if (exact) {
413
- engine.store.linkObservationToEvents(exact.id, sourceEventIds);
414
- engine.store.incrementObservationAccess(exact.id);
415
- if (exact.project === this.projectRoot
416
- && exact.quality_status === 'accepted'
417
- && exact.resolution_state === 'current') {
418
- committedObservationCount += 1;
419
- for (const eventId of sourceEventIds)
420
- acceptedSourceEventIds.add(eventId);
421
- }
422
- continue;
423
- }
424
- const semantic = normalizeSemanticMemory({
425
- project: this.projectRoot,
426
- type: obs.type,
427
- title: obs.title,
428
- narrative: obs.narrative ?? '',
429
- concepts: obs.concepts,
430
- subject: obs.subject,
431
- predicate: obs.predicate,
432
- value: obs.value,
433
- polarity: obs.polarity,
434
- entities: obs.entities,
435
- temporal: obs.temporal,
436
- });
437
- const result = engine.store.acceptSemanticObservation({
438
- sessionId: lease.sessionIds[0] ?? '',
439
- project: this.projectRoot,
440
- type: obs.type,
441
- title: obs.title,
442
- narrative: obs.narrative,
443
- facts: obs.facts,
403
+ const corpus = buildMemorySourceCorpus(sourceEvents);
404
+ const episode = createMemoryEpisodeV1({
405
+ episodeId: `memory-episode:distill:${stableRuntimeHash({
406
+ ...this.projectIdentity, actorId: 'actor:devflow-distill', batchId, sourceEvents: lease.eventIds,
407
+ }).slice(0, 32)}`,
408
+ ...this.projectIdentity,
409
+ actorId: 'actor:devflow-distill',
410
+ sessionId: lease.sessionIds[0] ?? `distill:${batchId}`,
411
+ turnId: `distill:${batchId}`,
412
+ sourceMessageId: `distill:${batchId}`,
413
+ sourceKind: 'distill',
414
+ coldRecallEligible: false,
415
+ content: corpus.content,
416
+ sourceEventIds: lease.eventIds,
417
+ createdAt: Date.now(),
418
+ });
419
+ const providerReceipt = createMemoryProviderReceipt(episode.contentHash, 'mcp-memory-distill.v1', capsules.accepted.length);
420
+ const persistenceByProposalId = {};
421
+ const sourceEventIdsByProposalId = new Map();
422
+ const proposals = capsules.accepted.map((acceptedCapsule, index) => {
423
+ const obs = acceptedCapsule.observation;
424
+ const sourceEventIds = acceptedCapsule.capsule.sourceEvidenceIds;
425
+ const quality = evaluateObservationQuality({
426
+ ...obs,
427
+ source: 'distill',
428
+ sourceEventIds,
429
+ }, sourceEvents.filter(event => sourceEventIds.includes(event.id)));
430
+ const semantic = normalizeSemanticMemory({
431
+ project: this.projectIdentity.memoryNamespace,
432
+ type: obs.type,
433
+ title: obs.title,
434
+ narrative: obs.narrative ?? '',
435
+ concepts: obs.concepts,
436
+ subject: obs.subject,
437
+ predicate: obs.predicate,
438
+ value: obs.value,
439
+ polarity: obs.polarity,
440
+ entities: obs.entities,
441
+ temporal: obs.temporal,
442
+ });
443
+ const proposal = createMemoryFactProposalV1({
444
+ proposalId: `memory-fact-proposal:distill:${stableRuntimeHash({ batchId, index, title: obs.title }).slice(0, 32)}`,
445
+ providerExecutionId: providerReceipt.executionId, type: obs.type,
446
+ subject: semantic.subject, predicate: semantic.predicate, value: semantic.value,
447
+ polarity: semantic.polarity === 'negative' ? 'negative' : 'positive', statement: obs.narrative ?? obs.title,
448
+ scope: this.projectIdentity.memoryNamespace, confidence: obs.confidence,
449
+ sourceSpans: sourceEventIds.flatMap(eventId => {
450
+ const span = corpus.spans.get(eventId);
451
+ return span ? [{ sourceMessageId: episode.sourceMessageId, ...span }] : [];
452
+ }),
453
+ relatedCandidateHandles: [],
454
+ observedAt: episode.createdAt,
455
+ ...hostTemporalFields(obs.temporal),
456
+ enrichment: {
457
+ ...withoutSemanticIdentity(semantic),
458
+ type: obs.type, title: obs.title, narrative: obs.narrative ?? obs.title,
459
+ facts: obs.facts ?? [], files: [], tools: [],
444
460
  concepts: mergeSemanticEntities(normalizeBatchConcepts(obs), [semantic.subject, semantic.predicate, semantic.value], semantic.entities.map(normalizeEntityKey)),
445
- importance: obs.importance ?? 5,
446
- confidence: obs.confidence,
447
- source: 'distill',
448
- sourceEventIds,
449
- qualityStatus: quality.status,
450
- quarantineReason: quality.reason,
451
- qualityDetails: quality.details,
452
- failureCategory: quality.failureCategory ?? undefined,
453
- semantic,
454
- capsule: acceptedCapsule.capsule,
455
- projection: acceptedCapsule.projection,
456
- });
457
- if (result.row.quality_status === 'accepted' && result.row.resolution_state === 'current') {
458
- committedObservationCount += 1;
459
- for (const eventId of sourceEventIds)
460
- acceptedSourceEventIds.add(eventId);
461
+ evidenceStatus: quality.status === 'accepted' ? 'verified' : 'unverified',
462
+ evidence: [],
463
+ },
464
+ });
465
+ sourceEventIdsByProposalId.set(proposal.proposalId, sourceEventIds);
466
+ persistenceByProposalId[proposal.proposalId] = {
467
+ sessionId: lease.sessionIds[0], source: 'distill', importance: obs.importance,
468
+ qualityStatus: quality.status, quarantineReason: quality.reason,
469
+ qualityDetails: quality.details, failureCategory: quality.failureCategory ?? undefined,
470
+ capsule: acceptedCapsule.capsule, projection: acceptedCapsule.projection,
471
+ };
472
+ return proposal;
473
+ });
474
+ const applied = await engine.commands.commitEpisodeWithProposals({
475
+ episode, idempotencyKey: `distill-memory:${batchId}`,
476
+ proposals, providerReceipt, persistenceByProposalId,
477
+ });
478
+ const acceptedSourceEventIds = new Set();
479
+ for (const decision of applied.decisions) {
480
+ if (decision.operation !== 'hold') {
481
+ for (const eventId of sourceEventIdsByProposalId.get(decision.candidateId) ?? []) {
482
+ acceptedSourceEventIds.add(eventId);
461
483
  }
462
484
  }
463
- if (committedObservationCount > 0) {
485
+ }
486
+ const transaction = this.getStoreDb().transaction(() => {
487
+ if (acceptedSourceEventIds.size > 0)
464
488
  engine.store.markEventsProcessed([...acceptedSourceEventIds]);
465
- }
466
489
  leases.createReceipt(batchId, validObs.length, lease.eventCount);
467
490
  leases.commitLease(batchId);
468
491
  });
@@ -530,75 +553,92 @@ export class MemoryGate {
530
553
  if (capsules.rejected.length > 0 || capsules.accepted.length !== validObs.length) {
531
554
  throw new Error(`memory_commit_turn failed semantic capsule validation: ${JSON.stringify(capsules.rejected)}`);
532
555
  }
533
- const saved = [];
534
556
  const existingIds = new Set(engine.store.listObservations().map(observation => observation.id));
535
557
  const turnId = eventTurnId(event, turnEventId);
536
- this.getStoreDb().transaction(() => {
537
- for (const acceptedCapsule of capsules.accepted) {
538
- const observation = acceptedCapsule.observation;
539
- const quality = evaluateObservationQuality({
540
- ...observation,
541
- source: 'manual',
542
- sourceEventIds: acceptedCapsule.capsule.sourceEvidenceIds,
543
- }, [event]);
544
- const semantic = normalizeSemanticMemory({
545
- project: this.projectRoot,
546
- type: observation.type,
547
- title: observation.title,
548
- narrative: observation.narrative ?? '',
549
- concepts: observation.concepts,
550
- subject: observation.subject,
551
- predicate: observation.predicate,
552
- value: observation.value,
553
- polarity: observation.polarity,
554
- entities: observation.entities,
555
- temporal: observation.temporal,
556
- });
557
- const write = engine.store.acceptSemanticObservation({
558
- sessionId,
559
- project: this.projectRoot,
560
- type: observation.type,
561
- title: observation.title,
562
- narrative: observation.narrative,
563
- facts: observation.facts,
558
+ const promptText = readCanonicalPrompt(event.payload, event.user_message);
559
+ const episodeContent = promptText || buildMemorySourceCorpus([event]).content;
560
+ const episode = createMemoryEpisodeV1({
561
+ episodeId: `memory-episode:turn:${stableRuntimeHash({
562
+ ...this.projectIdentity, actorId: 'actor:devflow-host', sessionId, turnId, turnEventId,
563
+ }).slice(0, 32)}`,
564
+ ...this.projectIdentity, actorId: 'actor:devflow-host', sessionId, turnId,
565
+ sourceMessageId: turnEventId, sourceKind: 'turn', coldRecallEligible: true,
566
+ content: episodeContent, sourceEventIds: [turnEventId], createdAt: event.created_at,
567
+ });
568
+ const providerReceipt = createMemoryProviderReceipt(episode.contentHash, 'mcp-memory-turn.v1', capsules.accepted.length);
569
+ const persistenceByProposalId = {};
570
+ const proposals = capsules.accepted.map((acceptedCapsule, index) => {
571
+ const observation = acceptedCapsule.observation;
572
+ const quality = evaluateObservationQuality({
573
+ ...observation,
574
+ source: 'manual',
575
+ sourceEventIds: acceptedCapsule.capsule.sourceEvidenceIds,
576
+ }, [event]);
577
+ const semantic = normalizeSemanticMemory({
578
+ project: this.projectIdentity.memoryNamespace,
579
+ type: observation.type,
580
+ title: observation.title,
581
+ narrative: observation.narrative ?? '',
582
+ concepts: observation.concepts,
583
+ subject: observation.subject,
584
+ predicate: observation.predicate,
585
+ value: observation.value,
586
+ polarity: observation.polarity,
587
+ entities: observation.entities,
588
+ temporal: observation.temporal,
589
+ });
590
+ const narrative = observation.narrative ?? observation.title;
591
+ const proposal = createMemoryFactProposalV1({
592
+ proposalId: `memory-fact-proposal:turn:${stableRuntimeHash({ turnEventId, index, title: observation.title }).slice(0, 32)}`,
593
+ providerExecutionId: providerReceipt.executionId, type: observation.type,
594
+ subject: semantic.subject, predicate: semantic.predicate, value: semantic.value,
595
+ polarity: semantic.polarity === 'negative' ? 'negative' : 'positive',
596
+ statement: narrative, scope: this.projectIdentity.memoryNamespace, confidence: observation.confidence,
597
+ sourceSpans: memoryPromptSourceSpans(turnEventId, episodeContent, observation.evidenceSpans),
598
+ relatedCandidateHandles: [],
599
+ observedAt: episode.createdAt,
600
+ ...hostTemporalFields(observation.temporal),
601
+ enrichment: {
602
+ ...withoutSemanticIdentity(semantic),
603
+ type: observation.type, title: observation.title, narrative,
604
+ facts: observation.facts ?? [], files: [], tools: [],
564
605
  concepts: mergeSemanticEntities(normalizeBatchConcepts(observation), [semantic.subject, semantic.predicate, semantic.value], semantic.entities.map(normalizeEntityKey)),
565
- importance: observation.importance,
566
- confidence: observation.confidence,
567
- source: 'distill',
568
- sourceEventIds: [turnEventId],
569
- qualityStatus: quality.status,
570
- quarantineReason: quality.reason,
571
- qualityDetails: quality.details,
572
- failureCategory: quality.failureCategory ?? undefined,
573
- semantic,
574
- capsule: acceptedCapsule.capsule,
575
- projection: acceptedCapsule.projection,
576
- });
577
- const row = write.row;
578
- saved.push({
579
- id: row.id,
580
- type: row.type,
581
- title: row.title,
582
- content: row.narrative,
583
- source: row.source,
584
- scope: row.project,
585
- confidence: row.confidence,
586
- importance: row.importance,
587
- score: 1,
588
- createdAt: row.timestamp,
606
+ evidenceStatus: quality.status === 'accepted' ? 'verified' : 'unverified',
607
+ evidence: [{ kind: 'source_event', source: turnEventId, summary: 'Host memory candidate grounded in the canonical turn event.' }],
608
+ },
609
+ });
610
+ persistenceByProposalId[proposal.proposalId] = {
611
+ sessionId, source: 'distill', importance: observation.importance,
612
+ qualityStatus: quality.status, quarantineReason: quality.reason,
613
+ qualityDetails: quality.details, failureCategory: quality.failureCategory ?? undefined,
614
+ capsule: acceptedCapsule.capsule, projection: acceptedCapsule.projection,
615
+ };
616
+ return proposal;
617
+ });
618
+ const applied = await engine.commands.commitEpisodeWithProposals({
619
+ episode, idempotencyKey: `turn-memory:${turnEventId}`,
620
+ proposals, providerReceipt, persistenceByProposalId,
621
+ });
622
+ const saved = applied.recordIds.flatMap(id => {
623
+ const row = engine.store.getObservation(id);
624
+ return row ? [{
625
+ id: row.id, type: row.type, title: row.title, content: row.narrative,
626
+ source: row.source, scope: row.project, confidence: row.confidence,
627
+ importance: row.importance, score: 1, createdAt: row.timestamp,
589
628
  indexStatus: row.embedding ? 'indexed' : 'pending',
590
- });
629
+ }] : [];
630
+ });
631
+ this.getStoreDb().transaction(() => {
632
+ for (const row of saved) {
591
633
  engine.store.recordPendingTurnObservation({
592
- turnEventId,
593
- turnId,
594
- sessionId,
595
- project: this.projectRoot,
596
- observationId: row.id,
597
- createdByTurn: !existingIds.has(row.id),
634
+ turnEventId, turnId, sessionId, project: this.projectIdentity.memoryNamespace,
635
+ observationId: row.id, createdByTurn: !existingIds.has(row.id),
598
636
  createdAt: event.created_at,
599
637
  });
600
638
  }
601
- engine.store.markEventsProcessed([turnEventId]);
639
+ if (applied.decisions.some(decision => decision.operation !== 'hold')) {
640
+ engine.store.markEventsProcessed([turnEventId]);
641
+ }
602
642
  })();
603
643
  return saved;
604
644
  }
@@ -632,110 +672,85 @@ export class MemoryGate {
632
672
  if (capsules.rejected.length > 0 || capsules.accepted.length !== validObs.length) {
633
673
  throw new Error(`Explicit memory refinement failed evidence validation: ${JSON.stringify(capsules.rejected)}`);
634
674
  }
635
- const existingIds = new Set(engine.store.listObservations().map(observation => observation.id));
636
675
  const turnId = eventTurnId(event, turnEventId);
637
- const memories = [];
638
- const retiredMemoryIds = [];
639
- const operations = [];
640
- this.getStoreDb().transaction(() => {
641
- for (const acceptedCapsule of capsules.accepted) {
642
- const observation = acceptedCapsule.observation;
643
- const semantic = normalizeSemanticMemory({
644
- project: this.projectRoot,
645
- type: observation.type,
646
- title: observation.title,
647
- narrative: observation.narrative ?? '',
648
- concepts: observation.concepts,
649
- subject: observation.subject,
650
- predicate: observation.predicate,
651
- value: observation.value,
652
- polarity: observation.polarity,
653
- entities: observation.entities,
654
- temporal: observation.temporal,
655
- });
656
- const write = engine.store.acceptSemanticObservation({
657
- sessionId,
658
- project: this.projectRoot,
659
- type: observation.type,
660
- title: observation.title,
661
- narrative: observation.narrative,
676
+ const episode = createMemoryEpisodeV1({
677
+ episodeId: `memory-episode:host:${stableRuntimeHash({
678
+ ...this.projectIdentity, actorId: 'actor:devflow-host', sessionId, turnId, turnEventId,
679
+ sourceHash: stableRuntimeHash(sourceText),
680
+ }).slice(0, 32)}`,
681
+ ...this.projectIdentity, actorId: 'actor:devflow-host', sessionId, turnId,
682
+ sourceMessageId: turnEventId, content: sourceText,
683
+ sourceEventIds: [turnEventId], createdAt: event.created_at,
684
+ });
685
+ const providerReceipt = createMemoryProviderReceipt(episode.contentHash, 'mcp-memory-refinement.v1', capsules.accepted.length);
686
+ const candidateHandles = createCandidateHandles(episode, capsules.accepted.flatMap(item => item.observation.referencedFactIds ?? []), id => engine.store.getObservation(id));
687
+ const handleByFactId = new Map(candidateHandles.flatMap(handle => handle.factId ? [[handle.factId, handle.handle]] : []));
688
+ const factProposals = capsules.accepted.map((acceptedCapsule, index) => {
689
+ const observation = acceptedCapsule.observation;
690
+ const narrative = observation.narrative ?? observation.evidenceSpans?.[0] ?? observation.title;
691
+ const semantic = normalizeSemanticMemory({
692
+ project: this.projectIdentity.memoryNamespace, type: observation.type, title: observation.title,
693
+ narrative, concepts: observation.concepts, subject: observation.subject,
694
+ predicate: observation.predicate, value: observation.value,
695
+ polarity: observation.polarity, entities: observation.entities, temporal: observation.temporal,
696
+ });
697
+ const base = enrichExplicitMemory({
698
+ projectRoot: this.projectRoot, content: narrative, sourceEventIds: [turnEventId],
699
+ });
700
+ const sourceSpans = (observation.evidenceSpans ?? []).map(span => {
701
+ const start = sourceText.indexOf(span);
702
+ return {
703
+ sourceMessageId: turnEventId, start, end: start + span.length,
704
+ textHash: stableRuntimeHash(span),
705
+ };
706
+ });
707
+ return createMemoryFactProposalV1({
708
+ proposalId: `memory-fact-proposal:host:${stableRuntimeHash({
709
+ episodeId: episode.episodeId, index, title: observation.title,
710
+ }).slice(0, 32)}`,
711
+ providerExecutionId: providerReceipt.executionId, type: observation.type,
712
+ subject: semantic.subject, predicate: semantic.predicate, value: semantic.value,
713
+ polarity: semantic.polarity === 'negative' ? 'negative' : 'positive',
714
+ statement: narrative, scope: this.projectIdentity.memoryNamespace,
715
+ confidence: Math.max(0.9, observation.confidence), sourceSpans,
716
+ relatedCandidateHandles: (observation.referencedFactIds ?? []).flatMap(id => {
717
+ const handle = handleByFactId.get(id);
718
+ return handle ? [handle] : [];
719
+ }), observedAt: episode.createdAt,
720
+ ...hostTemporalFields(observation.temporal),
721
+ enrichment: {
722
+ ...withoutSemanticIdentity(base), type: observation.type, title: observation.title, narrative,
662
723
  facts: [...new Set([...(observation.facts ?? []), ...(observation.evidenceSpans ?? [])])],
663
724
  concepts: mergeSemanticEntities(normalizeBatchConcepts(observation), [semantic.subject, semantic.predicate, semantic.value], semantic.entities.map(normalizeEntityKey)),
664
- importance: Math.max(8, observation.importance),
665
- confidence: Math.max(0.9, observation.confidence),
666
- source: 'explicit_intent',
667
- sourceEventIds: [turnEventId],
668
- qualityStatus: 'accepted',
669
- evidenceStatus: 'verified',
670
- evidenceDetails: (observation.evidenceSpans ?? []).map(span => ({
671
- kind: 'host_evidence_span',
672
- source: turnEventId,
673
- summary: span,
674
- })),
675
- semantic,
676
- capsule: acceptedCapsule.capsule,
677
- projection: acceptedCapsule.projection,
678
- preferIncomingNarrative: true,
679
- });
680
- const row = write.row;
681
- operations.push(write.action.toLocaleLowerCase('en-US'));
682
- memories.push({
683
- id: row.id,
684
- type: row.type,
685
- title: row.title,
686
- content: row.narrative,
687
- source: row.source,
688
- scope: row.project,
689
- confidence: row.confidence,
690
- importance: row.importance,
691
- score: 1,
692
- createdAt: row.timestamp,
725
+ evidenceStatus: 'verified', ...withoutSemanticIdentity(semantic),
726
+ },
727
+ });
728
+ });
729
+ const applied = await engine.commands.commitEpisodeWithProposals({
730
+ episode, idempotencyKey: `host-memory-refinement:${turnEventId}`,
731
+ proposals: factProposals, providerReceipt, candidateHandles,
732
+ });
733
+ const memories = applied.factIds.flatMap(id => {
734
+ const row = engine.store.getObservation(id);
735
+ return row ? [{
736
+ id: row.id, type: row.type, title: row.title, content: row.narrative,
737
+ source: row.source, scope: row.project, confidence: row.confidence,
738
+ importance: row.importance, score: 1, createdAt: row.timestamp,
693
739
  indexStatus: row.embedding ? 'indexed' : 'pending',
694
- });
695
- engine.store.recordPendingTurnObservation({
696
- turnEventId,
697
- turnId,
698
- sessionId,
699
- project: this.projectRoot,
700
- observationId: row.id,
701
- createdByTurn: !existingIds.has(row.id),
702
- createdAt: event.created_at,
703
- });
704
- }
705
- const constraintSpans = turnConstraints.flatMap(candidate => candidate.evidenceSpans ?? []);
706
- const baseline = engine.store.listObservations({ project: this.projectRoot })
707
- .filter(observation => observation.source === 'explicit_intent')
708
- .filter(observation => parseLearningStringArray(observation.source_event_ids).includes(turnEventId));
709
- const refinedIds = new Set(memories.map(memory => memory.id));
710
- for (const observation of baseline) {
711
- if (refinedIds.has(observation.id) || observation.resolution_state === 'superseded')
712
- continue;
713
- const replacementIndex = durableCandidates.findIndex(candidate => ((candidate.evidenceSpans ?? []).some(span => sameGroundedClause(observation.narrative, span))));
714
- const replacement = replacementIndex >= 0
715
- ? memories[replacementIndex]
716
- : durableCandidates.length === 1 ? memories[0] : undefined;
717
- if (!replacement || replacement.id === observation.id)
718
- continue;
719
- engine.store.setObservationResolution(observation.id, 'superseded', replacement.id);
720
- engine.store.addSemanticObservationRelation(replacement.id, observation.id, 'supersedes');
721
- retiredMemoryIds.push(observation.id);
722
- operations.push('supersede');
723
- }
724
- for (const observation of baseline) {
725
- if (observation.resolution_state === 'superseded' || retiredMemoryIds.includes(observation.id))
726
- continue;
727
- if (!constraintSpans.some(span => sameGroundedClause(observation.narrative, span)))
728
- continue;
729
- if (engine.store.quarantineObservation(observation.id, 'host_classified_turn_constraint', {
730
- turnId,
731
- eventId: turnEventId,
732
- evidenceSpans: constraintSpans,
733
- })) {
734
- retiredMemoryIds.push(observation.id);
735
- operations.push('retire');
736
- }
737
- }
738
- })();
740
+ }] : [];
741
+ });
742
+ const retiredMemoryIds = [...new Set(applied.decisions
743
+ .filter(decision => decision.operation === 'supersede' || decision.operation === 'retire')
744
+ .flatMap(decision => decision.referencedFactIds))];
745
+ const operations = applied.decisions.map(decision => decision.operation);
746
+ for (const memory of memories) {
747
+ engine.store.recordPendingTurnObservation({
748
+ turnEventId, turnId, sessionId, project: this.projectIdentity.memoryNamespace,
749
+ observationId: memory.id,
750
+ createdByTurn: operations.includes('add') || operations.includes('link') || operations.includes('supersede'),
751
+ createdAt: event.created_at,
752
+ });
753
+ }
739
754
  emitSemanticControlLog({
740
755
  event: 'memory.semantic.refined',
741
756
  identity: { projectRoot: this.projectRoot, sessionId, turnId },
@@ -767,7 +782,7 @@ export class MemoryGate {
767
782
  turnEventId,
768
783
  turnId: eventTurnId(event, turnEventId),
769
784
  sessionId,
770
- project: this.projectRoot,
785
+ project: this.projectIdentity.memoryNamespace,
771
786
  status: 'skipped',
772
787
  reason,
773
788
  });
@@ -777,7 +792,7 @@ export class MemoryGate {
777
792
  await this.forceWarmUp();
778
793
  return this.requireEngine().store.finalizeTurnObservationDecision({
779
794
  ...input,
780
- project: this.projectRoot,
795
+ project: this.projectIdentity.memoryNamespace,
781
796
  });
782
797
  }
783
798
  async reconcileTurnObservationLineage(turns) {
@@ -796,7 +811,7 @@ export class MemoryGate {
796
811
  turnEventId: turn.eventId,
797
812
  turnId: turn.turnId,
798
813
  sessionId: turn.sessionId,
799
- project: this.projectRoot,
814
+ project: this.projectIdentity.memoryNamespace,
800
815
  status: turn.status,
801
816
  receiptId: turn.receiptId,
802
817
  memoryIds: turn.memoryIds,
@@ -812,71 +827,6 @@ export class MemoryGate {
812
827
  logInvalidDistillBatch(batchId) {
813
828
  logInvalidObservationBatch(this.projectRoot, batchId);
814
829
  }
815
- /**
816
- * Saves distilled observations from Claude and marks source events as processed.
817
- * @deprecated Use saveDistilledObservationsByBatch(batchId, observations) instead.
818
- */
819
- async saveDistilledObservations(observations, sessionId) {
820
- emitSemanticControlLog({
821
- event: 'memory.write.degraded',
822
- identity: { projectRoot: this.projectRoot, sessionId },
823
- level: 'warn',
824
- timestamp: Date.now(),
825
- data: { stage: 'legacy_distill', reason: 'deprecated_session_write', fallback: 'legacy_compatibility_path' },
826
- });
827
- if (!this.engine)
828
- return 0;
829
- const validObs = validateObservationBatch(observations);
830
- if (!validObs) {
831
- logInvalidObservationBatch(this.projectRoot, `legacy:${sessionId}`);
832
- return 0;
833
- }
834
- const engine = this.engine;
835
- engine.store.ensureSession(sessionId, this.projectRoot, 'Distilled observation');
836
- const events = engine.store.getSessionEvents(sessionId);
837
- const unprocessedIds = events.filter(e => !e.processed).map(e => e.id);
838
- let committedObservationCount = 0;
839
- const transaction = this.getStoreDb().transaction(() => {
840
- for (let index = 0; index < validObs.length; index += 1) {
841
- const obs = validObs[index];
842
- const quality = evaluateObservationQuality({
843
- ...obs,
844
- source: 'distill',
845
- sourceEventIds: unprocessedIds,
846
- }, events.filter(event => unprocessedIds.includes(event.id)));
847
- const row = engine.store.addObservation({
848
- sessionId,
849
- project: this.projectRoot,
850
- type: obs.type,
851
- title: obs.title,
852
- narrative: obs.narrative,
853
- facts: obs.facts,
854
- concepts: obs.concepts,
855
- importance: obs.importance,
856
- confidence: obs.confidence,
857
- source: 'distill',
858
- sourceEventIds: unprocessedIds,
859
- qualityStatus: quality.status,
860
- quarantineReason: quality.reason,
861
- qualityDetails: quality.details,
862
- failureCategory: quality.failureCategory ?? undefined,
863
- });
864
- engine.store.linkObservationToEvents(row.id, unprocessedIds);
865
- committedObservationCount += 1;
866
- }
867
- if (committedObservationCount > 0)
868
- engine.store.markEventsProcessed(unprocessedIds);
869
- });
870
- transaction();
871
- emitSemanticControlLog({
872
- event: 'memory.write.completed',
873
- identity: { projectRoot: this.projectRoot, sessionId },
874
- level: 'info',
875
- timestamp: Date.now(),
876
- data: { stage: 'legacy_distill', observationCount: validObs.length, eventsMarked: unprocessedIds.length },
877
- });
878
- return validObs.length;
879
- }
880
830
  /** Returns the count of unprocessed events (for session-start hook). */
881
831
  getPendingEventCount(sessionId, triage = true) {
882
832
  if (!this.engine)
@@ -944,7 +894,7 @@ export class MemoryGate {
944
894
  event: 'memory.engine.transitioned', identity: { projectRoot: this.projectRoot }, level: 'info', timestamp: Date.now(),
945
895
  data: { from: 'cold', to: 'warming' },
946
896
  });
947
- this.engine = new MemoryEngine(this.projectRoot, this.embedder);
897
+ this.engine = new MemoryEngine(this.projectRoot, this.embedder, this.projectIdentity);
948
898
  this.setLeaseStore(new LeaseStore(this.getStoreDb()));
949
899
  // Clean up zombie sessions (>24h active)
950
900
  try {
@@ -1050,14 +1000,14 @@ export class MemoryGate {
1050
1000
  getRetrievalRevision() {
1051
1001
  return this.engine?.getRetrievalRevision() ?? 0;
1052
1002
  }
1053
- async saveExplicitMemoryIntent(content, sessionId, sourceEventId) {
1003
+ async saveExplicitMemoryIntent(content, provenance, sourceEventId) {
1054
1004
  if (!this.engine)
1055
1005
  await this.forceWarmUp();
1056
1006
  try {
1057
- const result = await this.requireEngine().saveExplicitMemoryIntent(content, sessionId, sourceEventId);
1007
+ const result = await this.requireEngine().saveExplicitMemoryIntent(content, provenance, sourceEventId);
1058
1008
  emitSemanticControlLog({
1059
1009
  event: 'memory.write.completed',
1060
- identity: { projectRoot: this.projectRoot, sessionId },
1010
+ identity: { projectRoot: this.projectRoot, projectId: this.projectIdentity.projectId, sessionId: provenance.sessionId },
1061
1011
  level: 'info',
1062
1012
  timestamp: Date.now(),
1063
1013
  data: {
@@ -1070,7 +1020,7 @@ export class MemoryGate {
1070
1020
  }
1071
1021
  catch (error) {
1072
1022
  emitSemanticControlLog({
1073
- event: 'memory.write.degraded', identity: { projectRoot: this.projectRoot, sessionId }, level: 'error', timestamp: Date.now(),
1023
+ event: 'memory.write.degraded', identity: { projectRoot: this.projectRoot, projectId: this.projectIdentity.projectId, sessionId: provenance.sessionId }, level: 'error', timestamp: Date.now(),
1074
1024
  data: { stage: 'explicit_memory', reason: memoryErrorReason(error) },
1075
1025
  });
1076
1026
  throw error;
@@ -1101,7 +1051,7 @@ export class MemoryGate {
1101
1051
  await this.forceWarmUp();
1102
1052
  return this.requireEngine().store.listObservations({
1103
1053
  sessionId,
1104
- project: this.projectRoot,
1054
+ project: this.projectIdentity.memoryNamespace,
1105
1055
  qualityStatus: 'accepted',
1106
1056
  resolutionState: 'current',
1107
1057
  }).map(row => ({
@@ -1148,9 +1098,6 @@ export class MemoryGate {
1148
1098
  let indexed = [];
1149
1099
  try {
1150
1100
  const embeddings = await this.embedder.embedBatch(texts);
1151
- if (embeddings.length !== rows.length) {
1152
- throw new Error(`Observation vector count mismatch: ${rows.length} observations, ${embeddings.length} vectors`);
1153
- }
1154
1101
  indexed = rows.map((row, index) => ({ id: row.id, embedding: embeddings[index] }));
1155
1102
  }
1156
1103
  catch {
@@ -1184,15 +1131,15 @@ export class MemoryGate {
1184
1131
  await this.forceWarmUp();
1185
1132
  return this.requireEngine().getCandidates();
1186
1133
  }
1187
- async approveCandidate(id) {
1134
+ async approveCandidate(id, provenance) {
1188
1135
  if (!this.engine)
1189
1136
  await this.forceWarmUp();
1190
- await this.requireEngine().approveCandidate(id);
1137
+ await this.requireEngine().approveCandidate(id, provenance);
1191
1138
  }
1192
- async rejectCandidate(id) {
1139
+ async rejectCandidate(id, provenance) {
1193
1140
  if (!this.engine)
1194
1141
  await this.forceWarmUp();
1195
- await this.requireEngine().rejectCandidate(id);
1142
+ await this.requireEngine().rejectCandidate(id, provenance);
1196
1143
  }
1197
1144
  async search(query, options) {
1198
1145
  if (!this.engine)
@@ -1230,41 +1177,46 @@ export class MemoryGate {
1230
1177
  throw error;
1231
1178
  }
1232
1179
  }
1180
+ async searchWithDiagnostics(query, options) {
1181
+ if (!this.engine)
1182
+ await this.forceWarmUp();
1183
+ return this.requireEngine().searchWithDiagnostics(query, options);
1184
+ }
1233
1185
  async listMemories(filter) {
1234
1186
  if (!this.engine)
1235
1187
  await this.forceWarmUp();
1236
1188
  return this.requireEngine().listMemories(filter);
1237
1189
  }
1238
- async addMemory(entry) {
1190
+ async addMemory(entry, provenance) {
1239
1191
  if (!this.engine) {
1240
1192
  await this.forceWarmUp();
1241
1193
  }
1242
- return this.requireEngine().addMemory(entry);
1194
+ return this.requireEngine().addMemory(entry, provenance);
1243
1195
  }
1244
- async updateMemory(id, updates) {
1196
+ async updateMemory(id, updates, provenance) {
1245
1197
  if (!this.engine)
1246
1198
  await this.forceWarmUp();
1247
- await this.requireEngine().updateMemory(id, updates);
1199
+ await this.requireEngine().updateMemory(id, updates, provenance);
1248
1200
  }
1249
- async deleteMemory(id) {
1201
+ async deleteMemory(id, provenance) {
1250
1202
  if (!this.engine)
1251
1203
  await this.forceWarmUp();
1252
- await this.requireEngine().deleteMemory(id);
1204
+ await this.requireEngine().deleteMemory(id, provenance);
1253
1205
  }
1254
1206
  async getPendingMemories() {
1255
1207
  if (!this.engine)
1256
1208
  await this.forceWarmUp();
1257
1209
  return this.requireEngine().getPendingMemories();
1258
1210
  }
1259
- async approveMemory(id) {
1211
+ async approveMemory(id, provenance) {
1260
1212
  if (!this.engine)
1261
1213
  await this.forceWarmUp();
1262
- await this.requireEngine().approveMemory(id);
1214
+ await this.requireEngine().approveMemory(id, provenance);
1263
1215
  }
1264
- async rejectMemory(id) {
1216
+ async rejectMemory(id, provenance) {
1265
1217
  if (!this.engine)
1266
1218
  await this.forceWarmUp();
1267
- await this.requireEngine().rejectMemory(id);
1219
+ await this.requireEngine().rejectMemory(id, provenance);
1268
1220
  }
1269
1221
  async extractFromEventsRealtime(events) {
1270
1222
  if (!this.engine)
@@ -1295,7 +1247,18 @@ export class MemoryGate {
1295
1247
  const engine = this.requireEngine();
1296
1248
  const eventExists = engine.store.getSessionEvents(event.sessionId).some(existing => existing.id === event.id);
1297
1249
  if (!eventExists) {
1298
- await this.recordEvent(event);
1250
+ await this.recordEvent({
1251
+ id: event.id,
1252
+ sessionId: event.sessionId,
1253
+ tool: event.tool,
1254
+ command: typeof event.payload.command === 'string' ? event.payload.command : undefined,
1255
+ exitCode: typeof event.payload.exitCode === 'number' ? event.payload.exitCode : undefined,
1256
+ stderr: typeof event.payload.stderr === 'string' ? event.payload.stderr : undefined,
1257
+ durationMs: typeof event.payload.durationMs === 'number' ? event.payload.durationMs : undefined,
1258
+ createdAt: event.createdAt,
1259
+ kind: event.kind,
1260
+ payload: event.payload,
1261
+ });
1299
1262
  }
1300
1263
  }
1301
1264
  /** Drain the signal buffer without running summarizer. Events are already persisted. */
@@ -1345,14 +1308,62 @@ function readCanonicalPrompt(payloadJson, userMessage) {
1345
1308
  }
1346
1309
  return userMessage?.trim() ?? '';
1347
1310
  }
1348
- function sameGroundedClause(narrative, evidenceSpan) {
1349
- const left = narrative.normalize('NFKC').trim().toLocaleLowerCase('en-US');
1350
- const right = evidenceSpan.normalize('NFKC').trim().toLocaleLowerCase('en-US');
1351
- if (left.length < 4 || right.length < 4)
1352
- return false;
1353
- return left === right || (right.includes(left) && left.length / right.length >= 0.7);
1311
+ function buildMemorySourceCorpus(events) {
1312
+ let content = '';
1313
+ const spans = new Map();
1314
+ for (const event of events) {
1315
+ const block = JSON.stringify({
1316
+ id: event.id,
1317
+ sessionId: event.session_id,
1318
+ tool: event.tool,
1319
+ command: event.command,
1320
+ exitCode: event.exit_code,
1321
+ stderr: event.stderr,
1322
+ payload: event.payload,
1323
+ createdAt: event.created_at,
1324
+ });
1325
+ const start = content.length;
1326
+ content += block;
1327
+ const end = content.length;
1328
+ spans.set(event.id, { start, end, textHash: stableRuntimeHash(block) });
1329
+ content += '\n';
1330
+ }
1331
+ return {
1332
+ content: content.trimEnd() || 'No source event content was available.',
1333
+ spans,
1334
+ };
1335
+ }
1336
+ function memoryPromptSourceSpans(sourceMessageId, sourceText, evidenceSpans) {
1337
+ const spans = [...new Set((evidenceSpans ?? []).map(value => value.trim()).filter(Boolean))]
1338
+ .flatMap(span => {
1339
+ const start = sourceText.indexOf(span);
1340
+ if (start < 0)
1341
+ return [];
1342
+ return [{
1343
+ sourceMessageId,
1344
+ start,
1345
+ end: start + span.length,
1346
+ textHash: stableRuntimeHash(sourceText.slice(start, start + span.length)),
1347
+ }];
1348
+ });
1349
+ if (spans.length > 0)
1350
+ return spans;
1351
+ return [{
1352
+ sourceMessageId,
1353
+ start: 0,
1354
+ end: sourceText.length,
1355
+ textHash: stableRuntimeHash(sourceText),
1356
+ }];
1357
+ }
1358
+ function hostTemporalFields(temporal) {
1359
+ const validFrom = temporal?.validFrom ? Date.parse(temporal.validFrom) : Number.NaN;
1360
+ const validUntil = temporal?.validUntil ? Date.parse(temporal.validUntil) : Number.NaN;
1361
+ return {
1362
+ ...(Number.isFinite(validFrom) ? { validFrom } : {}),
1363
+ ...(Number.isFinite(validUntil) ? { validUntil } : {}),
1364
+ };
1354
1365
  }
1355
- // --- L1 Rule Scorer (backward compat) ---
1366
+ // --- L1 Rule Scorer ---
1356
1367
  function scoreL1Rules(event) {
1357
1368
  let score = 0;
1358
1369
  const stderrText = event.stderr ?? '';
@@ -1416,5 +1427,34 @@ function scoreDialogSignals(message) {
1416
1427
  function memoryErrorReason(error) {
1417
1428
  return (error instanceof Error ? error.message : String(error)).trim().slice(0, 500) || 'memory_operation_failed';
1418
1429
  }
1430
+ function withoutSemanticIdentity(value) {
1431
+ const { semanticKey: _semanticKey, facetKey: _facetKey, ...rest } = value;
1432
+ return rest;
1433
+ }
1434
+ function createMemoryProviderReceipt(requestHash, providerVersion, proposalCount) {
1435
+ const body = {
1436
+ schemaVersion: 'provider-execution-receipt.v1',
1437
+ receiptId: `memory-provider-receipt:${stableRuntimeHash({ requestHash, providerVersion }).slice(0, 32)}`,
1438
+ executionId: `memory-provider-execution:${stableRuntimeHash({ requestHash, providerVersion, proposalCount }).slice(0, 32)}`,
1439
+ requestHash, providerId: 'memory-host-boundary', providerVersion,
1440
+ capability: 'memory_facts', status: 'ready',
1441
+ proposalCount, acceptedCount: proposalCount, rejectedCount: 0,
1442
+ reasonCodes: [], latencyMs: 0, createdAt: Date.now(),
1443
+ };
1444
+ return { ...body, receiptHash: stableRuntimeHash(body) };
1445
+ }
1446
+ function createCandidateHandles(episode, factIds, load) {
1447
+ return [...new Set(factIds)].flatMap(factId => {
1448
+ const fact = load(factId);
1449
+ if (!fact || fact.project !== episode.memoryNamespace || fact.resolution_state !== 'current'
1450
+ || !isObservationType(fact.type))
1451
+ return [];
1452
+ return [{
1453
+ handle: `memory-candidate-handle:${stableRuntimeHash({ episodeId: episode.episodeId, factId }).slice(0, 32)}`,
1454
+ statement: fact.narrative, type: fact.type, scope: fact.project,
1455
+ slotIdentity: `memory-existing-slot:${fact.id}`, factId: fact.id,
1456
+ }];
1457
+ });
1458
+ }
1419
1459
  export { scoreL1Rules, scoreDialogSignals };
1420
1460
  //# sourceMappingURL=memory-gate.js.map