Skip to content

Commit 9a6bf0f

Browse files
raw34claude
andcommitted
feat(retrieval): add fresh memory boost for recently stored memories
Memories stored within 30 minutes (configurable) get an additional score boost (+0.15), with an extra +0.05 for reflection/preference categories. This ensures corrections from ongoing conversations outrank stale high-access memories in auto-recall ranking. Configurable via retrieval.freshMemoryBoostMinutes (default: 30, set 0 to disable) and retrieval.freshMemoryBoostWeight (default: 0.15). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 619d703 commit 9a6bf0f

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed

src/retriever.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ export interface RetrievalConfig {
4040
recencyHalfLifeDays: number;
4141
/** Max recency boost factor (default: 0.10) */
4242
recencyWeight: number;
43+
/** Minutes window for fresh memory boost. Memories stored within this window
44+
* get an additional score bonus (default: 30). Set to 0 to disable. */
45+
freshMemoryBoostMinutes?: number;
46+
/** Base score boost for fresh memories (default: 0.15) */
47+
freshMemoryBoostWeight?: number;
4348
/** Filter noise from results (default: true) */
4449
filterNoise: boolean;
4550
/** Reranker API key (enables cross-encoder reranking) */
@@ -1364,6 +1369,25 @@ export class MemoryRetriever {
13641369
};
13651370
});
13661371

1372+
// 新鲜记忆加分:窗口内存储的记忆获得额外分数,
1373+
// 确保纠正信息能排在高频旧记忆之前。
1374+
const freshWindowMinutes = this.config.freshMemoryBoostMinutes ?? 30;
1375+
const freshWeight = this.config.freshMemoryBoostWeight ?? 0.15;
1376+
if (freshWindowMinutes > 0) {
1377+
const freshWindowMs = freshWindowMinutes * 60_000;
1378+
for (const r of boosted) {
1379+
const ts = r.entry.timestamp && r.entry.timestamp > 0 ? r.entry.timestamp : now;
1380+
const ageMs = now - ts;
1381+
if (ageMs < freshWindowMs) {
1382+
let bonus = freshWeight;
1383+
if (r.entry.category === "reflection" || r.entry.category === "preference") {
1384+
bonus += 0.05;
1385+
}
1386+
r.score = clamp01(r.score + bonus, r.score);
1387+
}
1388+
}
1389+
}
1390+
13671391
return boosted.sort((a, b) => b.score - a.score);
13681392
}
13691393

test/fresh-memory-boost.test.mjs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { describe, it } from "node:test";
2+
import assert from "node:assert/strict";
3+
4+
/**
5+
* 新鲜记忆 recency boost 测试。
6+
*
7+
* 验证在 freshMemoryBoostMinutes 窗口内存储的记忆会获得额外加分,
8+
* 确保纠正信息和最新上下文能排在高访问量旧记忆之前。
9+
*/
10+
11+
// 模拟检索结果工厂
12+
function makeResult(id, score, timestampMinutesAgo, category = "fact") {
13+
const now = Date.now();
14+
return {
15+
entry: {
16+
id,
17+
text: `memory ${id}`,
18+
category,
19+
scope: "agent:test",
20+
importance: 0.8,
21+
timestamp: now - timestampMinutesAgo * 60_000,
22+
vector: [0.1, 0.2, 0.3],
23+
metadata: "{}",
24+
},
25+
score,
26+
sources: {},
27+
};
28+
}
29+
30+
describe("fresh memory boost", () => {
31+
it("应对窗口内的记忆加分", () => {
32+
const oldMemory = makeResult("old-1", 0.65, 120);
33+
const freshMemory = makeResult("fresh-1", 0.55, 5);
34+
35+
const freshBoost = 0.15;
36+
const windowMinutes = 30;
37+
38+
const now = Date.now();
39+
const results = [oldMemory, freshMemory].map((r) => {
40+
const ageMinutes = (now - r.entry.timestamp) / 60_000;
41+
if (ageMinutes < windowMinutes) {
42+
return { ...r, score: Math.min(1, r.score + freshBoost) };
43+
}
44+
return r;
45+
});
46+
47+
results.sort((a, b) => b.score - a.score);
48+
49+
assert.equal(results[0].entry.id, "fresh-1");
50+
assert.ok(results[0].score > results[1].score);
51+
});
52+
53+
it("应对 reflection/preference 类别额外加分", () => {
54+
const freshFact = makeResult("fact-1", 0.50, 10, "fact");
55+
const freshReflection = makeResult("refl-1", 0.50, 10, "reflection");
56+
57+
const freshBoost = 0.15;
58+
const categoryBonus = 0.05;
59+
const windowMinutes = 30;
60+
61+
const now = Date.now();
62+
const results = [freshFact, freshReflection].map((r) => {
63+
const ageMinutes = (now - r.entry.timestamp) / 60_000;
64+
if (ageMinutes < windowMinutes) {
65+
let boost = freshBoost;
66+
if (r.entry.category === "reflection" || r.entry.category === "preference") {
67+
boost += categoryBonus;
68+
}
69+
return { ...r, score: Math.min(1, r.score + boost) };
70+
}
71+
return r;
72+
});
73+
74+
results.sort((a, b) => b.score - a.score);
75+
76+
assert.equal(results[0].entry.id, "refl-1");
77+
assert.ok(results[0].score - results[1].score >= 0.04);
78+
});
79+
80+
it("不应对窗口外的记忆加分", () => {
81+
const oldMemory = makeResult("old-1", 0.60, 60);
82+
83+
const windowMinutes = 30;
84+
const now = Date.now();
85+
const ageMinutes = (now - oldMemory.entry.timestamp) / 60_000;
86+
87+
assert.ok(ageMinutes > windowMinutes);
88+
assert.equal(oldMemory.score, 0.60);
89+
});
90+
91+
it("freshMemoryBoostMinutes 为 0 时应禁用加分", () => {
92+
const freshMemory = makeResult("fresh-1", 0.55, 5);
93+
const windowMinutes = 0;
94+
95+
const boosted = windowMinutes > 0;
96+
assert.equal(boosted, false);
97+
assert.equal(freshMemory.score, 0.55);
98+
});
99+
});

0 commit comments

Comments
 (0)