Repository navigation
feat(agent): compact the run's own messages, and count what it costs - #311
Merged
Merged
Conversation
B5's S2. compressHistory ran only at the start of a run, on a.History; the loop's own messages — every assistant step and every tool result — grew inside a run and were never compressed, so a long round grew until the provider refused. That is the failure D7's continuation rounds make routine. `compactRunMessages` compacts that stream at a step boundary once it crosses the compression threshold: the head (system prompt, history, this run's prompt) and the last eight messages stay verbatim, the rest becomes one `[COMPACTED RUN — REFERENCE ONLY]` message. The cut snaps forward to an assistant message, because each round of the loop starts with one and a tool_calls message separated from its results is rejected by the provider. A failed summary is a log line: the messages stay as they were and the run continues. The summarizer call is now accounted. It used to bypass `recordCall`, so its tokens and cost were invisible to UsageTotal, the token budgets and the status bar — a pre-existing gap that compaction would have multiplied. One helper, `summarizeText`, is the only place compression talks to a provider, so the spend is counted in exactly one place. Pins now survive a resume: `chatMessage.Pinned` travels through persistSession into the session file, and historyFromSession restores it into the history the compressor reads. The TUI mirrors the agent's pins into the transcript by matching role and content, newest first, because the transcript also holds system and banner messages and the two lists are not index-aligned. Path-targeted unload is split out as S4 and recorded in the issue: `/drop <path>` cannot act on the run's messages, because those live inside Run and the TUI refuses commands while a run is active. The acceptance sentence that assumed a command channel is corrected there rather than quietly dropped. Verified: go test ./... -count=1, 16 packages ok; go vet and gofmt clean; the loop hook mutation-checked (disabling it fails TestRunCompactsItsOwnMessages with "no provider request carried a compacted run across 12 calls"). Refs #306, #168
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 changed
agent/compaction.go(new):compactRunMessages(ctx, messages, step)compacts the loop's own trafficat a step boundary once it crosses
CompressionThreshold. Head (system prompt, history, this run'sprompt) and the last eight messages stay verbatim; the rest becomes
[COMPACTED RUN — REFERENCE ONLY]. The cut snaps forward to an assistant message, so atool_callsmessage never loses its results. A failed summary logs and leaves the messages alone.
agent/compression.go:summarizeTextis now the single summary call site and it goes throughrecordCall, so the summary's tokens and cost reachUsageTotal, the budgets and the status bar —they were invisible before.
describeMessagesis shared by both compaction paths.agent/agent.go: the loop calls the compaction hook before the provider call.chatMessage.Pinned→persistSession→historyFromSession; the TUI mirrors theagent's pins into the transcript by role and content (newest first), because the transcript also holds
system and banner messages.
PinnedTurnTextsis the view it mirrors from.CODEBASE.md(the two new functions, the account path), roadmap B5 (S1 and S2 landed, S3 and S4open), test count 1270 → 1277.
Why
Compaction ran only at the start of a run, on
a.History. The messages the loop actually sends — everystep's assistant message and every tool result — were never compressed, so a long round grew until the
provider refused. A continuation round (D7) is one
Run, which makes that the routine case rather thanthe edge one.
The accounting gap is the same class of defect: the summary was a real provider call whose spend no
budget could see. Fixing it once, at the single call site, is cheaper than remembering it at each caller.
Verification
go test ./... -count=1→ 16 packagesok;go vetandgofmt -lclean.TestRunCompactsItsOwnMessagesfails withno provider request carried a compacted run across 12 calls; restored, it passes.lands in
UsageTotal); the cut never separates a tool call from its results; a short run is untouchedwith no provider call;
compressHistorynow counts its own call; and a full run throughRuncarriesthe compaction marker into a later request.
/pinmarks the transcript,persistSessionwrites the flag,historyFromSessionrestores it.git log --format='%h %G? %s'→G.Honest scope
compression_threshold, 500k by default): a runhas to grow a long way before it compacts. That is deliberate for now — a lower run-level bound is a
tuning decision with no measurement behind it.
/drop <path>cannot act on the run's messages: they live insideRun, and the TUI refuses commands while a run is active. S2's acceptance sentence assumed a commandchannel that does not exist; the issue is corrected and the work is S4 (a pre-run rule compaction
honours when it walks tool calls by path, or a tool the model calls).
Acceptable for a command a person types; noted rather than hidden.
Follow-ups
Refs #306, #168