@devflow-tools/memory-engine 0.17.7 → 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 (158) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/consolidator.d.ts.map +1 -1
  4. package/dist/consolidator.js +2 -0
  5. package/dist/consolidator.js.map +1 -1
  6. package/dist/durable-memory-text.js +1 -1
  7. package/dist/durable-memory-text.js.map +1 -1
  8. package/dist/embedding-model-store.d.ts +1 -4
  9. package/dist/embedding-model-store.d.ts.map +1 -1
  10. package/dist/embedding-model-store.js +3 -74
  11. package/dist/embedding-model-store.js.map +1 -1
  12. package/dist/embedding-provider.d.ts +3 -2
  13. package/dist/embedding-provider.d.ts.map +1 -1
  14. package/dist/embedding-provider.js +151 -43
  15. package/dist/embedding-provider.js.map +1 -1
  16. package/dist/hybrid-search.d.ts +5 -1
  17. package/dist/hybrid-search.d.ts.map +1 -1
  18. package/dist/hybrid-search.js +92 -56
  19. package/dist/hybrid-search.js.map +1 -1
  20. package/dist/index.d.ts +16 -3
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +8 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/lease-store.d.ts.map +1 -1
  25. package/dist/lease-store.js +30 -0
  26. package/dist/lease-store.js.map +1 -1
  27. package/dist/memory-command-service.d.ts +73 -0
  28. package/dist/memory-command-service.d.ts.map +1 -0
  29. package/dist/memory-command-service.js +283 -0
  30. package/dist/memory-command-service.js.map +1 -0
  31. package/dist/memory-engine.d.ts +19 -15
  32. package/dist/memory-engine.d.ts.map +1 -1
  33. package/dist/memory-engine.js +207 -158
  34. package/dist/memory-engine.js.map +1 -1
  35. package/dist/memory-episode-repository.d.ts +77 -0
  36. package/dist/memory-episode-repository.d.ts.map +1 -0
  37. package/dist/memory-episode-repository.js +262 -0
  38. package/dist/memory-episode-repository.js.map +1 -0
  39. package/dist/memory-fact-provider-port.d.ts +83 -0
  40. package/dist/memory-fact-provider-port.d.ts.map +1 -0
  41. package/dist/memory-fact-provider-port.js +22 -0
  42. package/dist/memory-fact-provider-port.js.map +1 -0
  43. package/dist/memory-fact-validator.d.ts +16 -0
  44. package/dist/memory-fact-validator.d.ts.map +1 -0
  45. package/dist/memory-fact-validator.js +52 -0
  46. package/dist/memory-fact-validator.js.map +1 -0
  47. package/dist/memory-gate.d.ts +14 -17
  48. package/dist/memory-gate.d.ts.map +1 -1
  49. package/dist/memory-gate.js +377 -320
  50. package/dist/memory-gate.js.map +1 -1
  51. package/dist/memory-identity-compiler.d.ts +18 -0
  52. package/dist/memory-identity-compiler.d.ts.map +1 -0
  53. package/dist/memory-identity-compiler.js +26 -0
  54. package/dist/memory-identity-compiler.js.map +1 -0
  55. package/dist/memory-index-projector.d.ts +22 -0
  56. package/dist/memory-index-projector.d.ts.map +1 -0
  57. package/dist/memory-index-projector.js +11 -0
  58. package/dist/memory-index-projector.js.map +1 -0
  59. package/dist/memory-lifecycle-reducer.d.ts +21 -0
  60. package/dist/memory-lifecycle-reducer.d.ts.map +1 -0
  61. package/dist/memory-lifecycle-reducer.js +34 -0
  62. package/dist/memory-lifecycle-reducer.js.map +1 -0
  63. package/dist/memory-maintenance.d.ts +1 -4
  64. package/dist/memory-maintenance.d.ts.map +1 -1
  65. package/dist/memory-maintenance.js +3 -20
  66. package/dist/memory-maintenance.js.map +1 -1
  67. package/dist/memory-query-service.d.ts +24 -0
  68. package/dist/memory-query-service.d.ts.map +1 -0
  69. package/dist/memory-query-service.js +180 -0
  70. package/dist/memory-query-service.js.map +1 -0
  71. package/dist/memory-relevance.d.ts +16 -1
  72. package/dist/memory-relevance.d.ts.map +1 -1
  73. package/dist/memory-relevance.js +83 -139
  74. package/dist/memory-relevance.js.map +1 -1
  75. package/dist/memory-store.d.ts +52 -35
  76. package/dist/memory-store.d.ts.map +1 -1
  77. package/dist/memory-store.js +406 -690
  78. package/dist/memory-store.js.map +1 -1
  79. package/dist/semantic-memory-compiler.d.ts +0 -2
  80. package/dist/semantic-memory-compiler.d.ts.map +1 -1
  81. package/dist/semantic-memory-compiler.js +4 -4
  82. package/dist/semantic-memory-compiler.js.map +1 -1
  83. package/dist/session-summarizer.d.ts +3 -0
  84. package/dist/session-summarizer.d.ts.map +1 -1
  85. package/dist/session-summarizer.js.map +1 -1
  86. package/dist/signal-analyzer.d.ts +2 -2
  87. package/dist/signal-analyzer.d.ts.map +1 -1
  88. package/dist/signal-analyzer.js +10 -16
  89. package/dist/signal-analyzer.js.map +1 -1
  90. package/package.json +14 -5
  91. package/dist/lease-store.test.d.ts +0 -2
  92. package/dist/lease-store.test.d.ts.map +0 -1
  93. package/dist/lease-store.test.js +0 -182
  94. package/dist/lease-store.test.js.map +0 -1
  95. package/dist/migrations/v10-to-v11.d.ts +0 -3
  96. package/dist/migrations/v10-to-v11.d.ts.map +0 -1
  97. package/dist/migrations/v10-to-v11.js +0 -21
  98. package/dist/migrations/v10-to-v11.js.map +0 -1
  99. package/dist/migrations/v11-to-v12.d.ts +0 -66
  100. package/dist/migrations/v11-to-v12.d.ts.map +0 -1
  101. package/dist/migrations/v11-to-v12.js +0 -577
  102. package/dist/migrations/v11-to-v12.js.map +0 -1
  103. package/dist/migrations/v12-to-v13.d.ts +0 -3
  104. package/dist/migrations/v12-to-v13.d.ts.map +0 -1
  105. package/dist/migrations/v12-to-v13.js +0 -50
  106. package/dist/migrations/v12-to-v13.js.map +0 -1
  107. package/dist/migrations/v2-to-v3.d.ts +0 -3
  108. package/dist/migrations/v2-to-v3.d.ts.map +0 -1
  109. package/dist/migrations/v2-to-v3.js +0 -122
  110. package/dist/migrations/v2-to-v3.js.map +0 -1
  111. package/dist/migrations/v2-to-v3.test.d.ts +0 -2
  112. package/dist/migrations/v2-to-v3.test.d.ts.map +0 -1
  113. package/dist/migrations/v2-to-v3.test.js +0 -88
  114. package/dist/migrations/v2-to-v3.test.js.map +0 -1
  115. package/dist/migrations/v3-to-v4.d.ts +0 -3
  116. package/dist/migrations/v3-to-v4.d.ts.map +0 -1
  117. package/dist/migrations/v3-to-v4.js +0 -97
  118. package/dist/migrations/v3-to-v4.js.map +0 -1
  119. package/dist/migrations/v4-to-v5.d.ts +0 -3
  120. package/dist/migrations/v4-to-v5.d.ts.map +0 -1
  121. package/dist/migrations/v4-to-v5.js +0 -34
  122. package/dist/migrations/v4-to-v5.js.map +0 -1
  123. package/dist/migrations/v4-to-v5.test.d.ts +0 -2
  124. package/dist/migrations/v4-to-v5.test.d.ts.map +0 -1
  125. package/dist/migrations/v4-to-v5.test.js +0 -32
  126. package/dist/migrations/v4-to-v5.test.js.map +0 -1
  127. package/dist/migrations/v6-to-v7.d.ts +0 -3
  128. package/dist/migrations/v6-to-v7.d.ts.map +0 -1
  129. package/dist/migrations/v6-to-v7.js +0 -18
  130. package/dist/migrations/v6-to-v7.js.map +0 -1
  131. package/dist/migrations/v6-to-v7.test.d.ts +0 -2
  132. package/dist/migrations/v6-to-v7.test.d.ts.map +0 -1
  133. package/dist/migrations/v6-to-v7.test.js +0 -33
  134. package/dist/migrations/v6-to-v7.test.js.map +0 -1
  135. package/dist/migrations/v7-to-v8.d.ts +0 -3
  136. package/dist/migrations/v7-to-v8.d.ts.map +0 -1
  137. package/dist/migrations/v7-to-v8.js +0 -20
  138. package/dist/migrations/v7-to-v8.js.map +0 -1
  139. package/dist/migrations/v8-to-v9.d.ts +0 -3
  140. package/dist/migrations/v8-to-v9.d.ts.map +0 -1
  141. package/dist/migrations/v8-to-v9.js +0 -18
  142. package/dist/migrations/v8-to-v9.js.map +0 -1
  143. package/dist/migrations/v9-to-v10.d.ts +0 -3
  144. package/dist/migrations/v9-to-v10.d.ts.map +0 -1
  145. package/dist/migrations/v9-to-v10.js +0 -45
  146. package/dist/migrations/v9-to-v10.js.map +0 -1
  147. package/dist/migrations/v9-to-v10.test.d.ts +0 -2
  148. package/dist/migrations/v9-to-v10.test.d.ts.map +0 -1
  149. package/dist/migrations/v9-to-v10.test.js +0 -40
  150. package/dist/migrations/v9-to-v10.test.js.map +0 -1
  151. package/dist/preference-conflicts.test.d.ts +0 -2
  152. package/dist/preference-conflicts.test.d.ts.map +0 -1
  153. package/dist/preference-conflicts.test.js +0 -80
  154. package/dist/preference-conflicts.test.js.map +0 -1
  155. package/dist/semantic-memory.test.d.ts +0 -2
  156. package/dist/semantic-memory.test.d.ts.map +0 -1
  157. package/dist/semantic-memory.test.js +0 -61
  158. 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,61 +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 transaction = this.getStoreDb().transaction(() => {
401
- for (let index = 0; index < capsules.accepted.length; index++) {
402
- const acceptedCapsule = capsules.accepted[index];
403
- const obs = acceptedCapsule.observation;
404
- const sourceEventIds = acceptedCapsule.capsule.sourceEvidenceIds;
405
- const quality = evaluateObservationQuality({
406
- ...obs,
407
- source: 'distill',
408
- sourceEventIds,
409
- }, sourceEvents.filter(event => sourceEventIds.includes(event.id)));
410
- const exact = engine.store.findObservationByDedupKey(observationDedupKey(obs.type, obs.title));
411
- if (exact) {
412
- engine.store.linkObservationToEvents(exact.id, sourceEventIds);
413
- engine.store.incrementObservationAccess(exact.id);
414
- committedObservationCount += 1;
415
- continue;
416
- }
417
- const semantic = normalizeSemanticMemory({
418
- project: this.projectRoot,
419
- type: obs.type,
420
- title: obs.title,
421
- narrative: obs.narrative ?? '',
422
- concepts: obs.concepts,
423
- subject: obs.subject,
424
- predicate: obs.predicate,
425
- value: obs.value,
426
- polarity: obs.polarity,
427
- entities: obs.entities,
428
- temporal: obs.temporal,
429
- });
430
- engine.store.acceptSemanticObservation({
431
- sessionId: lease.sessionIds[0] ?? '',
432
- project: this.projectRoot,
433
- type: obs.type,
434
- title: obs.title,
435
- narrative: obs.narrative,
436
- 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: [],
437
460
  concepts: mergeSemanticEntities(normalizeBatchConcepts(obs), [semantic.subject, semantic.predicate, semantic.value], semantic.entities.map(normalizeEntityKey)),
438
- importance: obs.importance ?? 5,
439
- confidence: obs.confidence,
440
- source: 'distill',
441
- sourceEventIds,
442
- qualityStatus: quality.status,
443
- quarantineReason: quality.reason,
444
- qualityDetails: quality.details,
445
- failureCategory: quality.failureCategory ?? undefined,
446
- semantic,
447
- capsule: acceptedCapsule.capsule,
448
- projection: acceptedCapsule.projection,
449
- });
450
- committedObservationCount += 1;
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);
483
+ }
451
484
  }
452
- if (committedObservationCount > 0)
453
- engine.store.markEventsProcessed(lease.eventIds);
485
+ }
486
+ const transaction = this.getStoreDb().transaction(() => {
487
+ if (acceptedSourceEventIds.size > 0)
488
+ engine.store.markEventsProcessed([...acceptedSourceEventIds]);
454
489
  leases.createReceipt(batchId, validObs.length, lease.eventCount);
455
490
  leases.commitLease(batchId);
456
491
  });
@@ -460,7 +495,12 @@ export class MemoryGate {
460
495
  identity: { projectRoot: this.projectRoot, sessionId: lease.sessionIds[0] },
461
496
  level: 'info',
462
497
  timestamp: Date.now(),
463
- data: { stage: 'distill_batch', batchId, observationCount: validObs.length, eventsMarked: lease.eventIds.length },
498
+ data: {
499
+ stage: 'distill_batch',
500
+ batchId,
501
+ observationCount: validObs.length,
502
+ eventsMarked: acceptedSourceEventIds.size,
503
+ },
464
504
  });
465
505
  return validObs.length;
466
506
  }
@@ -513,75 +553,92 @@ export class MemoryGate {
513
553
  if (capsules.rejected.length > 0 || capsules.accepted.length !== validObs.length) {
514
554
  throw new Error(`memory_commit_turn failed semantic capsule validation: ${JSON.stringify(capsules.rejected)}`);
515
555
  }
516
- const saved = [];
517
556
  const existingIds = new Set(engine.store.listObservations().map(observation => observation.id));
518
557
  const turnId = eventTurnId(event, turnEventId);
519
- this.getStoreDb().transaction(() => {
520
- for (const acceptedCapsule of capsules.accepted) {
521
- const observation = acceptedCapsule.observation;
522
- const quality = evaluateObservationQuality({
523
- ...observation,
524
- source: 'manual',
525
- sourceEventIds: acceptedCapsule.capsule.sourceEvidenceIds,
526
- }, [event]);
527
- const semantic = normalizeSemanticMemory({
528
- project: this.projectRoot,
529
- type: observation.type,
530
- title: observation.title,
531
- narrative: observation.narrative ?? '',
532
- concepts: observation.concepts,
533
- subject: observation.subject,
534
- predicate: observation.predicate,
535
- value: observation.value,
536
- polarity: observation.polarity,
537
- entities: observation.entities,
538
- temporal: observation.temporal,
539
- });
540
- const write = engine.store.acceptSemanticObservation({
541
- sessionId,
542
- project: this.projectRoot,
543
- type: observation.type,
544
- title: observation.title,
545
- narrative: observation.narrative,
546
- 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: [],
547
605
  concepts: mergeSemanticEntities(normalizeBatchConcepts(observation), [semantic.subject, semantic.predicate, semantic.value], semantic.entities.map(normalizeEntityKey)),
548
- importance: observation.importance,
549
- confidence: observation.confidence,
550
- source: 'distill',
551
- sourceEventIds: [turnEventId],
552
- qualityStatus: quality.status,
553
- quarantineReason: quality.reason,
554
- qualityDetails: quality.details,
555
- failureCategory: quality.failureCategory ?? undefined,
556
- semantic,
557
- capsule: acceptedCapsule.capsule,
558
- projection: acceptedCapsule.projection,
559
- });
560
- const row = write.row;
561
- saved.push({
562
- id: row.id,
563
- type: row.type,
564
- title: row.title,
565
- content: row.narrative,
566
- source: row.source,
567
- scope: row.project,
568
- confidence: row.confidence,
569
- importance: row.importance,
570
- score: 1,
571
- 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,
572
628
  indexStatus: row.embedding ? 'indexed' : 'pending',
573
- });
629
+ }] : [];
630
+ });
631
+ this.getStoreDb().transaction(() => {
632
+ for (const row of saved) {
574
633
  engine.store.recordPendingTurnObservation({
575
- turnEventId,
576
- turnId,
577
- sessionId,
578
- project: this.projectRoot,
579
- observationId: row.id,
580
- createdByTurn: !existingIds.has(row.id),
634
+ turnEventId, turnId, sessionId, project: this.projectIdentity.memoryNamespace,
635
+ observationId: row.id, createdByTurn: !existingIds.has(row.id),
581
636
  createdAt: event.created_at,
582
637
  });
583
638
  }
584
- engine.store.markEventsProcessed([turnEventId]);
639
+ if (applied.decisions.some(decision => decision.operation !== 'hold')) {
640
+ engine.store.markEventsProcessed([turnEventId]);
641
+ }
585
642
  })();
586
643
  return saved;
587
644
  }
@@ -615,110 +672,85 @@ export class MemoryGate {
615
672
  if (capsules.rejected.length > 0 || capsules.accepted.length !== validObs.length) {
616
673
  throw new Error(`Explicit memory refinement failed evidence validation: ${JSON.stringify(capsules.rejected)}`);
617
674
  }
618
- const existingIds = new Set(engine.store.listObservations().map(observation => observation.id));
619
675
  const turnId = eventTurnId(event, turnEventId);
620
- const memories = [];
621
- const retiredMemoryIds = [];
622
- const operations = [];
623
- this.getStoreDb().transaction(() => {
624
- for (const acceptedCapsule of capsules.accepted) {
625
- const observation = acceptedCapsule.observation;
626
- const semantic = normalizeSemanticMemory({
627
- project: this.projectRoot,
628
- type: observation.type,
629
- title: observation.title,
630
- narrative: observation.narrative ?? '',
631
- concepts: observation.concepts,
632
- subject: observation.subject,
633
- predicate: observation.predicate,
634
- value: observation.value,
635
- polarity: observation.polarity,
636
- entities: observation.entities,
637
- temporal: observation.temporal,
638
- });
639
- const write = engine.store.acceptSemanticObservation({
640
- sessionId,
641
- project: this.projectRoot,
642
- type: observation.type,
643
- title: observation.title,
644
- 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,
645
723
  facts: [...new Set([...(observation.facts ?? []), ...(observation.evidenceSpans ?? [])])],
646
724
  concepts: mergeSemanticEntities(normalizeBatchConcepts(observation), [semantic.subject, semantic.predicate, semantic.value], semantic.entities.map(normalizeEntityKey)),
647
- importance: Math.max(8, observation.importance),
648
- confidence: Math.max(0.9, observation.confidence),
649
- source: 'explicit_intent',
650
- sourceEventIds: [turnEventId],
651
- qualityStatus: 'accepted',
652
- evidenceStatus: 'verified',
653
- evidenceDetails: (observation.evidenceSpans ?? []).map(span => ({
654
- kind: 'host_evidence_span',
655
- source: turnEventId,
656
- summary: span,
657
- })),
658
- semantic,
659
- capsule: acceptedCapsule.capsule,
660
- projection: acceptedCapsule.projection,
661
- preferIncomingNarrative: true,
662
- });
663
- const row = write.row;
664
- operations.push(write.action.toLocaleLowerCase('en-US'));
665
- memories.push({
666
- id: row.id,
667
- type: row.type,
668
- title: row.title,
669
- content: row.narrative,
670
- source: row.source,
671
- scope: row.project,
672
- confidence: row.confidence,
673
- importance: row.importance,
674
- score: 1,
675
- 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,
676
739
  indexStatus: row.embedding ? 'indexed' : 'pending',
677
- });
678
- engine.store.recordPendingTurnObservation({
679
- turnEventId,
680
- turnId,
681
- sessionId,
682
- project: this.projectRoot,
683
- observationId: row.id,
684
- createdByTurn: !existingIds.has(row.id),
685
- createdAt: event.created_at,
686
- });
687
- }
688
- const constraintSpans = turnConstraints.flatMap(candidate => candidate.evidenceSpans ?? []);
689
- const baseline = engine.store.listObservations({ project: this.projectRoot })
690
- .filter(observation => observation.source === 'explicit_intent')
691
- .filter(observation => parseLearningStringArray(observation.source_event_ids).includes(turnEventId));
692
- const refinedIds = new Set(memories.map(memory => memory.id));
693
- for (const observation of baseline) {
694
- if (refinedIds.has(observation.id) || observation.resolution_state === 'superseded')
695
- continue;
696
- const replacementIndex = durableCandidates.findIndex(candidate => ((candidate.evidenceSpans ?? []).some(span => sameGroundedClause(observation.narrative, span))));
697
- const replacement = replacementIndex >= 0
698
- ? memories[replacementIndex]
699
- : durableCandidates.length === 1 ? memories[0] : undefined;
700
- if (!replacement || replacement.id === observation.id)
701
- continue;
702
- engine.store.setObservationResolution(observation.id, 'superseded', replacement.id);
703
- engine.store.addSemanticObservationRelation(replacement.id, observation.id, 'supersedes');
704
- retiredMemoryIds.push(observation.id);
705
- operations.push('supersede');
706
- }
707
- for (const observation of baseline) {
708
- if (observation.resolution_state === 'superseded' || retiredMemoryIds.includes(observation.id))
709
- continue;
710
- if (!constraintSpans.some(span => sameGroundedClause(observation.narrative, span)))
711
- continue;
712
- if (engine.store.quarantineObservation(observation.id, 'host_classified_turn_constraint', {
713
- turnId,
714
- eventId: turnEventId,
715
- evidenceSpans: constraintSpans,
716
- })) {
717
- retiredMemoryIds.push(observation.id);
718
- operations.push('retire');
719
- }
720
- }
721
- })();
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
+ }
722
754
  emitSemanticControlLog({
723
755
  event: 'memory.semantic.refined',
724
756
  identity: { projectRoot: this.projectRoot, sessionId, turnId },
@@ -750,7 +782,7 @@ export class MemoryGate {
750
782
  turnEventId,
751
783
  turnId: eventTurnId(event, turnEventId),
752
784
  sessionId,
753
- project: this.projectRoot,
785
+ project: this.projectIdentity.memoryNamespace,
754
786
  status: 'skipped',
755
787
  reason,
756
788
  });
@@ -760,7 +792,7 @@ export class MemoryGate {
760
792
  await this.forceWarmUp();
761
793
  return this.requireEngine().store.finalizeTurnObservationDecision({
762
794
  ...input,
763
- project: this.projectRoot,
795
+ project: this.projectIdentity.memoryNamespace,
764
796
  });
765
797
  }
766
798
  async reconcileTurnObservationLineage(turns) {
@@ -779,7 +811,7 @@ export class MemoryGate {
779
811
  turnEventId: turn.eventId,
780
812
  turnId: turn.turnId,
781
813
  sessionId: turn.sessionId,
782
- project: this.projectRoot,
814
+ project: this.projectIdentity.memoryNamespace,
783
815
  status: turn.status,
784
816
  receiptId: turn.receiptId,
785
817
  memoryIds: turn.memoryIds,
@@ -795,71 +827,6 @@ export class MemoryGate {
795
827
  logInvalidDistillBatch(batchId) {
796
828
  logInvalidObservationBatch(this.projectRoot, batchId);
797
829
  }
798
- /**
799
- * Saves distilled observations from Claude and marks source events as processed.
800
- * @deprecated Use saveDistilledObservationsByBatch(batchId, observations) instead.
801
- */
802
- async saveDistilledObservations(observations, sessionId) {
803
- emitSemanticControlLog({
804
- event: 'memory.write.degraded',
805
- identity: { projectRoot: this.projectRoot, sessionId },
806
- level: 'warn',
807
- timestamp: Date.now(),
808
- data: { stage: 'legacy_distill', reason: 'deprecated_session_write', fallback: 'legacy_compatibility_path' },
809
- });
810
- if (!this.engine)
811
- return 0;
812
- const validObs = validateObservationBatch(observations);
813
- if (!validObs) {
814
- logInvalidObservationBatch(this.projectRoot, `legacy:${sessionId}`);
815
- return 0;
816
- }
817
- const engine = this.engine;
818
- engine.store.ensureSession(sessionId, this.projectRoot, 'Distilled observation');
819
- const events = engine.store.getSessionEvents(sessionId);
820
- const unprocessedIds = events.filter(e => !e.processed).map(e => e.id);
821
- let committedObservationCount = 0;
822
- const transaction = this.getStoreDb().transaction(() => {
823
- for (let index = 0; index < validObs.length; index += 1) {
824
- const obs = validObs[index];
825
- const quality = evaluateObservationQuality({
826
- ...obs,
827
- source: 'distill',
828
- sourceEventIds: unprocessedIds,
829
- }, events.filter(event => unprocessedIds.includes(event.id)));
830
- const row = engine.store.addObservation({
831
- sessionId,
832
- project: this.projectRoot,
833
- type: obs.type,
834
- title: obs.title,
835
- narrative: obs.narrative,
836
- facts: obs.facts,
837
- concepts: obs.concepts,
838
- importance: obs.importance,
839
- confidence: obs.confidence,
840
- source: 'distill',
841
- sourceEventIds: unprocessedIds,
842
- qualityStatus: quality.status,
843
- quarantineReason: quality.reason,
844
- qualityDetails: quality.details,
845
- failureCategory: quality.failureCategory ?? undefined,
846
- });
847
- engine.store.linkObservationToEvents(row.id, unprocessedIds);
848
- committedObservationCount += 1;
849
- }
850
- if (committedObservationCount > 0)
851
- engine.store.markEventsProcessed(unprocessedIds);
852
- });
853
- transaction();
854
- emitSemanticControlLog({
855
- event: 'memory.write.completed',
856
- identity: { projectRoot: this.projectRoot, sessionId },
857
- level: 'info',
858
- timestamp: Date.now(),
859
- data: { stage: 'legacy_distill', observationCount: validObs.length, eventsMarked: unprocessedIds.length },
860
- });
861
- return validObs.length;
862
- }
863
830
  /** Returns the count of unprocessed events (for session-start hook). */
864
831
  getPendingEventCount(sessionId, triage = true) {
865
832
  if (!this.engine)
@@ -927,7 +894,7 @@ export class MemoryGate {
927
894
  event: 'memory.engine.transitioned', identity: { projectRoot: this.projectRoot }, level: 'info', timestamp: Date.now(),
928
895
  data: { from: 'cold', to: 'warming' },
929
896
  });
930
- this.engine = new MemoryEngine(this.projectRoot, this.embedder);
897
+ this.engine = new MemoryEngine(this.projectRoot, this.embedder, this.projectIdentity);
931
898
  this.setLeaseStore(new LeaseStore(this.getStoreDb()));
932
899
  // Clean up zombie sessions (>24h active)
933
900
  try {
@@ -1033,14 +1000,14 @@ export class MemoryGate {
1033
1000
  getRetrievalRevision() {
1034
1001
  return this.engine?.getRetrievalRevision() ?? 0;
1035
1002
  }
1036
- async saveExplicitMemoryIntent(content, sessionId, sourceEventId) {
1003
+ async saveExplicitMemoryIntent(content, provenance, sourceEventId) {
1037
1004
  if (!this.engine)
1038
1005
  await this.forceWarmUp();
1039
1006
  try {
1040
- const result = await this.requireEngine().saveExplicitMemoryIntent(content, sessionId, sourceEventId);
1007
+ const result = await this.requireEngine().saveExplicitMemoryIntent(content, provenance, sourceEventId);
1041
1008
  emitSemanticControlLog({
1042
1009
  event: 'memory.write.completed',
1043
- identity: { projectRoot: this.projectRoot, sessionId },
1010
+ identity: { projectRoot: this.projectRoot, projectId: this.projectIdentity.projectId, sessionId: provenance.sessionId },
1044
1011
  level: 'info',
1045
1012
  timestamp: Date.now(),
1046
1013
  data: {
@@ -1053,7 +1020,7 @@ export class MemoryGate {
1053
1020
  }
1054
1021
  catch (error) {
1055
1022
  emitSemanticControlLog({
1056
- 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(),
1057
1024
  data: { stage: 'explicit_memory', reason: memoryErrorReason(error) },
1058
1025
  });
1059
1026
  throw error;
@@ -1084,7 +1051,7 @@ export class MemoryGate {
1084
1051
  await this.forceWarmUp();
1085
1052
  return this.requireEngine().store.listObservations({
1086
1053
  sessionId,
1087
- project: this.projectRoot,
1054
+ project: this.projectIdentity.memoryNamespace,
1088
1055
  qualityStatus: 'accepted',
1089
1056
  resolutionState: 'current',
1090
1057
  }).map(row => ({
@@ -1131,9 +1098,6 @@ export class MemoryGate {
1131
1098
  let indexed = [];
1132
1099
  try {
1133
1100
  const embeddings = await this.embedder.embedBatch(texts);
1134
- if (embeddings.length !== rows.length) {
1135
- throw new Error(`Observation vector count mismatch: ${rows.length} observations, ${embeddings.length} vectors`);
1136
- }
1137
1101
  indexed = rows.map((row, index) => ({ id: row.id, embedding: embeddings[index] }));
1138
1102
  }
1139
1103
  catch {
@@ -1167,15 +1131,15 @@ export class MemoryGate {
1167
1131
  await this.forceWarmUp();
1168
1132
  return this.requireEngine().getCandidates();
1169
1133
  }
1170
- async approveCandidate(id) {
1134
+ async approveCandidate(id, provenance) {
1171
1135
  if (!this.engine)
1172
1136
  await this.forceWarmUp();
1173
- await this.requireEngine().approveCandidate(id);
1137
+ await this.requireEngine().approveCandidate(id, provenance);
1174
1138
  }
1175
- async rejectCandidate(id) {
1139
+ async rejectCandidate(id, provenance) {
1176
1140
  if (!this.engine)
1177
1141
  await this.forceWarmUp();
1178
- await this.requireEngine().rejectCandidate(id);
1142
+ await this.requireEngine().rejectCandidate(id, provenance);
1179
1143
  }
1180
1144
  async search(query, options) {
1181
1145
  if (!this.engine)
@@ -1213,41 +1177,46 @@ export class MemoryGate {
1213
1177
  throw error;
1214
1178
  }
1215
1179
  }
1180
+ async searchWithDiagnostics(query, options) {
1181
+ if (!this.engine)
1182
+ await this.forceWarmUp();
1183
+ return this.requireEngine().searchWithDiagnostics(query, options);
1184
+ }
1216
1185
  async listMemories(filter) {
1217
1186
  if (!this.engine)
1218
1187
  await this.forceWarmUp();
1219
1188
  return this.requireEngine().listMemories(filter);
1220
1189
  }
1221
- async addMemory(entry) {
1190
+ async addMemory(entry, provenance) {
1222
1191
  if (!this.engine) {
1223
1192
  await this.forceWarmUp();
1224
1193
  }
1225
- return this.requireEngine().addMemory(entry);
1194
+ return this.requireEngine().addMemory(entry, provenance);
1226
1195
  }
1227
- async updateMemory(id, updates) {
1196
+ async updateMemory(id, updates, provenance) {
1228
1197
  if (!this.engine)
1229
1198
  await this.forceWarmUp();
1230
- await this.requireEngine().updateMemory(id, updates);
1199
+ await this.requireEngine().updateMemory(id, updates, provenance);
1231
1200
  }
1232
- async deleteMemory(id) {
1201
+ async deleteMemory(id, provenance) {
1233
1202
  if (!this.engine)
1234
1203
  await this.forceWarmUp();
1235
- await this.requireEngine().deleteMemory(id);
1204
+ await this.requireEngine().deleteMemory(id, provenance);
1236
1205
  }
1237
1206
  async getPendingMemories() {
1238
1207
  if (!this.engine)
1239
1208
  await this.forceWarmUp();
1240
1209
  return this.requireEngine().getPendingMemories();
1241
1210
  }
1242
- async approveMemory(id) {
1211
+ async approveMemory(id, provenance) {
1243
1212
  if (!this.engine)
1244
1213
  await this.forceWarmUp();
1245
- await this.requireEngine().approveMemory(id);
1214
+ await this.requireEngine().approveMemory(id, provenance);
1246
1215
  }
1247
- async rejectMemory(id) {
1216
+ async rejectMemory(id, provenance) {
1248
1217
  if (!this.engine)
1249
1218
  await this.forceWarmUp();
1250
- await this.requireEngine().rejectMemory(id);
1219
+ await this.requireEngine().rejectMemory(id, provenance);
1251
1220
  }
1252
1221
  async extractFromEventsRealtime(events) {
1253
1222
  if (!this.engine)
@@ -1278,7 +1247,18 @@ export class MemoryGate {
1278
1247
  const engine = this.requireEngine();
1279
1248
  const eventExists = engine.store.getSessionEvents(event.sessionId).some(existing => existing.id === event.id);
1280
1249
  if (!eventExists) {
1281
- 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
+ });
1282
1262
  }
1283
1263
  }
1284
1264
  /** Drain the signal buffer without running summarizer. Events are already persisted. */
@@ -1328,14 +1308,62 @@ function readCanonicalPrompt(payloadJson, userMessage) {
1328
1308
  }
1329
1309
  return userMessage?.trim() ?? '';
1330
1310
  }
1331
- function sameGroundedClause(narrative, evidenceSpan) {
1332
- const left = narrative.normalize('NFKC').trim().toLocaleLowerCase('en-US');
1333
- const right = evidenceSpan.normalize('NFKC').trim().toLocaleLowerCase('en-US');
1334
- if (left.length < 4 || right.length < 4)
1335
- return false;
1336
- 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
+ };
1337
1365
  }
1338
- // --- L1 Rule Scorer (backward compat) ---
1366
+ // --- L1 Rule Scorer ---
1339
1367
  function scoreL1Rules(event) {
1340
1368
  let score = 0;
1341
1369
  const stderrText = event.stderr ?? '';
@@ -1399,5 +1427,34 @@ function scoreDialogSignals(message) {
1399
1427
  function memoryErrorReason(error) {
1400
1428
  return (error instanceof Error ? error.message : String(error)).trim().slice(0, 500) || 'memory_operation_failed';
1401
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
+ }
1402
1459
  export { scoreL1Rules, scoreDialogSignals };
1403
1460
  //# sourceMappingURL=memory-gate.js.map