@@ -41,6 +41,7 @@ import { textSimilarity as snippetSimilarity } from "./memory/tokenize.js";
4141import {
4242 filterLiveShortTermRecallEntries ,
4343 readLightStagedKeys ,
44+ readPhaseSignalEntries ,
4445 readShortTermRecallEntries ,
4546 recordDreamingPhaseSignals ,
4647 recordRemConsideredPhaseSignals ,
@@ -1703,8 +1704,29 @@ async function runLightDreaming(params: {
17031704 lookbackDays : params . config . lookbackDays ,
17041705 } ) ,
17051706 } ) ;
1707+ // Filter entries already emitted in a prior light-phase cycle and not
1708+ // recalled again since. This prevents the work-summary block from
1709+ // repeating verbatim across consecutive cycles when the same entries
1710+ // remain within the lookback window. See #72096.
1711+ const phaseSignals = await readPhaseSignalEntries ( {
1712+ workspaceDir : params . workspaceDir ,
1713+ nowMs,
1714+ } ) ;
1715+ const freshEntries = recentEntries . filter ( ( entry ) => {
1716+ const signal = phaseSignals [ entry . key ] ;
1717+ if ( ! signal ?. lastLightAt ) {
1718+ return true ; // never emitted → include
1719+ }
1720+ const lastLightMs = Date . parse ( signal . lastLightAt ) ;
1721+ const lastRecalledMs = Date . parse ( entry . lastRecalledAt ) ;
1722+ if ( ! Number . isFinite ( lastLightMs ) || ! Number . isFinite ( lastRecalledMs ) ) {
1723+ return true ; // malformed timestamps → include conservatively
1724+ }
1725+ // Include only if the entry was recalled again after the last emission.
1726+ return lastRecalledMs > lastLightMs ;
1727+ } ) ;
17061728 const rankedEntries = dedupeEntries (
1707- recentEntries . toSorted ( ( a , b ) => {
1729+ freshEntries . toSorted ( ( a , b ) => {
17081730 const byTime = Date . parse ( b . lastRecalledAt ) - Date . parse ( a . lastRecalledAt ) ;
17091731 if ( byTime !== 0 ) {
17101732 return byTime ;
0 commit comments