Skip to content

Commit ee2b04a

Browse files
committed
fix(dreaming): filter already-emitted entries in light phase to prevent duplicate summaries (fixes #72096)
1 parent cc95479 commit ee2b04a

2 files changed

Lines changed: 41 additions & 1 deletion

File tree

extensions/memory-core/src/dreaming-phases.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { textSimilarity as snippetSimilarity } from "./memory/tokenize.js";
4040
import {
4141
filterLiveShortTermRecallEntries,
4242
readLightStagedKeys,
43+
readPhaseSignalEntries,
4344
readShortTermRecallEntries,
4445
recordDreamingPhaseSignals,
4546
recordRemConsideredPhaseSignals,
@@ -1660,8 +1661,29 @@ async function runLightDreaming(params: {
16601661
lookbackDays: params.config.lookbackDays,
16611662
}),
16621663
});
1664+
// Filter entries already emitted in a prior light-phase cycle and not
1665+
// recalled again since. This prevents the work-summary block from
1666+
// repeating verbatim across consecutive cycles when the same entries
1667+
// remain within the lookback window. See #72096.
1668+
const phaseSignals = await readPhaseSignalEntries({
1669+
workspaceDir: params.workspaceDir,
1670+
nowMs,
1671+
});
1672+
const freshEntries = recentEntries.filter((entry) => {
1673+
const signal = phaseSignals[entry.key];
1674+
if (!signal?.lastLightAt) {
1675+
return true; // never emitted → include
1676+
}
1677+
const lastLightMs = Date.parse(signal.lastLightAt);
1678+
const lastRecalledMs = Date.parse(entry.lastRecalledAt);
1679+
if (!Number.isFinite(lastLightMs) || !Number.isFinite(lastRecalledMs)) {
1680+
return true; // malformed timestamps → include conservatively
1681+
}
1682+
// Include only if the entry was recalled again after the last emission.
1683+
return lastRecalledMs > lastLightMs;
1684+
});
16631685
const entries = dedupeEntries(
1664-
recentEntries
1686+
freshEntries
16651687
.toSorted((a, b) => {
16661688
const byTime = Date.parse(b.lastRecalledAt) - Date.parse(a.lastRecalledAt);
16671689
if (byTime !== 0) {

extensions/memory-core/src/short-term-promotion.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1775,6 +1775,24 @@ export async function readLightStagedKeys(params: {
17751775
return keys;
17761776
}
17771777

1778+
/**
1779+
* Read the phase signal entries for a workspace so callers can inspect
1780+
* per-entry `lastLightAt` / `lastRemAt` timestamps for deduplication.
1781+
*/
1782+
export async function readPhaseSignalEntries(params: {
1783+
workspaceDir: string;
1784+
nowMs?: number;
1785+
}): Promise<Record<string, ShortTermPhaseSignalEntry>> {
1786+
const workspaceDir = params.workspaceDir?.trim();
1787+
if (!workspaceDir) {
1788+
return {};
1789+
}
1790+
const nowMs = resolveMemoryCoreNowMs(params.nowMs);
1791+
const nowIso = resolveMemoryCoreTimestamp(nowMs);
1792+
const store = await readPhaseSignalStore(workspaceDir, nowIso);
1793+
return store.entries;
1794+
}
1795+
17781796
export async function rankShortTermPromotionCandidates(
17791797
options: RankShortTermPromotionOptions,
17801798
): Promise<PromotionCandidate[]> {

0 commit comments

Comments
 (0)