Sertaç Yıldırım field notes

Home → Engineering

Context: The Right Summary, Not a Longer Window

I asked the same question twice: “why was this customer’s last withdrawal rejected?” The first time, the agent had been running for 40 minutes and the window held 118,000 tokens; the answer was wrong and pointed at a record from three months earlier. The second time I opened a clean session — same tools, same model, 9,000 tokens in the window: the answer was right. The model did not get smarter. Its desk had been cleared.

In short
  • A model does not remember; it re-reads. Every turn, the whole stack in front of it is processed again. There is no “I told you earlier”: either it is in the stack or it does not exist.
  • A longer window is a bigger messy desk. Hundreds of irrelevant sentences sit next to the right one. At 70% full we got 31/34 labels right; at 95% we got 24/34.
  • The biggest win is in tool output. We never sent the 2,300-row list to the model; the tool cut it to 13 rows. That one change took tokens per run from 118k to 31k.
  • Run state does not live in the window. Which step we are on, what was decided, what is open — that lives in a record on disk, and only a short version enters the window.
  • Summarising is the last resort and it is a loss. Identifiers, decisions and open questions are carried word for word; intermediate reasoning is dropped. Always check the first answer after a summary.
  • Buying a longer window is not buying a solution. Doubling the window delays the moment it fills; it does not change how fast you fill it.

What is in the window?

The agent loop sends a stack to the model on every turn. This is what piles up in it:

PartHow much spaceLifetime
System promptFixed, ~900 tokens for usThe whole run; sent again every turn
Tool definitions (11 tools)~2,400 tokensThe whole run
The user’s questionSmallThe whole run
Tool repliesUnbounded — this is what growsUsually useful for one or two turns, kept until the end
The model’s own intermediate textMediumWorthless once the decision is made

Watch the fourth row. When a tool returns 2,300 rows, those 2,300 rows are sent again on every turn until the end of the run. In an 8-turn run you pay for the same data 8 times, and 7 of those times it does nothing.

A model does not remember; it re-reads. Every line you put in the window is paid for again in every remaining turn.

Why a longer window is not enough

“The window is 200k tokens, 2,300 rows fit” is a true sentence that leads to a wrong conclusion. Fitting is not the same as working. Three things I measured:

1. Accuracy falls as the window fills

I repeated the labelling job from the previous post — 34 withdrawals — while deliberately filling the window. Same model, same prompt, same data; the only variable is how full the window is:

Window fillCorrect labels (out of 34)Answer time
20% (only the data needed)319 s
50%3014 s
70%3119 s
85%2726 s
95%2434 s

Up to 70% there is no serious loss; after that the drop is sharp. When I looked at the records it got wrong, most of them depended on data sitting in the middle of the stack. The instruction at the start and the question at the end stay strong; what is in between drowns.

2. Cost snowballs with turns

The window is re-sent every turn. A 10k-token stack in an 8-turn run is 80k tokens. If the stack also grows across turns, the total rises close to the square of the turn count. In that night job, about 71k of the 118k tokens was data being sent again after it had already been sent.

3. Latency loses the user

The last column of the table: a four-fold gap between 9 and 34 seconds, and the only difference is a full window. In a chat window, 34 seconds means the user switches tabs.

Where to cut: three places, in this order

1. Tool output: the biggest win

Never sending raw data to the model is always cheaper than sending it and summarising later. Our list_transactions tool used to return every row. It does not any more:

Shaping the tool reply
# BEFORE: 2,300 rows, ~46,000 tokens
return {"transactions": [row for row in query()]}

# AFTER: summary + small sample + continuation key, ~380 tokens
return {
  "count": 2300,
  "total_amount": 1841250.40,
  "currency": "USD",
  "sample": first_n(query(), 5),      # 5 rows, just to show the shape
  "unmatched": unmatched(),           # 13 rows: what the job is about
  "next": "page=2"                    # the model can ask for detail
}

The rule: a tool returns what the model needs in order to decide; it is not a data store. If detail is needed, there is a second call. This one change took the night job from 118k to 31k tokens per run. (The tool contract itself is a post of its own.)

2. Run state: on disk, not in the window

If the agent’s “where was I” lives in the window, it disappears when the window is cleared. We keep run state in a separate record:

Run state (on disk, not in the window)
{
  "run_id": "rec-2026-09-19-01",
  "step": 3,
  "decisions": ["date range: 18.09 00:00-23:59",
                "tolerance: 0.01 USD"],
  "open_questions": ["WD-24817 appears in two records"],
  "completed": ["file_read", "matched"],
  "identifiers": ["WD-24817", "WD-24902", "CUST-7741"]
}

What enters each turn is not this record but a short translation of it: 6–8 lines. If the run stops halfway, it continues from here; even if the window is reset, the work is not lost. The nice part is that a person can read it too: whoever looks in the morning sees where it stopped in 8 lines.

3. Summarising: the last resort

If the window still nears its limit, old turns are reduced to a single note. There are two rules here, and both were learned the hard way:

  • Identifiers, decisions and open questions are carried word for word. They are not rephrased. In my first attempt the model wrote WD-24817 as WD-24871, and the agent went on to review the wrong record. Numbers travel outside the summary, as a list.
  • The first answer after a summary gets checked. Summarising is the most fragile moment of a run. Ours was clearly less accurate right after a summary, so we ordered the flow to make the first step after a summary a verifiable one: a count, or an identifier check.
A summary is a loss. The question is not “should I lose something?” but “which loss am I choosing?”

From the field: turn 13 of the night job

In the orchestrator post, turns 13–15 of that 17-turn night were the ones where the agent started a summary, stopped and fetched data again. I read those turns again, this time from the context side:

TurnWindowWhat happened
1–312k tokensFine
4–758k2,300 rows entered the window as text
9–1296kFour more detail calls were added
13112kThe summary started: the date-range decision from turn 1 was now in the middle of the stack
14118kThe model was no longer sure about the date range and fetched the data again
16–17118k (trimmed)The summary was written from half-finished matching: wrong

The mistake here is mine, not the model’s: I put decisions in the same place as data. “Date range is 18.09 00:00–23:59” is a decision and holds for the whole run; the 2,300-row list was used once and finished. Put them in the same stack and the data buries the decision.

Keep in the window
  • The task and its constraints.
  • Decisions made (date range, threshold, the label set chosen).
  • Identifiers: record ids, customer numbers, file names.
  • Questions still open.
  • The last step’s output, trimmed.
Keep out of the window
  • Raw lists, full file contents, long JSON.
  • Detail from a step that is already decided.
  • The model’s own intermediate reasoning (keep the decision, drop the thinking).
  • Stack traces; one line of the error is enough.
  • Reference text that never changes (leave it behind a tool).

Why “just use a bigger window” is not the answer

Moving to a model with a larger window does not change how fast you fill it; it only delays the moment. All three problems stay: quality drops in the middle, cost is paid again every turn, latency grows. Our 4-billion-parameter model had a small window to begin with, and that forced discipline on us early. When I later tried a large-window model, the same mistakes appeared later but in exactly the same shape. Window size is a budget; a bigger budget does not make spending discipline optional.

What to watch

  • Tokens per run, broken down by turn. You see which turn jumps; the jump is always a tool reply.
  • Window fill, p95. Above 70%, quality loss has started.
  • Re-sent token ratio. Total tokens ÷ unique tokens. Above 3 means the stack is carried too long.
  • Number of summaries, and error rate right after one. The most fragile moment of a run; measure it.
  • Tool reply size, p95 per tool. This number names your fattest tool.
  • Time to first answer. The only number the user sees.

Checklist

Before you buy a bigger window
  • How much of the tokens per run is tool output? Which are the three biggest tools?
  • Which tool returns raw data? Can it be cut to summary + sample + continuation key?
  • How many turns is the same data re-sent for?
  • Where is run state: in the window or on disk?
  • Do decisions sit in the same place as data?
  • Are identifiers inside the summary, or in a separate list?
  • When is summarising triggered? Is the next step verifiable?
  • Above 70% fill, did you measure quality or are you guessing?
  • Could splitting the work (sub-agents) shrink the window?
  • How many seconds until the user sees a first answer?

Conclusion

Two sessions gave two different answers to the same question. The difference was not the model, not the prompt, not the data: it was the window. In the full window the right record was there, but it sat among 2,300 rows and the model could not pick it. In the clean window the same record was one of 13 rows.

We did three things, in order: trimmed the tools, moved run state out of the window, and made summarising a last resort. Tokens per run went from 118k to 31k, time to first answer from 34 seconds to 11, and label accuracy from 24/34 to 31/34. None of it was a model change.

The sentence to remember: context is a desk, not a warehouse. Watch what you put on it. A bigger desk does not fix the mess; it only lets you carry more of it.