Repository navigation
agent-runtime: stub old tool results in history instead of carrying them forever - #1586
Merged
obadakhalili merged 1 commit intoOct 30, 2026
Conversation
…hem forever
read_files and run_terminal_command results (up to 100k/50k chars each)
stayed verbatim in messageHistory and were re-sent to the model on every
step until compaction at ~80% context. Cost grew quadratically with
session length; a 30-step session with 20 reads reshipped megabytes of
untouched content per step.
runAgentStep now runs simplifyOldToolResults over the expired, orphaned-
call-free history each step (next to expireMessages). Results older than
the newest keep window (5 terminal, 3 read_files) become the omit stubs
fitToolResults and getPreviouslyReadFiles already consume:
{ path, contentOmittedForLength: true } / { command, stdoutOmittedForLength:
true, exitCode? }. Terminal background-process and error results, already-
stubbed entries, and media-carrying read results never stub and never
consume window slots, matching simplifyTerminalHelper's baseline in
util/messages.ts.
Safety: the pass is pure copy-on-write. Stubbed messages are new objects;
everything else keeps reference identity, and the input array is returned
unchanged (same reference) when nothing stubbed, so the
inputHistory.every((m, i) => history[i] === m) extension check in
runAgentStep and shared history snapshots see exactly the pass's edits.
Newest results and anything the in-flight step appends later are
untouched. Malformed results log and stay verbatim.
Quality: the pass waits until past-window results carry at least 20k
chars before stubbing anything. Below that the savings don't justify
dropping content the model may still re-read, and a verbatim result keeps
fitToolResults' graded prefix-clip (with re-read offset/limit hints)
available on overflow, while a stub has nothing left to clip. The stubs
themselves preserve every read path, and a stubbed read can be re-read
exactly with offset/limit; a re-run of a stubbed command is never
suggested.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
read_filesandrun_terminal_commandresults (up to 100k/50k chars each) stayed verbatim inmessageHistoryand were re-sent to the model on every step until compaction at ~80% context. Cost grows quadratically with session length: a 30-step session with 20 reads re-ships megabytes of untouched content per step.The simplifier helpers for this already existed (
simplifyReadFileResults/simplifyTerminalCommandResults, the same omit stubsfitToolResultsandgetPreviouslyReadFilesalready consume) but had zero production callers — only tests. This PR wires them into the main loop.Change
runAgentStepnow runssimplifyOldToolResultsover the expired, orphan-free history each step (right next toexpireMessages). Results older than the newest keep window (5 terminal / 3 read_files) become the omit stubs:{ path, contentOmittedForLength: true }/{ command, stdoutOmittedForLength: true, exitCode? }.Safety:
inputHistory.every((m, i) => history[i] === m)extension check and SDK snapshots (which share message objects) see exactly the pass's edits.fitToolResults' graded prefix-clip (with offset/limit re-read hints) available on overflow — a stub has nothing left to clip.Quality: stubbed reads can be re-read exactly via offset/limit (the stub tells the model so — same phrasing
fitToolResultsuses); re-running stubbed commands is never suggested.Tests
8 new unit tests (window semantics, per-tool independence, passthrough classes, reference identity, savings floor, override) + a loop-level test proving three oldest bulky reads are stubbed in both the request and the stored history while newest-3 stay verbatim. Suite: 975/976 (one pre-existing base failure, verified unrelated), typecheck clean.