mirror of
https://github.com/sdwolf4103/opencode-working-memory.git
synced 2026-06-02 06:19:36 +02:00
fix(retention): add UTC calendar-day diversity gate to reinforceMemory
Implement OQ-2 decision: allow at most one reinforcement per memory identity per UTC calendar day. Same-day reinforcement is blocked regardless of session or interval. This prevents repetitive-task gaming where a daily recurring task could reach MAX_COUNT=6 in hours. Guard order: same-session → calendar-day → 1-hour → max-count (existing guards kept as defense-in-depth) 1 hour guard is redundant within same day but preserved for sub-hour edge cases.
This commit is contained in:
@@ -108,6 +108,14 @@ export function calculateEffectiveAgeDays(
|
||||
return activeDays + dormantOverlapDays * DORMANT_DECAY_MULTIPLIER;
|
||||
}
|
||||
|
||||
function isSameUTCCalendarDay(ts1: number, ts2: number): boolean {
|
||||
const d1 = new Date(ts1);
|
||||
const d2 = new Date(ts2);
|
||||
return d1.getUTCFullYear() === d2.getUTCFullYear()
|
||||
&& d1.getUTCMonth() === d2.getUTCMonth()
|
||||
&& d1.getUTCDate() === d2.getUTCDate();
|
||||
}
|
||||
|
||||
export function reinforceMemory(
|
||||
memory: LongTermMemoryEntry,
|
||||
sessionId: string,
|
||||
@@ -117,6 +125,11 @@ export function reinforceMemory(
|
||||
return memory;
|
||||
}
|
||||
|
||||
// Calendar-day diversity gate (OQ-2): same UTC day = no reinforcement.
|
||||
if (memory.lastReinforcedAt && isSameUTCCalendarDay(memory.lastReinforcedAt, now)) {
|
||||
return memory;
|
||||
}
|
||||
|
||||
if (memory.lastReinforcedAt && now - memory.lastReinforcedAt < REINFORCEMENT_MIN_INTERVAL_MS) {
|
||||
return memory;
|
||||
}
|
||||
|
||||
@@ -505,6 +505,33 @@ test("reinforceMemory enforces session interval and max guards", () => {
|
||||
assert.equal(reinforceMemory(atMax, "session-c", now), atMax);
|
||||
});
|
||||
|
||||
test("reinforceMemory requires distinct UTC calendar days between reinforcements", () => {
|
||||
const firstReinforcedAt = Date.UTC(2026, 3, 29, 0, 15);
|
||||
const sameUtcDayMuchLater = Date.UTC(2026, 3, 29, 23, 30);
|
||||
const nextUtcDayAfterInterval = Date.UTC(2026, 3, 30, 1, 30);
|
||||
const base: LongTermMemoryEntry = {
|
||||
...entry("calendar-day-gated", "Reinforcement requires distinct UTC calendar days", "decision"),
|
||||
reinforcementCount: 1,
|
||||
lastReinforcedAt: firstReinforcedAt,
|
||||
lastReinforcedSessionID: "session-a",
|
||||
};
|
||||
|
||||
assert.equal(reinforceMemory(base, "session-b", sameUtcDayMuchLater), base);
|
||||
|
||||
const reinforcedNextDay = reinforceMemory(base, "session-b", nextUtcDayAfterInterval);
|
||||
assert.notEqual(reinforcedNextDay, base);
|
||||
assert.equal(reinforcedNextDay.reinforcementCount, 2);
|
||||
assert.equal(reinforcedNextDay.lastReinforcedAt, nextUtcDayAfterInterval);
|
||||
assert.equal(reinforcedNextDay.lastReinforcedSessionID, "session-b");
|
||||
assert.equal(reinforcedNextDay.retentionClock, nextUtcDayAfterInterval);
|
||||
|
||||
const atMax: LongTermMemoryEntry = {
|
||||
...base,
|
||||
reinforcementCount: 6,
|
||||
};
|
||||
assert.equal(reinforceMemory(atMax, "session-c", nextUtcDayAfterInterval), atMax);
|
||||
});
|
||||
|
||||
test("dedupeLongTermEntriesWithAccounting reinforces absorbed exact duplicates", () => {
|
||||
const now = Date.now();
|
||||
const retained: LongTermMemoryEntry = {
|
||||
|
||||
Reference in New Issue
Block a user