Trim or Summarise: The Cheap Fix That Makes Your Agent Forget Why It Is Here
An agent’s context grows every turn and keeps growing until something stops it. That much is arithmetic, and the context window is a parcel, not a memory works through why: nothing is stored between messages, the whole transcript is re-sent each time, and the total therefore grows with the square of the conversation length. Twenty turns of small talk bills twenty-one times what the words you typed would suggest.
That page ends by naming the remedies in a single line — trim the old turns, summarise them, or start fresh. This page is about the fact that those are not one remedy. They are two, they cost almost exactly the same, and one of them quietly destroys the run.
Twenty turns, and you decide when to intervene
A 20-turn agent run: 60 tokens in, 250 back, and 400 tokens of instructions on every request. Left alone the transcript grows until it bills 68,100 tokens across the run, and turn 20 alone costs 6,350. Something has to stop that, and there are two ways to do it.
Summarising every four turns costs 28,480. Throwing the old part away on the same schedule costs 28,420 — 60 tokens cheaper, a fifth of one percent — and arrives at turn 20 having lost all 3 of the facts the job depends on, while the summarised run still holds every one.
It is not a trade-off. It is a free choice where the cheaper option is catastrophically worse, and it gets taken constantly because truncation is four lines of code and summarisation is something you have to design. Summarising less often is the milder version of the same mistake: every eight turns costs 36,520 rather than 28,480.
Reach turn 20 under 40,000 tokens with all 3 facts still in context. Turns 3, 7, 12 establish something the last turn needs. You can summarise the transcript or throw it away, at any point, as often as you like.
- Turn
- 0 of 20
- That turn billed
- 0
- Spent
- 0 / 40,000
- Facts in context
- 0 of 3
This run, as text
Next request
Billed so far
Facts
Token counts are a model, not a measurement of any particular provider: a fixed cost per message, a fixed instruction block, and a summary that costs one read of the transcript to write. Real systems differ in every one of those, and caching can make a repeated prefix much cheaper. What does not differ is the shape — cost per turn rises with the conversation unless something actively stops it.
Reach turn 20 under budget with all three facts still in context. Both shrinking buttons work. Only one of them leaves the agent knowing why it is here.
Throwing the old part away is instant, free to implement, and the first thing anyone builds. Watch what it costs.
The two remedies, priced
SYSTEM = 400 # instructions and tool definitions, resent every single turn
ASK = 60 # what you type
REPLY = 250 # what it says back
TURNS = 20
SUMMARY = 180 # what a compaction leaves behind
KEEP = 2 # exchanges a truncation keeps
FACTS = (3, 7, 12) # turns that establish something the last turn needs
def run(plan):
"""plan(turn) -> None | 'compact' | 'truncate', applied BEFORE the turn is sent.
Returns tokens billed as input across the run, the last turn's bill, and which facts survive.
Everything is an integer count of tokens."""
carried, spent, last = 0, 0, 0
held = set()
for turn in range(1, TURNS + 1):
move = plan(turn)
if move == "compact":
# Summarising is not free: something has to read the transcript to write the summary.
spent += SYSTEM + carried
carried = SUMMARY
elif move == "truncate":
carried = min(carried, KEEP * (ASK + REPLY))
held = {f for f in held if turn - f <= KEEP}
last = SYSTEM + carried + ASK
spent += last
carried += ASK + REPLY
if turn in FACTS:
held.add(turn)
return spent, last, held
def every(n, move):
return lambda t: move if t > 1 and (t - 1) % n == 0 else None
def show(name, plan, note):
spent, last, held = run(plan)
facts = f"{len(held)}/{len(FACTS)}"
print(f"{name:<26}{spent:>9,}{last:>8,}{facts:>8} {note}")
print(f"A {TURNS}-turn agent run: {ASK} tokens in, {REPLY} back, {SYSTEM} of instructions every time.")
print(f"Turns {', '.join(map(str, FACTS))} establish something the last turn needs.")
print()
print(f"{'what you do':<26}{'total in':>9}{'turn 20':>8}{'facts':>8} outcome")
print("-" * 78)
show("nothing", lambda t: None, "the transcript grows without limit")
show("truncate every 4 turns", every(4, "truncate"), "cheapest, and it forgot why it is here")
show("summarise every 4 turns", every(4, "compact"), "cheapest that still knows anything")
show("summarise every 8 turns", every(8, "compact"), "fewer summaries, bigger transcript")
print()
t4, _, theld = run(every(4, "truncate"))
c4, _, cheld = run(every(4, "compact"))
print(f"Truncating and summarising on the same schedule differ by {c4 - t4} tokens out of {c4:,}.")
print(f"One of them ends the run holding {len(cheld)} of {len(FACTS)} facts. The other holds {len(theld)}.")
print("That is not a trade-off. It is a free choice, and the cheap-looking option is the wrong one.")
print()
print("Summarising costs a read of the transcript, so doing it more often is not automatically")
print("better - there is a floor. Sweeping the interval:")
print()
print(f"{'summarise every':>16}{'total in':>11}{'summaries':>11}")
best = None
for n in range(1, TURNS + 1):
spent, _, _ = run(every(n, "compact"))
count = sum(1 for t in range(2, TURNS + 1) if (t - 1) % n == 0)
if best is None or spent < best[1]:
best = (n, spent, count)
if n in (1, 2, 3, 4, 6, 8, 10, 20):
print(f"{n:>16}{spent:>11,}{count:>11}")
none_spent, _, _ = run(lambda t: None)
print(f"{'never':>16}{none_spent:>11,}{0:>11}")
print()
print(f"Best here is every {best[0]} turns at {best[1]:,} tokens, using {best[2]} summaries.")
print(f"Against {none_spent:,} for leaving it alone, that is {(none_spent - best[1]) * 100 // none_spent}% off the bill")
print(f"with every fact still in context.")A 20-turn agent run: 60 tokens in, 250 back, 400 of instructions every time.
Turns 3, 7, 12 establish something the last turn needs.
what you do total in turn 20 facts outcome
------------------------------------------------------------------------------
nothing 68,100 6,350 3/3 the transcript grows without limit
truncate every 4 turns 28,420 2,010 0/3 cheapest, and it forgot why it is here
summarise every 4 turns 28,480 1,570 3/3 cheapest that still knows anything
summarise every 8 turns 36,520 1,570 3/3 fewer summaries, bigger transcript
Truncating and summarising on the same schedule differ by 60 tokens out of 28,480.
One of them ends the run holding 3 of 3 facts. The other holds 0.
That is not a trade-off. It is a free choice, and the cheap-looking option is the wrong one.
Summarising costs a read of the transcript, so doing it more often is not automatically
better - there is a floor. Sweeping the interval:
summarise every total in summaries
1 29,350 19
2 26,160 9
3 27,030 6
4 28,480 4
6 33,120 3
8 36,520 2
10 42,400 1
20 68,100 0
never 68,100 0
Best here is every 2 turns at 26,160 tokens, using 9 summaries.
Against 68,100 for leaving it alone, that is 61% off the bill
with every fact still in context. Sixty tokens
Truncating every four turns costs 28,420. Summarising every four turns costs 28,480.
Sixty tokens apart, out of twenty-eight thousand. A fifth of one percent.
And the truncated run reaches turn 20 holding none of the three facts it needs, while the summarised one holds all three.
That is not a trade-off, and calling it one is how the mistake survives. It is a free choice where one option is slightly cheaper and catastrophically worse, and people take it constantly because truncation is four lines of code and summarisation is an API call you have to design.
The failure is also silent. A truncated agent does not announce that it has forgotten your constraint from turn 3. It carries on with total confidence, and the first sign is an answer that contradicts something you are certain you said.
Why summarising is charged for here
It would be easy to make this argument dishonestly, by treating summarisation as free. It is not: something has to read the whole transcript in order to write the summary, and the model above bills for exactly that.
Charging for it is what makes the comparison mean anything — and it is also what produces the second result. Look at the sweep. Summarising every turn costs 29,350 tokens; summarising every second turn costs 26,160. More often is not better, because past a point you are paying to re-read a transcript that had barely grown since the last time you read it.
So compaction has a floor as well as a ceiling. Leaving it alone costs 68,100. Doing it obsessively costs 29,350. Doing it on a sensible interval costs 26,160, and every fact survives.
What this means when you are the one at the keyboard
Starting a new chat is a compaction — if you carry something across. The urge to open a fresh conversation when one gets slow is right. Opening it empty is the truncation row: you have taken the cheap option and lost the context. Pasting in a paragraph of what was decided is the summarise row, at almost identical cost, and it still knows why it is here.
“Summarise what we have established so far” is the highest-value message in a long chat. It costs one turn, shrinks every turn after it, and hands you something to check — which is the only moment you find out whether it actually understood the constraint you set fifteen turns ago.
This is what a coding agent means by compacting. When a tool like Claude Code compacts a session, this table is what it is doing and why. The alternative is not “keep everything” — that row costs 68,100 and eventually stops fitting at all. The alternative is throwing the transcript away, which is the row that forgets the task.
Put the durable facts somewhere that is not the transcript. Anything the run depends on — the constraint, the file path, the decision — is safer written to a file, a task description, or a pinned instruction than left sitting in a conversation that something is going to shrink. The three facts in this model are lost precisely because the transcript was the only place they lived.
And the cheapest compaction of all is not needing the corrections in the first place: every decision left vague is a round of follow-up, and follow-ups arrive exactly when the transcript is at its longest.
What this page simplifies
- Prompt caching is not modelled, and it matters. Most providers can cache a repeated prefix so re-sending it is much cheaper than sending it fresh. That changes the price of everything above; it does not change the ranking, because a cache does not help the turn after a compaction — the prefix just changed — and it does not make a truncated agent remember anything.
- The summary is assumed to be good. A 180-token summary here keeps everything that matters. Choosing what survives is the genuinely hard part of compaction, and a bad summary is a slow truncation.
- The optimum interval is an artifact of these constants. Every-two-turns wins here because a summary costs one read at these sizes. The shape — a floor, not a slope — is the transferable part. In practice latency and summary quality decide the interval long before token cost does.
- Facts are treated as atomic and permanent. Real context is not three flags; it is a hundred details of varying importance whose relevance changes as the task moves.
- Output tokens are ignored, as is the wall-clock cost of a summarisation call, which is a real pause the user of an interactive agent notices.