@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.
- package/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/consolidator.d.ts.map +1 -1
- package/dist/consolidator.js +2 -0
- package/dist/consolidator.js.map +1 -1
- package/dist/durable-memory-text.js +1 -1
- package/dist/durable-memory-text.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 -74
- 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 +377 -320
- 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 -35
- package/dist/memory-store.d.ts.map +1 -1
- package/dist/memory-store.js +406 -690
- 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,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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
|
|
453
|
-
|
|
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: {
|
|
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
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
|
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,
|
|
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.
|
|
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(
|
|
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
|
|
1332
|
-
|
|
1333
|
-
const
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
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
|
|
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
|