From 35015485439914f959d2e2096f5f6076c15e9271 Mon Sep 17 00:00:00 2001 From: wolfy Date: Mon, 1 Jun 2026 13:29:51 -0500 Subject: [PATCH] feat: preserve read_file context across pruning with file-read summary injection --- agents/ollama.ts | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/agents/ollama.ts b/agents/ollama.ts index f3ee160..c692d14 100644 --- a/agents/ollama.ts +++ b/agents/ollama.ts @@ -150,6 +150,11 @@ async function runOllamaLoop(ctx: AgentRunContext): Promise { let addedContinueNudge = false; let reportProgressCount = 0; let selectedModeName = ""; + // Accumulates content previews from read_file calls so that when tool + // messages are pruned under context pressure, the model retains a compact + // summary of what it already read. User messages survive pruning. + const readFileLog = new Map(); // path → content excerpt + let injectedFileReadSummary = false; while (iterations < MAX_ITERATIONS) { iterations++; @@ -205,6 +210,22 @@ async function runOllamaLoop(ctx: AgentRunContext): Promise { ); if (promptTokens > numCtx * 0.77) { messages = pruneToolMessages(messages); + // Inject a compact summary of all files read so far as a user message + // so the model retains the context even after tool messages are pruned. + // Only inject once — subsequent prune events leave this message intact. + if (readFileLog.size > 0 && !injectedFileReadSummary) { + injectedFileReadSummary = true; + const entries = [...readFileLog.entries()] + .map(([p, c]) => `### ${p}\n${c}`) + .join("\n\n"); + messages.push({ + role: "user", + content: + `The following files were read earlier but their content was pruned from context. ` + + `Use these excerpts as your understanding of each file when writing the review:\n\n${entries}`, + }); + log.info(`» injected file-read summary: ${readFileLog.size} file(s)`); + } } } @@ -285,6 +306,39 @@ async function runOllamaLoop(ctx: AgentRunContext): Promise { content: result, }); + // Track read_file calls so we can reconstruct context after pruning. + if (toolName === "read_file") { + const path = (toolArgs as Record)?.path; + if (typeof path === "string" && !readFileLog.has(path)) { + readFileLog.set(path, result.slice(0, 500)); + } + } + + // After checkout_pr returns, extract and pin the diffPath as a user + // message. Only tool messages are pruned — user messages survive context + // pressure — so the model retains the exact diff file path and knows how + // to call read_file with offset/limit to read diff ranges. + if (toolName === "checkout_pr") { + try { + const parsed = JSON.parse(result) as Record; + if (typeof parsed?.diffPath === "string") { + const dp = parsed.diffPath; + messages.push({ + role: "user", + content: + `The diff file is at: ${dp}\n\n` + + `Read each file's diff by calling read_file on this path with the line range from the TOC.\n` + + `Example — for "device.ts (163 lines, 1146-1308)": read_file(path="${dp}", offset=1146, limit=163)\n` + + `IMPORTANT: Do NOT call read_file on source files (apps/..., packages/...). ` + + `Use read_file on the diff file path above with the TOC ranges.`, + }); + log.info(`» pinned diffPath to context: ${dp}`); + } + } catch { + // best-effort: if checkout_pr result isn't JSON, skip + } + } + // report_progress called more than once means the model is looping — // it has no signal to stop since the tool always returns success. if (toolName === "report_progress") {