@xdarkicex/openclaw-memory-libravdb 1.4.2 → 1.4.4
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/README.md +76 -16
- package/docs/README.md +3 -12
- package/docs/architecture.md +68 -153
- package/docs/contributing.md +1 -2
- package/docs/embedding-profiles.md +5 -6
- package/docs/installation.md +1 -1
- package/openclaw.plugin.json +65 -2
- package/package.json +2 -2
- package/src/cli.ts +34 -0
- package/src/comparison-experiments.ts +128 -0
- package/src/context-engine.ts +276 -62
- package/src/dream-promotion.ts +492 -0
- package/src/dream-routing.ts +40 -0
- package/src/index.ts +16 -1
- package/src/markdown-hash.ts +104 -0
- package/src/markdown-ingest.ts +627 -0
- package/src/memory-runtime.ts +32 -9
- package/src/scoring.ts +6 -3
- package/src/temporal.ts +657 -80
- package/src/types.ts +48 -0
- package/docs/ast-v2.md +0 -167
- package/docs/ast.md +0 -70
- package/docs/compaction-evaluation.md +0 -182
- package/docs/continuity.md +0 -708
- package/docs/elevated-guidance.md +0 -258
- package/docs/gating.md +0 -134
- package/docs/implementation.md +0 -447
- package/docs/mathematics-v2.md +0 -1879
- package/docs/mathematics.md +0 -695
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export const COMPARISON_ABLATION_MODES = [
|
|
2
|
+
"reserve_bump",
|
|
3
|
+
"protected_pair_pack",
|
|
4
|
+
"discriminative_affiliation",
|
|
5
|
+
"witness_position_pastness",
|
|
6
|
+
"contamination_penalty",
|
|
7
|
+
"pair_score_on_witness",
|
|
8
|
+
"comparison_blend",
|
|
9
|
+
] as const;
|
|
10
|
+
|
|
11
|
+
export type ComparisonAblationMode = typeof COMPARISON_ABLATION_MODES[number];
|
|
12
|
+
|
|
13
|
+
export interface ComparisonProfileSummary {
|
|
14
|
+
ablationMode?: ComparisonAblationMode;
|
|
15
|
+
rankTotalMs: number;
|
|
16
|
+
decorateMs: number;
|
|
17
|
+
slotCoverageMs: number;
|
|
18
|
+
sideAffiliationMs: number;
|
|
19
|
+
specificityMs: number;
|
|
20
|
+
pairSelectionMs: number;
|
|
21
|
+
greedyFillMs: number;
|
|
22
|
+
recoveryPackingMs: number;
|
|
23
|
+
debugBuildMs: number;
|
|
24
|
+
rawCandidateCount: number;
|
|
25
|
+
comparisonCandidateCount: number;
|
|
26
|
+
side0AffiliatedCount: number;
|
|
27
|
+
side1AffiliatedCount: number;
|
|
28
|
+
normalizeTermsCalls: number;
|
|
29
|
+
normalizeContentTermsCalls: number;
|
|
30
|
+
estimateTokensCalls: number;
|
|
31
|
+
sortCalls: number;
|
|
32
|
+
totalSortedLength: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ComparisonExperimentConfig {
|
|
36
|
+
profilingEnabled: boolean;
|
|
37
|
+
ablationMode: ComparisonAblationMode | null;
|
|
38
|
+
disableReserveBump: boolean;
|
|
39
|
+
disableProtectedPairPack: boolean;
|
|
40
|
+
disableDiscriminativeAffiliation: boolean;
|
|
41
|
+
disableWitnessPositionPastness: boolean;
|
|
42
|
+
disableContaminationPenalty: boolean;
|
|
43
|
+
disablePairScoreOnWitness: boolean;
|
|
44
|
+
disableComparisonBlend: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function resolveComparisonExperimentConfig(
|
|
48
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
49
|
+
): ComparisonExperimentConfig {
|
|
50
|
+
const profilingEnabled = env.LONGMEMEVAL_PROFILE_COMPARISON === "1";
|
|
51
|
+
const rawMode = env.LONGMEMEVAL_COMPARISON_PROFILE_MODE?.trim() ?? "";
|
|
52
|
+
const ablationMode = COMPARISON_ABLATION_MODES.includes(rawMode as ComparisonAblationMode)
|
|
53
|
+
? rawMode as ComparisonAblationMode
|
|
54
|
+
: null;
|
|
55
|
+
return {
|
|
56
|
+
profilingEnabled,
|
|
57
|
+
ablationMode,
|
|
58
|
+
disableReserveBump: ablationMode === "reserve_bump",
|
|
59
|
+
disableProtectedPairPack: ablationMode === "protected_pair_pack",
|
|
60
|
+
disableDiscriminativeAffiliation: ablationMode === "discriminative_affiliation",
|
|
61
|
+
disableWitnessPositionPastness: ablationMode === "witness_position_pastness",
|
|
62
|
+
disableContaminationPenalty: ablationMode === "contamination_penalty",
|
|
63
|
+
disablePairScoreOnWitness: ablationMode === "pair_score_on_witness",
|
|
64
|
+
disableComparisonBlend: ablationMode === "comparison_blend",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createComparisonProfileSummary(
|
|
69
|
+
ablationMode: ComparisonAblationMode | null,
|
|
70
|
+
): ComparisonProfileSummary {
|
|
71
|
+
return {
|
|
72
|
+
ablationMode: ablationMode ?? undefined,
|
|
73
|
+
rankTotalMs: 0,
|
|
74
|
+
decorateMs: 0,
|
|
75
|
+
slotCoverageMs: 0,
|
|
76
|
+
sideAffiliationMs: 0,
|
|
77
|
+
specificityMs: 0,
|
|
78
|
+
pairSelectionMs: 0,
|
|
79
|
+
greedyFillMs: 0,
|
|
80
|
+
recoveryPackingMs: 0,
|
|
81
|
+
debugBuildMs: 0,
|
|
82
|
+
rawCandidateCount: 0,
|
|
83
|
+
comparisonCandidateCount: 0,
|
|
84
|
+
side0AffiliatedCount: 0,
|
|
85
|
+
side1AffiliatedCount: 0,
|
|
86
|
+
normalizeTermsCalls: 0,
|
|
87
|
+
normalizeContentTermsCalls: 0,
|
|
88
|
+
estimateTokensCalls: 0,
|
|
89
|
+
sortCalls: 0,
|
|
90
|
+
totalSortedLength: 0,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function mergeComparisonProfileSummaries(
|
|
95
|
+
profiles: ComparisonProfileSummary[],
|
|
96
|
+
): ComparisonProfileSummary | null {
|
|
97
|
+
if (profiles.length === 0) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const merged = createComparisonProfileSummary(
|
|
102
|
+
profiles.every((profile) => profile.ablationMode === profiles[0]!.ablationMode)
|
|
103
|
+
? (profiles[0]!.ablationMode ?? null)
|
|
104
|
+
: null,
|
|
105
|
+
);
|
|
106
|
+
for (const profile of profiles) {
|
|
107
|
+
merged.rankTotalMs += profile.rankTotalMs;
|
|
108
|
+
merged.decorateMs += profile.decorateMs;
|
|
109
|
+
merged.slotCoverageMs += profile.slotCoverageMs;
|
|
110
|
+
merged.sideAffiliationMs += profile.sideAffiliationMs;
|
|
111
|
+
merged.specificityMs += profile.specificityMs;
|
|
112
|
+
merged.pairSelectionMs += profile.pairSelectionMs;
|
|
113
|
+
merged.greedyFillMs += profile.greedyFillMs;
|
|
114
|
+
merged.recoveryPackingMs += profile.recoveryPackingMs;
|
|
115
|
+
merged.debugBuildMs += profile.debugBuildMs;
|
|
116
|
+
merged.rawCandidateCount += profile.rawCandidateCount;
|
|
117
|
+
merged.comparisonCandidateCount += profile.comparisonCandidateCount;
|
|
118
|
+
merged.side0AffiliatedCount += profile.side0AffiliatedCount;
|
|
119
|
+
merged.side1AffiliatedCount += profile.side1AffiliatedCount;
|
|
120
|
+
merged.normalizeTermsCalls += profile.normalizeTermsCalls;
|
|
121
|
+
merged.normalizeContentTermsCalls += profile.normalizeContentTermsCalls;
|
|
122
|
+
merged.estimateTokensCalls += profile.estimateTokensCalls;
|
|
123
|
+
merged.sortCalls += profile.sortCalls;
|
|
124
|
+
merged.totalSortedLength += profile.totalSortedLength;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return merged;
|
|
128
|
+
}
|
package/src/context-engine.ts
CHANGED
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
rankSection7VariantCandidates,
|
|
14
14
|
} from "./scoring.js";
|
|
15
15
|
import { buildInjectedMemoryMessageContent, buildMemoryHeader, recentIds } from "./recall-utils.js";
|
|
16
|
+
import { detectDreamQuerySignal, resolveDreamCollection } from "./dream-routing.js";
|
|
17
|
+
import { resolveComparisonExperimentConfig } from "./comparison-experiments.js";
|
|
16
18
|
import {
|
|
17
19
|
decideTemporalSelectorGuard,
|
|
18
20
|
detectTemporalQuerySignal,
|
|
@@ -58,6 +60,8 @@ export function buildContextEngineFactory(
|
|
|
58
60
|
let authoredVariantCache: SearchResult[] | null = null;
|
|
59
61
|
const authoredVariantRecallCache = new Map<string, SearchResult[]>();
|
|
60
62
|
const afterTurnIngestedKeys = new Map<string, number>();
|
|
63
|
+
// Tracks accumulated uncompacted token count per session for auto-compaction
|
|
64
|
+
const sessionTokenAccumulators = new Map<string, number>();
|
|
61
65
|
|
|
62
66
|
// Session-scoped elevated-guidance cache keyed by sessionId + generation + durable namespace + queryText
|
|
63
67
|
const elevatedRecallCache = new Map<string, SearchResult[]>();
|
|
@@ -168,6 +172,20 @@ export function buildContextEngineFactory(
|
|
|
168
172
|
if (result.ingested) {
|
|
169
173
|
rememberAfterTurnIngest(afterTurnIngestedKeys, dedupeKey);
|
|
170
174
|
}
|
|
175
|
+
|
|
176
|
+
// Auto-trigger compaction when the session's uncompacted token count exceeds
|
|
177
|
+
// the budget. This keeps session size bounded without relying on the host
|
|
178
|
+
// to call compact() explicitly.
|
|
179
|
+
const sessionTokenBudget = cfg.compactSessionTokenBudget ?? 2000;
|
|
180
|
+
if (sessionTokenBudget > 0) {
|
|
181
|
+
const tokens = estimateTokens(normalized.content);
|
|
182
|
+
const accumulated = (sessionTokenAccumulators.get(sessionId) ?? 0) + tokens;
|
|
183
|
+
sessionTokenAccumulators.set(sessionId, accumulated);
|
|
184
|
+
if (accumulated >= sessionTokenBudget) {
|
|
185
|
+
sessionTokenAccumulators.set(sessionId, 0);
|
|
186
|
+
void this.compact({ sessionId, force: false }).catch(() => {});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
171
189
|
}
|
|
172
190
|
},
|
|
173
191
|
async assemble({ sessionId, sessionKey, userId, messages, tokenBudget, ...rest }: ContextAssembleArgs & Record<string, unknown>) {
|
|
@@ -187,11 +205,14 @@ export function buildContextEngineFactory(
|
|
|
187
205
|
systemPromptAddition: "",
|
|
188
206
|
} satisfies ContextAssembleResult;
|
|
189
207
|
}
|
|
208
|
+
const dreamQuery = detectDreamQuerySignal(queryText);
|
|
190
209
|
const temporalQuery = detectTemporalQuerySignal(queryText);
|
|
191
210
|
const temporalSelectorGuard = decideTemporalSelectorGuard(queryText, temporalQuery);
|
|
211
|
+
const comparisonExperiment = resolveComparisonExperimentConfig();
|
|
212
|
+
const emitComparisonProfile = comparisonExperiment.profilingEnabled;
|
|
192
213
|
|
|
193
214
|
const excluded = recentIds(normalizedMessages, 4);
|
|
194
|
-
const cached = recallCache.take({ userId: durableNamespace, queryText });
|
|
215
|
+
const cached = dreamQuery.active ? undefined : recallCache.take({ userId: durableNamespace, queryText });
|
|
195
216
|
|
|
196
217
|
const rpc = await getRpc();
|
|
197
218
|
|
|
@@ -252,8 +273,11 @@ export function buildContextEngineFactory(
|
|
|
252
273
|
cached,
|
|
253
274
|
excluded,
|
|
254
275
|
queryText,
|
|
276
|
+
dreamQuery,
|
|
255
277
|
temporalQuery,
|
|
256
278
|
temporalSelectorGuard,
|
|
279
|
+
comparisonExperiment,
|
|
280
|
+
emitComparisonProfile,
|
|
257
281
|
sessionId,
|
|
258
282
|
userId: durableNamespace,
|
|
259
283
|
visibleMessages: originalMessages,
|
|
@@ -289,8 +313,11 @@ export function buildContextEngineFactory(
|
|
|
289
313
|
cached,
|
|
290
314
|
excluded,
|
|
291
315
|
queryText,
|
|
316
|
+
dreamQuery,
|
|
292
317
|
temporalQuery,
|
|
293
318
|
temporalSelectorGuard,
|
|
319
|
+
comparisonExperiment,
|
|
320
|
+
emitComparisonProfile,
|
|
294
321
|
sessionId,
|
|
295
322
|
userId,
|
|
296
323
|
visibleMessages,
|
|
@@ -308,8 +335,11 @@ export function buildContextEngineFactory(
|
|
|
308
335
|
cached: ReturnType<RecallCache<SearchResult>["take"]>;
|
|
309
336
|
excluded: string[];
|
|
310
337
|
queryText: string;
|
|
338
|
+
dreamQuery: ReturnType<typeof detectDreamQuerySignal>;
|
|
311
339
|
temporalQuery: ReturnType<typeof detectTemporalQuerySignal>;
|
|
312
340
|
temporalSelectorGuard: ReturnType<typeof decideTemporalSelectorGuard>;
|
|
341
|
+
comparisonExperiment: ReturnType<typeof resolveComparisonExperimentConfig>;
|
|
342
|
+
emitComparisonProfile: boolean;
|
|
313
343
|
sessionId: string;
|
|
314
344
|
userId: string;
|
|
315
345
|
visibleMessages: MemoryMessage[];
|
|
@@ -321,6 +351,52 @@ export function buildContextEngineFactory(
|
|
|
321
351
|
const memoryBudget = tokenBudget * (cfg.tokenBudgetFraction ?? 0.25);
|
|
322
352
|
const hardItems = authoredHard;
|
|
323
353
|
const hardUsed = tokenCostSum(hardItems);
|
|
354
|
+
const dreamMode = dreamQuery.active;
|
|
355
|
+
const dreamCollection = resolveDreamCollection(userId);
|
|
356
|
+
|
|
357
|
+
if (dreamMode) {
|
|
358
|
+
const authoredSoftTarget = Math.max(0, memoryBudget * (cfg.authoredSoftBudgetFraction ?? 0.3));
|
|
359
|
+
const softBudget = Math.max(0, Math.min(authoredSoftTarget, memoryBudget - hardUsed));
|
|
360
|
+
const softItems = fitPromptBudget(authoredSoft, softBudget);
|
|
361
|
+
const remainingBudget = Math.max(0, memoryBudget - hardUsed - tokenCostSum(softItems));
|
|
362
|
+
|
|
363
|
+
profiler?.mark("dream_search");
|
|
364
|
+
const dreamTopK = Math.max(cfg.topK ?? 8, 1);
|
|
365
|
+
const dreamHits = await rpc.call<{ results: SearchResult[] }>("search_text", {
|
|
366
|
+
collection: dreamCollection,
|
|
367
|
+
text: queryText,
|
|
368
|
+
k: dreamTopK,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
profiler?.mark("dream_rank");
|
|
372
|
+
const rankedDream = rankSection7VariantCandidates(
|
|
373
|
+
annotateCollection(dreamHits.results ?? [], dreamCollection),
|
|
374
|
+
{
|
|
375
|
+
queryText,
|
|
376
|
+
k1: dreamTopK,
|
|
377
|
+
k2: dreamTopK,
|
|
378
|
+
theta1: cfg.section7Theta1,
|
|
379
|
+
kappa: cfg.section7Kappa,
|
|
380
|
+
authorityRecencyLambda: cfg.section7AuthorityRecencyLambda,
|
|
381
|
+
authorityRecencyWeight: cfg.section7AuthorityRecencyWeight,
|
|
382
|
+
authorityFrequencyWeight: cfg.section7AuthorityFrequencyWeight,
|
|
383
|
+
authorityAuthoredWeight: cfg.section7AuthorityAuthoredWeight,
|
|
384
|
+
sessionId,
|
|
385
|
+
userId,
|
|
386
|
+
},
|
|
387
|
+
);
|
|
388
|
+
const dreamItems = fitPromptBudget(rankedDream, remainingBudget);
|
|
389
|
+
const selected = [...hardItems, ...softItems, ...dreamItems];
|
|
390
|
+
const selectedMessages = selected.map((item) => ({
|
|
391
|
+
role: "system",
|
|
392
|
+
content: buildInjectedMemoryMessageContent(item),
|
|
393
|
+
}));
|
|
394
|
+
return {
|
|
395
|
+
messages: [...selectedMessages, ...visibleMessages],
|
|
396
|
+
estimatedTokens: countTokens(selectedMessages) + countTokens(visibleMessages),
|
|
397
|
+
systemPromptAddition: buildMemoryHeader(selected),
|
|
398
|
+
};
|
|
399
|
+
}
|
|
324
400
|
|
|
325
401
|
profiler?.mark("session");
|
|
326
402
|
const sessionRecords = await rpc.call<{ results: SearchResult[] }>("list_by_meta", {
|
|
@@ -397,7 +473,10 @@ export function buildContextEngineFactory(
|
|
|
397
473
|
const searchSessionSummary = useSessionSummarySearchExperiment(cfg);
|
|
398
474
|
let sessionSearchCollection = `session:${sessionId}`;
|
|
399
475
|
let sessionExcludeIds = [...excluded, ...recentTailIDs];
|
|
400
|
-
if (
|
|
476
|
+
if (dreamMode) {
|
|
477
|
+
sessionSearchCollection = dreamCollection;
|
|
478
|
+
sessionExcludeIds = [...excluded];
|
|
479
|
+
} else if (searchSessionSummary) {
|
|
401
480
|
const summaryCollection = sessionSummaryCollection(sessionId);
|
|
402
481
|
const summaryRecords = await rpc.call<{ results: SearchResult[] }>("list_collection", {
|
|
403
482
|
collection: summaryCollection,
|
|
@@ -423,14 +502,18 @@ export function buildContextEngineFactory(
|
|
|
423
502
|
|
|
424
503
|
profiler?.mark("recall_user_global");
|
|
425
504
|
const [userHits, globalHits] = await Promise.all([
|
|
426
|
-
|
|
505
|
+
dreamMode
|
|
506
|
+
? Promise.resolve({ results: [] as SearchResult[] })
|
|
507
|
+
: cached?.userHits
|
|
427
508
|
? Promise.resolve({ results: cached.userHits })
|
|
428
509
|
: rpc.call<{ results: SearchResult[] }>("search_text", {
|
|
429
510
|
collection: `user:${userId}`,
|
|
430
511
|
text: queryText,
|
|
431
512
|
k: Math.ceil((cfg.topK ?? 8) / 2),
|
|
432
513
|
}),
|
|
433
|
-
|
|
514
|
+
dreamMode
|
|
515
|
+
? Promise.resolve({ results: [] as SearchResult[] })
|
|
516
|
+
: cached?.globalHits
|
|
434
517
|
? Promise.resolve({ results: cached.globalHits })
|
|
435
518
|
: rpc.call<{ results: SearchResult[] }>("search_text", {
|
|
436
519
|
collection: "global",
|
|
@@ -439,7 +522,7 @@ export function buildContextEngineFactory(
|
|
|
439
522
|
}),
|
|
440
523
|
]);
|
|
441
524
|
|
|
442
|
-
if (!cached) {
|
|
525
|
+
if (!cached && !dreamMode) {
|
|
443
526
|
recallCache.put({
|
|
444
527
|
userId,
|
|
445
528
|
queryText,
|
|
@@ -453,7 +536,9 @@ export function buildContextEngineFactory(
|
|
|
453
536
|
const authoredVariantKey = `${queryText}\n${coarseTopK}`;
|
|
454
537
|
const cachedAuthoredVariantHits = authoredVariantRecallCache.get(authoredVariantKey);
|
|
455
538
|
const [authoredVariantHits] = await Promise.all([
|
|
456
|
-
|
|
539
|
+
dreamMode
|
|
540
|
+
? Promise.resolve({ results: [] as SearchResult[] })
|
|
541
|
+
: cachedAuthoredVariantHits
|
|
457
542
|
? Promise.resolve({ results: cachedAuthoredVariantHits })
|
|
458
543
|
: rpc.call<{ results: SearchResult[] }>("search_text", {
|
|
459
544
|
collection: AUTHORED_VARIANT_COLLECTION,
|
|
@@ -470,7 +555,9 @@ export function buildContextEngineFactory(
|
|
|
470
555
|
const elevatedKey = `${sessionId}\n${elevatedGeneration}\n${userId}\n${queryText}`;
|
|
471
556
|
const cachedElevated = elevatedRecallCache.get(elevatedKey);
|
|
472
557
|
const [elevatedHits] = await Promise.all([
|
|
473
|
-
|
|
558
|
+
dreamMode
|
|
559
|
+
? Promise.resolve({ results: [] as SearchResult[] })
|
|
560
|
+
: cachedElevated
|
|
474
561
|
? Promise.resolve({ results: cachedElevated })
|
|
475
562
|
: rpc.call<{ results: SearchResult[] }>("search_text_collections", {
|
|
476
563
|
collections: [
|
|
@@ -489,7 +576,7 @@ export function buildContextEngineFactory(
|
|
|
489
576
|
profiler?.mark("rank");
|
|
490
577
|
const ranked = rankSection7VariantCandidates(
|
|
491
578
|
[
|
|
492
|
-
...annotateCollection(sessionHits.results,
|
|
579
|
+
...annotateCollection(sessionHits.results, sessionSearchCollection),
|
|
493
580
|
...elevatedHits.results,
|
|
494
581
|
...userHits.results,
|
|
495
582
|
...globalHits.results,
|
|
@@ -525,17 +612,31 @@ export function buildContextEngineFactory(
|
|
|
525
612
|
// Recovery trigger is evaluated before variant fitting so healthy sessions
|
|
526
613
|
// do not lose recall budget to an unused recovery reserve.
|
|
527
614
|
profiler?.mark("recovery_trigger");
|
|
528
|
-
const recoveryTrigger =
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
615
|
+
const recoveryTrigger = dreamMode
|
|
616
|
+
? {
|
|
617
|
+
signal1CascadeTier3: false,
|
|
618
|
+
signal2TopScoreBelowFloor: false,
|
|
619
|
+
signal3AllSummariesLowConfidence: false,
|
|
620
|
+
fire: false,
|
|
621
|
+
}
|
|
622
|
+
: detectRetrievalFailure(mergedCandidates, {
|
|
623
|
+
floorScore: cfg.recoveryFloorScore ?? 0.15,
|
|
624
|
+
minTopK: cfg.recoveryMinTopK ?? 4,
|
|
625
|
+
meanConfidenceThresh: cfg.recoveryMinConfidenceMean ?? 0.5,
|
|
626
|
+
});
|
|
627
|
+
const crossSessionRawRecovery = !dreamMode &&
|
|
534
628
|
rawSessionTurns.length === 0 &&
|
|
535
629
|
sessionHits.results.length === 0;
|
|
536
|
-
const
|
|
630
|
+
const baseRecoveryReserveTokens = (recoveryTrigger.fire || crossSessionRawRecovery)
|
|
537
631
|
? Math.min(memoryBudget, Math.max(Math.floor(memoryBudget * 0.10), 16), 128)
|
|
538
632
|
: 0;
|
|
633
|
+
const isComparisonTemporalRecovery = temporalSelectorGuard.shouldApply &&
|
|
634
|
+
temporalQuery.matchedPatterns.includes("first or earlier");
|
|
635
|
+
const recoveryReserveTokens = isComparisonTemporalRecovery &&
|
|
636
|
+
!comparisonExperiment.disableReserveBump &&
|
|
637
|
+
baseRecoveryReserveTokens > 0
|
|
638
|
+
? Math.min(memoryBudget, Math.max(baseRecoveryReserveTokens, Math.ceil(baseRecoveryReserveTokens * 1.8)))
|
|
639
|
+
: baseRecoveryReserveTokens;
|
|
539
640
|
const elevatedGuidanceBudget = Math.max(
|
|
540
641
|
0,
|
|
541
642
|
Math.min(
|
|
@@ -570,8 +671,10 @@ export function buildContextEngineFactory(
|
|
|
570
671
|
// it never modifies the C_total(q) output and does not spend from tau_V.
|
|
571
672
|
let recoveryItems: SearchResult[] = [];
|
|
572
673
|
let rawUserRecoveryDebug: NonNullable<NonNullable<ContextAssembleResult["_debug"]>["rawUserRecoveryCandidates"]> = [];
|
|
674
|
+
let dedupedRecoveryDebug: NonNullable<NonNullable<ContextAssembleResult["_debug"]>["recoveryDedupedOrder"]> = [];
|
|
675
|
+
let fittedRecoveryDebug: NonNullable<NonNullable<ContextAssembleResult["_debug"]>["recoveryFittedOrder"]> = [];
|
|
573
676
|
let temporalRecoveryResult: TemporalRecoveryRankingResult | null = null;
|
|
574
|
-
if (recoveryTrigger.fire || crossSessionRawRecovery) {
|
|
677
|
+
if (!dreamMode && (recoveryTrigger.fire || crossSessionRawRecovery)) {
|
|
575
678
|
profiler?.mark("recovery_expand");
|
|
576
679
|
const recoveryExcludeIDs = [...excluded, ...recentTailIDs, ...theoremSelectedIDs];
|
|
577
680
|
const recoveryCandidates: SearchResult[] = [];
|
|
@@ -615,6 +718,7 @@ export function buildContextEngineFactory(
|
|
|
615
718
|
maxSelected: 3,
|
|
616
719
|
nowMs: Date.now(),
|
|
617
720
|
recencyLambda: cfg.recencyLambdaUser ?? 0.00001,
|
|
721
|
+
selectionTokenBudget: recoveryReserveTokens,
|
|
618
722
|
})
|
|
619
723
|
: null;
|
|
620
724
|
const reranked = temporalRecoveryResult
|
|
@@ -646,26 +750,91 @@ export function buildContextEngineFactory(
|
|
|
646
750
|
: 0,
|
|
647
751
|
finalScore: typeof item.finalScore === "number" ? item.finalScore : 0,
|
|
648
752
|
rationale: typeof item.rationale === "string" ? item.rationale : "",
|
|
753
|
+
comparisonSide: "comparisonSide" in item && (item.comparisonSide === 0 || item.comparisonSide === 1 || item.comparisonSide === null)
|
|
754
|
+
? item.comparisonSide
|
|
755
|
+
: undefined,
|
|
756
|
+
comparisonSlot: "comparisonSlot" in item && typeof item.comparisonSlot === "string"
|
|
757
|
+
? item.comparisonSlot
|
|
758
|
+
: undefined,
|
|
759
|
+
comparisonSlotRecall: "comparisonSlotRecall" in item && typeof item.comparisonSlotRecall === "number"
|
|
760
|
+
? item.comparisonSlotRecall
|
|
761
|
+
: undefined,
|
|
762
|
+
comparisonSlotPrecision: "comparisonSlotPrecision" in item && typeof item.comparisonSlotPrecision === "number"
|
|
763
|
+
? item.comparisonSlotPrecision
|
|
764
|
+
: undefined,
|
|
765
|
+
comparisonSlotSpecificity: "comparisonSlotSpecificity" in item && typeof item.comparisonSlotSpecificity === "number"
|
|
766
|
+
? item.comparisonSlotSpecificity
|
|
767
|
+
: undefined,
|
|
768
|
+
comparisonSlotPositionWeightedRecall: "comparisonSlotPositionWeightedRecall" in item && typeof item.comparisonSlotPositionWeightedRecall === "number"
|
|
769
|
+
? item.comparisonSlotPositionWeightedRecall
|
|
770
|
+
: undefined,
|
|
771
|
+
comparisonSlotPositionWeightedPrecision: "comparisonSlotPositionWeightedPrecision" in item && typeof item.comparisonSlotPositionWeightedPrecision === "number"
|
|
772
|
+
? item.comparisonSlotPositionWeightedPrecision
|
|
773
|
+
: undefined,
|
|
774
|
+
comparisonSlotPositionWeightedSpecificity: "comparisonSlotPositionWeightedSpecificity" in item && typeof item.comparisonSlotPositionWeightedSpecificity === "number"
|
|
775
|
+
? item.comparisonSlotPositionWeightedSpecificity
|
|
776
|
+
: undefined,
|
|
777
|
+
comparisonFirstPersonClauseCount: "comparisonFirstPersonClauseCount" in item && typeof item.comparisonFirstPersonClauseCount === "number"
|
|
778
|
+
? item.comparisonFirstPersonClauseCount
|
|
779
|
+
: undefined,
|
|
780
|
+
comparisonProspectivePersonalVerbCount: "comparisonProspectivePersonalVerbCount" in item && typeof item.comparisonProspectivePersonalVerbCount === "number"
|
|
781
|
+
? item.comparisonProspectivePersonalVerbCount
|
|
782
|
+
: undefined,
|
|
783
|
+
comparisonPlanningDensity: "comparisonPlanningDensity" in item && typeof item.comparisonPlanningDensity === "number"
|
|
784
|
+
? item.comparisonPlanningDensity
|
|
785
|
+
: undefined,
|
|
786
|
+
comparisonPastness: "comparisonPastness" in item && typeof item.comparisonPastness === "number"
|
|
787
|
+
? item.comparisonPastness
|
|
788
|
+
: undefined,
|
|
789
|
+
comparisonSideWitnessScore: "comparisonSideWitnessScore" in item && typeof item.comparisonSideWitnessScore === "number"
|
|
790
|
+
? item.comparisonSideWitnessScore
|
|
791
|
+
: undefined,
|
|
649
792
|
}));
|
|
650
793
|
}
|
|
651
794
|
recoveryCandidates.push(
|
|
652
|
-
...reranked.ranked.map((item) =>
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
795
|
+
...reranked.ranked.map((item) => {
|
|
796
|
+
return {
|
|
797
|
+
...item,
|
|
798
|
+
finalScore: typeof item.finalScore === "number" ? item.finalScore : item.score,
|
|
799
|
+
metadata: {
|
|
800
|
+
...item.metadata,
|
|
801
|
+
recovery_fallback: true,
|
|
802
|
+
recovery_scope: "user_turns",
|
|
803
|
+
},
|
|
804
|
+
};
|
|
805
|
+
}),
|
|
661
806
|
);
|
|
662
807
|
}
|
|
663
808
|
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
809
|
+
const dedupedRecovery = dedupeRecoveryCandidates(recoveryCandidates);
|
|
810
|
+
const packingStart = temporalRecoveryResult?.comparisonProfile ? process.hrtime.bigint() : 0n;
|
|
811
|
+
const fittedRecovery = comparisonExperiment.disableProtectedPairPack
|
|
812
|
+
? fitPromptBudgetFirstFit(dedupedRecovery, recoveryReserveTokens)
|
|
813
|
+
: fitProtectedComparisonRecovery(
|
|
814
|
+
dedupedRecovery,
|
|
815
|
+
recoveryReserveTokens,
|
|
816
|
+
temporalRecoveryResult?.comparisonCoverageApplied === true
|
|
817
|
+
? temporalRecoveryResult.comparisonWitnessIds
|
|
818
|
+
: undefined,
|
|
819
|
+
);
|
|
820
|
+
if (temporalRecoveryResult?.comparisonProfile) {
|
|
821
|
+
temporalRecoveryResult.comparisonProfile.recoveryPackingMs += Number(process.hrtime.bigint() - packingStart) / 1_000_000;
|
|
822
|
+
}
|
|
668
823
|
recoveryItems = fittedRecovery;
|
|
824
|
+
if (debugRecovery) {
|
|
825
|
+
dedupedRecoveryDebug = dedupedRecovery.map((item) => ({
|
|
826
|
+
id: item.id,
|
|
827
|
+
recoveryScope: typeof item.metadata.recovery_scope === "string" ? item.metadata.recovery_scope : "unknown",
|
|
828
|
+
finalScore: typeof item.finalScore === "number" ? item.finalScore : item.score,
|
|
829
|
+
tokenEstimate: estimateTokens(item.text),
|
|
830
|
+
}));
|
|
831
|
+
fittedRecoveryDebug = fittedRecovery.map((item) => ({
|
|
832
|
+
id: item.id,
|
|
833
|
+
recoveryScope: typeof item.metadata.recovery_scope === "string" ? item.metadata.recovery_scope : "unknown",
|
|
834
|
+
finalScore: typeof item.finalScore === "number" ? item.finalScore : item.score,
|
|
835
|
+
tokenEstimate: estimateTokens(item.text),
|
|
836
|
+
}));
|
|
837
|
+
}
|
|
669
838
|
if (debugRecovery && rawUserRecoveryDebug.length > 0) {
|
|
670
839
|
const selectedIDs = new Set(
|
|
671
840
|
fittedRecovery
|
|
@@ -698,11 +867,11 @@ export function buildContextEngineFactory(
|
|
|
698
867
|
content: buildInjectedMemoryMessageContent(item),
|
|
699
868
|
}));
|
|
700
869
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
870
|
+
return {
|
|
871
|
+
messages: [...selectedMessages, ...visibleMessages],
|
|
872
|
+
estimatedTokens: countTokens(selectedMessages) + countTokens(visibleMessages),
|
|
873
|
+
systemPromptAddition: buildMemoryHeader(selected),
|
|
874
|
+
_debug: (debugRecovery || emitComparisonProfile)
|
|
706
875
|
? {
|
|
707
876
|
recoveryTriggerFired: recoveryTrigger.fire,
|
|
708
877
|
crossSessionRawRecovery,
|
|
@@ -713,10 +882,17 @@ export function buildContextEngineFactory(
|
|
|
713
882
|
temporalSelectorApplied: temporalSelectorGuard.shouldApply,
|
|
714
883
|
temporalSelectorReason: temporalSelectorGuard.reason,
|
|
715
884
|
temporalRecoverySlots: temporalRecoveryResult?.slots,
|
|
885
|
+
temporalComparisonCoverageApplied: temporalRecoveryResult?.comparisonCoverageApplied,
|
|
886
|
+
temporalComparisonCoverageSlots: temporalRecoveryResult?.comparisonCoverageSlots,
|
|
887
|
+
temporalComparisonCoverageMinTokens: temporalRecoveryResult?.comparisonCoverageMinTokens,
|
|
888
|
+
temporalComparisonWitnessIds: temporalRecoveryResult?.comparisonWitnessIds,
|
|
889
|
+
comparisonProfile: temporalRecoveryResult?.comparisonProfile,
|
|
890
|
+
recoveryDedupedOrder: dedupedRecoveryDebug,
|
|
891
|
+
recoveryFittedOrder: fittedRecoveryDebug,
|
|
716
892
|
rawUserRecoveryCandidates: rawUserRecoveryDebug,
|
|
717
893
|
}
|
|
718
894
|
: undefined,
|
|
719
|
-
|
|
895
|
+
};
|
|
720
896
|
},
|
|
721
897
|
async compact({ sessionId, force, targetSize }: ContextCompactArgs) {
|
|
722
898
|
const rpc = await getRpc();
|
|
@@ -967,6 +1143,42 @@ function dedupeRecoveryCandidates(items: SearchResult[]): SearchResult[] {
|
|
|
967
1143
|
return [...byKey.values()].sort((left, right) => (right.finalScore ?? right.score) - (left.finalScore ?? left.score));
|
|
968
1144
|
}
|
|
969
1145
|
|
|
1146
|
+
function fitProtectedComparisonRecovery(
|
|
1147
|
+
items: SearchResult[],
|
|
1148
|
+
tokenBudget: number,
|
|
1149
|
+
protectedWitnessIds?: string[],
|
|
1150
|
+
): SearchResult[] {
|
|
1151
|
+
if (!protectedWitnessIds || protectedWitnessIds.length === 0) {
|
|
1152
|
+
return fitPromptBudgetFirstFit(items, tokenBudget);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
const protectedById = new Map<string, SearchResult>();
|
|
1156
|
+
const remaining: SearchResult[] = [];
|
|
1157
|
+
|
|
1158
|
+
for (const item of items) {
|
|
1159
|
+
if (protectedWitnessIds.includes(item.id) && !protectedById.has(item.id)) {
|
|
1160
|
+
protectedById.set(item.id, item);
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
remaining.push(item);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
const protectedItems = protectedWitnessIds
|
|
1167
|
+
.map((id) => protectedById.get(id))
|
|
1168
|
+
.filter((item): item is SearchResult => Boolean(item));
|
|
1169
|
+
if (protectedItems.length !== protectedWitnessIds.length) {
|
|
1170
|
+
return fitPromptBudgetFirstFit(items, tokenBudget);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
const protectedTokens = protectedItems.reduce((sum, item) => sum + estimateTokens(item.text), 0);
|
|
1174
|
+
if (protectedTokens > tokenBudget) {
|
|
1175
|
+
return fitPromptBudgetFirstFit(items, tokenBudget);
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
const tail = fitPromptBudgetFirstFit(remaining, tokenBudget - protectedTokens);
|
|
1179
|
+
return [...protectedItems, ...tail];
|
|
1180
|
+
}
|
|
1181
|
+
|
|
970
1182
|
function clampFraction(value: number | undefined): number {
|
|
971
1183
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
972
1184
|
return 0;
|
|
@@ -1046,33 +1258,35 @@ async function ingestCanonicalMessage(params: {
|
|
|
1046
1258
|
text: normalized.content,
|
|
1047
1259
|
});
|
|
1048
1260
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1261
|
+
// Gating is designed for markdown file deduplication — not conversational content.
|
|
1262
|
+
// User turns must be stored durably regardless of gating score so the agent can recall
|
|
1263
|
+
// friends, preferences, and context across sessions. Gating signals are still
|
|
1264
|
+
// recorded in metadata for observability, but do not gate the insert.
|
|
1265
|
+
void rpc.call("insert_text", {
|
|
1266
|
+
collection: `user:${durableNamespace}`,
|
|
1267
|
+
id: `${durableNamespace}:${turnId}`,
|
|
1268
|
+
text: normalized.content,
|
|
1269
|
+
metadata: {
|
|
1270
|
+
role: normalized.role,
|
|
1271
|
+
ts,
|
|
1272
|
+
sessionId: params.sessionId,
|
|
1273
|
+
type: "turn",
|
|
1274
|
+
userId: durableNamespace,
|
|
1275
|
+
source_turn_id: turnId,
|
|
1276
|
+
provenance_class: "durable_user_memory",
|
|
1277
|
+
stability_weight: Math.max(stabilityWeightForMessage(normalized.role), gating.g),
|
|
1278
|
+
gating_score: gating.g,
|
|
1279
|
+
gating_t: gating.t,
|
|
1280
|
+
gating_h: gating.h,
|
|
1281
|
+
gating_r: gating.r,
|
|
1282
|
+
gating_d: gating.d,
|
|
1283
|
+
gating_p: gating.p,
|
|
1284
|
+
gating_a: gating.a,
|
|
1285
|
+
gating_dtech: gating.dtech,
|
|
1286
|
+
gating_gconv: gating.gconv,
|
|
1287
|
+
gating_gtech: gating.gtech,
|
|
1288
|
+
},
|
|
1289
|
+
}).catch(console.error);
|
|
1076
1290
|
} catch {
|
|
1077
1291
|
// Session storage already happened; skip durable promotion on gating failure.
|
|
1078
1292
|
}
|