@@ -252,8 +252,70 @@ export function loadAgentReflectionSlicesFromEntries(params: LoadReflectionSlice
252252 const itemRows = reflectionRows . filter ( ( { metadata } ) => metadata . type === "memory-reflection-item" ) ;
253253 const legacyRows = reflectionRows . filter ( ( { metadata } ) => metadata . type === "memory-reflection" ) ;
254254
255- const invariantCandidates = buildInvariantCandidates ( itemRows , legacyRows ) ;
256- const derivedCandidates = buildDerivedCandidates ( itemRows , legacyRows ) ;
255+ // [P1] Filter out resolved items — passive suppression for #447
256+ // resolvedAt === undefined means unresolved (default)
257+ const unresolvedItemRows = itemRows . filter ( ( { metadata } ) => metadata . resolvedAt === undefined ) ;
258+ const resolvedItemRows = itemRows . filter ( ( { metadata } ) => metadata . resolvedAt !== undefined ) ;
259+
260+ const hasItemRows = itemRows . length > 0 ;
261+ const hasLegacyRows = legacyRows . length > 0 ;
262+
263+ // Collect normalized text of resolved items so we can detect whether legacy
264+ // rows are pure duplicates of already-resolved content.
265+ const resolvedInvariantTexts = new Set (
266+ resolvedItemRows
267+ . filter ( ( { metadata } ) => metadata . itemKind === "invariant" )
268+ . flatMap ( ( { entry } ) => sanitizeInjectableReflectionLines ( [ entry . text ] ) )
269+ . map ( ( line ) => normalizeReflectionLineForAggregation ( line ) )
270+ ) ;
271+ const resolvedDerivedTexts = new Set (
272+ resolvedItemRows
273+ . filter ( ( { metadata } ) => metadata . itemKind === "derived" )
274+ . flatMap ( ( { entry } ) => sanitizeInjectableReflectionLines ( [ entry . text ] ) )
275+ . map ( ( line ) => normalizeReflectionLineForAggregation ( line ) )
276+ ) ;
277+
278+ // Check whether legacy rows add any content not already covered by resolved items.
279+ const legacyHasUniqueInvariant = legacyRows . some ( ( { metadata } ) =>
280+ toStringArray ( metadata . invariants ) . some (
281+ ( line ) => ! resolvedInvariantTexts . has ( normalizeReflectionLineForAggregation ( line ) )
282+ )
283+ ) ;
284+ const legacyHasUniqueDerived = legacyRows . some ( ( { metadata } ) =>
285+ toStringArray ( metadata . derived ) . some (
286+ ( line ) => ! resolvedDerivedTexts . has ( normalizeReflectionLineForAggregation ( line ) )
287+ )
288+ ) ;
289+
290+ // Suppress when:
291+ // 1) there were item rows, all are resolved, and there are no legacy rows, OR
292+ // 2) there were item rows, all are resolved, legacy rows exist BUT all of their
293+ // content duplicates already-resolved items (prevents legacy fallback from
294+ // reviving just-resolved advice — the P1 bug fixed here).
295+ const shouldSuppress =
296+ hasItemRows &&
297+ unresolvedItemRows . length === 0 &&
298+ ( ! hasLegacyRows || ( ! legacyHasUniqueInvariant && ! legacyHasUniqueDerived ) ) ;
299+ if ( shouldSuppress ) {
300+ return { invariants : [ ] , derived : [ ] } ;
301+ }
302+
303+ // [P2] Per-section legacy filtering: only pass legacy rows that have unique
304+ // content for this specific section. Prevents resolved items in section A from being
305+ // revived when section B has unique legacy content (cross-section legacy fallback bug).
306+ const invariantLegacyRows = legacyRows . filter ( ( { metadata } ) =>
307+ toStringArray ( metadata . invariants ) . some (
308+ ( line ) => ! resolvedInvariantTexts . has ( normalizeReflectionLineForAggregation ( line ) )
309+ )
310+ ) ;
311+ const derivedLegacyRows = legacyRows . filter ( ( { metadata } ) =>
312+ toStringArray ( metadata . derived ) . some (
313+ ( line ) => ! resolvedDerivedTexts . has ( normalizeReflectionLineForAggregation ( line ) )
314+ )
315+ ) ;
316+
317+ const invariantCandidates = buildInvariantCandidates ( unresolvedItemRows , invariantLegacyRows ) ;
318+ const derivedCandidates = buildDerivedCandidates ( unresolvedItemRows , derivedLegacyRows ) ;
257319
258320 const invariants = rankReflectionLines ( invariantCandidates , {
259321 now,
0 commit comments