@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.
- package/LICENSE +21 -0
- package/dist/consolidator.d.ts.map +1 -1
- package/dist/consolidator.js +2 -0
- package/dist/consolidator.js.map +1 -1
- package/dist/embedding-model-store.d.ts +1 -4
- package/dist/embedding-model-store.d.ts.map +1 -1
- package/dist/embedding-model-store.js +3 -75
- package/dist/embedding-model-store.js.map +1 -1
- package/dist/embedding-provider.d.ts +3 -2
- package/dist/embedding-provider.d.ts.map +1 -1
- package/dist/embedding-provider.js +151 -43
- package/dist/embedding-provider.js.map +1 -1
- package/dist/hybrid-search.d.ts +5 -1
- package/dist/hybrid-search.d.ts.map +1 -1
- package/dist/hybrid-search.js +92 -56
- package/dist/hybrid-search.js.map +1 -1
- package/dist/index.d.ts +16 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/lease-store.d.ts.map +1 -1
- package/dist/lease-store.js +30 -0
- package/dist/lease-store.js.map +1 -1
- package/dist/memory-command-service.d.ts +73 -0
- package/dist/memory-command-service.d.ts.map +1 -0
- package/dist/memory-command-service.js +283 -0
- package/dist/memory-command-service.js.map +1 -0
- package/dist/memory-engine.d.ts +19 -15
- package/dist/memory-engine.d.ts.map +1 -1
- package/dist/memory-engine.js +207 -158
- package/dist/memory-engine.js.map +1 -1
- package/dist/memory-episode-repository.d.ts +77 -0
- package/dist/memory-episode-repository.d.ts.map +1 -0
- package/dist/memory-episode-repository.js +262 -0
- package/dist/memory-episode-repository.js.map +1 -0
- package/dist/memory-fact-provider-port.d.ts +83 -0
- package/dist/memory-fact-provider-port.d.ts.map +1 -0
- package/dist/memory-fact-provider-port.js +22 -0
- package/dist/memory-fact-provider-port.js.map +1 -0
- package/dist/memory-fact-validator.d.ts +16 -0
- package/dist/memory-fact-validator.d.ts.map +1 -0
- package/dist/memory-fact-validator.js +52 -0
- package/dist/memory-fact-validator.js.map +1 -0
- package/dist/memory-gate.d.ts +14 -17
- package/dist/memory-gate.d.ts.map +1 -1
- package/dist/memory-gate.js +369 -329
- package/dist/memory-gate.js.map +1 -1
- package/dist/memory-identity-compiler.d.ts +18 -0
- package/dist/memory-identity-compiler.d.ts.map +1 -0
- package/dist/memory-identity-compiler.js +26 -0
- package/dist/memory-identity-compiler.js.map +1 -0
- package/dist/memory-index-projector.d.ts +22 -0
- package/dist/memory-index-projector.d.ts.map +1 -0
- package/dist/memory-index-projector.js +11 -0
- package/dist/memory-index-projector.js.map +1 -0
- package/dist/memory-lifecycle-reducer.d.ts +21 -0
- package/dist/memory-lifecycle-reducer.d.ts.map +1 -0
- package/dist/memory-lifecycle-reducer.js +34 -0
- package/dist/memory-lifecycle-reducer.js.map +1 -0
- package/dist/memory-maintenance.d.ts +1 -4
- package/dist/memory-maintenance.d.ts.map +1 -1
- package/dist/memory-maintenance.js +3 -20
- package/dist/memory-maintenance.js.map +1 -1
- package/dist/memory-query-service.d.ts +24 -0
- package/dist/memory-query-service.d.ts.map +1 -0
- package/dist/memory-query-service.js +180 -0
- package/dist/memory-query-service.js.map +1 -0
- package/dist/memory-relevance.d.ts +16 -1
- package/dist/memory-relevance.d.ts.map +1 -1
- package/dist/memory-relevance.js +83 -139
- package/dist/memory-relevance.js.map +1 -1
- package/dist/memory-store.d.ts +52 -36
- package/dist/memory-store.d.ts.map +1 -1
- package/dist/memory-store.js +406 -704
- package/dist/memory-store.js.map +1 -1
- package/dist/semantic-memory-compiler.d.ts +0 -2
- package/dist/semantic-memory-compiler.d.ts.map +1 -1
- package/dist/semantic-memory-compiler.js +4 -4
- package/dist/semantic-memory-compiler.js.map +1 -1
- package/dist/session-summarizer.d.ts +3 -0
- package/dist/session-summarizer.d.ts.map +1 -1
- package/dist/session-summarizer.js.map +1 -1
- package/dist/signal-analyzer.d.ts +2 -2
- package/dist/signal-analyzer.d.ts.map +1 -1
- package/dist/signal-analyzer.js +10 -16
- package/dist/signal-analyzer.js.map +1 -1
- package/package.json +14 -5
- package/dist/lease-store.test.d.ts +0 -2
- package/dist/lease-store.test.d.ts.map +0 -1
- package/dist/lease-store.test.js +0 -182
- package/dist/lease-store.test.js.map +0 -1
- package/dist/migrations/v10-to-v11.d.ts +0 -3
- package/dist/migrations/v10-to-v11.d.ts.map +0 -1
- package/dist/migrations/v10-to-v11.js +0 -21
- package/dist/migrations/v10-to-v11.js.map +0 -1
- package/dist/migrations/v11-to-v12.d.ts +0 -66
- package/dist/migrations/v11-to-v12.d.ts.map +0 -1
- package/dist/migrations/v11-to-v12.js +0 -577
- package/dist/migrations/v11-to-v12.js.map +0 -1
- package/dist/migrations/v12-to-v13.d.ts +0 -3
- package/dist/migrations/v12-to-v13.d.ts.map +0 -1
- package/dist/migrations/v12-to-v13.js +0 -50
- package/dist/migrations/v12-to-v13.js.map +0 -1
- package/dist/migrations/v2-to-v3.d.ts +0 -3
- package/dist/migrations/v2-to-v3.d.ts.map +0 -1
- package/dist/migrations/v2-to-v3.js +0 -122
- package/dist/migrations/v2-to-v3.js.map +0 -1
- package/dist/migrations/v2-to-v3.test.d.ts +0 -2
- package/dist/migrations/v2-to-v3.test.d.ts.map +0 -1
- package/dist/migrations/v2-to-v3.test.js +0 -88
- package/dist/migrations/v2-to-v3.test.js.map +0 -1
- package/dist/migrations/v3-to-v4.d.ts +0 -3
- package/dist/migrations/v3-to-v4.d.ts.map +0 -1
- package/dist/migrations/v3-to-v4.js +0 -97
- package/dist/migrations/v3-to-v4.js.map +0 -1
- package/dist/migrations/v4-to-v5.d.ts +0 -3
- package/dist/migrations/v4-to-v5.d.ts.map +0 -1
- package/dist/migrations/v4-to-v5.js +0 -34
- package/dist/migrations/v4-to-v5.js.map +0 -1
- package/dist/migrations/v4-to-v5.test.d.ts +0 -2
- package/dist/migrations/v4-to-v5.test.d.ts.map +0 -1
- package/dist/migrations/v4-to-v5.test.js +0 -32
- package/dist/migrations/v4-to-v5.test.js.map +0 -1
- package/dist/migrations/v6-to-v7.d.ts +0 -3
- package/dist/migrations/v6-to-v7.d.ts.map +0 -1
- package/dist/migrations/v6-to-v7.js +0 -18
- package/dist/migrations/v6-to-v7.js.map +0 -1
- package/dist/migrations/v6-to-v7.test.d.ts +0 -2
- package/dist/migrations/v6-to-v7.test.d.ts.map +0 -1
- package/dist/migrations/v6-to-v7.test.js +0 -33
- package/dist/migrations/v6-to-v7.test.js.map +0 -1
- package/dist/migrations/v7-to-v8.d.ts +0 -3
- package/dist/migrations/v7-to-v8.d.ts.map +0 -1
- package/dist/migrations/v7-to-v8.js +0 -20
- package/dist/migrations/v7-to-v8.js.map +0 -1
- package/dist/migrations/v8-to-v9.d.ts +0 -3
- package/dist/migrations/v8-to-v9.d.ts.map +0 -1
- package/dist/migrations/v8-to-v9.js +0 -18
- package/dist/migrations/v8-to-v9.js.map +0 -1
- package/dist/migrations/v9-to-v10.d.ts +0 -3
- package/dist/migrations/v9-to-v10.d.ts.map +0 -1
- package/dist/migrations/v9-to-v10.js +0 -45
- package/dist/migrations/v9-to-v10.js.map +0 -1
- package/dist/migrations/v9-to-v10.test.d.ts +0 -2
- package/dist/migrations/v9-to-v10.test.d.ts.map +0 -1
- package/dist/migrations/v9-to-v10.test.js +0 -40
- package/dist/migrations/v9-to-v10.test.js.map +0 -1
- package/dist/preference-conflicts.test.d.ts +0 -2
- package/dist/preference-conflicts.test.d.ts.map +0 -1
- package/dist/preference-conflicts.test.js +0 -80
- package/dist/preference-conflicts.test.js.map +0 -1
- package/dist/semantic-memory.test.d.ts +0 -2
- package/dist/semantic-memory.test.d.ts.map +0 -1
- package/dist/semantic-memory.test.js +0 -61
- package/dist/semantic-memory.test.js.map +0 -1
package/dist/memory-gate.js
CHANGED
|
@@ -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,
|
|
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(
|
|
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
|
|
128
|
-
//
|
|
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
|
|
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
|
-
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
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
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
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
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
|
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,
|
|
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.
|
|
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(
|
|
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
|
|
1349
|
-
|
|
1350
|
-
const
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
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
|
|
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
|