···663663 baseConversation = sanitizeMessages(baseConversation)
664664 }
665665666666- const requestContext = await buildCompletionMessages(
667667- baseConversation,
668668- state.memoryRecallCooldowns,
669669- state.memoryRecallTurn,
670670- )
666666+ // Only run memory recall on the first step of a new turn. Re-running it on
667667+ // every agentic iteration floods context with the same recalled chunks for
668668+ // the same (unchanged) user message.
669669+ const requestContext = state.memoryRecallPending
670670+ ? await buildCompletionMessages(
671671+ baseConversation,
672672+ state.memoryRecallCooldowns,
673673+ state.memoryRecallTurn,
674674+ )
675675+ : { messages: baseConversation, recalledChunkIds: [] as number[] }
671676 const requestMessages = requestContext.messages
672677673678 if (USE_FALLBACK) {
···5757async function processAssistantTurn(convId: number, state: LoopState, hooks: LoopHooks): Promise<CycleOutcome> {
5858 state.memoryRecallTurn += 1
5959 const response = await fetchCompletion(state)
6060+ // Recall (if any) has been applied for this turn; don't re-recall on the
6161+ // follow-up iterations that work through the same incoming event.
6262+ state.memoryRecallPending = false
6063 applyUsage(state, response.usage, {
6164 elapsedMs: response.elapsedMs,
6265 tokensPerSecond: response.tokensPerSecond,
···1111 toolInFlight: boolean
1212 memoryRecallCooldowns: Record<number, number>
1313 memoryRecallTurn: number
1414+ /**
1515+ * True only on the first assistant step after a new incoming event. Memory
1616+ * recall runs once per turn rather than on every agentic iteration so it
1717+ * doesn't flood context while the assistant works through a single turn.
1818+ */
1919+ memoryRecallPending: boolean
1420}
15211622/** Lifecycle hooks injected by the runner orchestrator into the loop. */