The quadratic you didn't budget for#
The simplest way to give an NPC memory of a conversation is to resend the whole conversation every turn. It works, it's what most prototypes do, and it makes cost grow with the square of conversation length. A character who gets twice as chatty costs four times as much. This post derives the cost of full history, the cost of a common alternative (summarise the last exchanges, then drop them), and the interval that minimises spend. Then it checks the formula against an exact turn-by-turn count.
Code: research/context_budget.py. Prices are illustrative (, per million tokens). Only their ratio affects the optimum.
Cost of full history#
Each turn sends a static prompt of tokens (character sheet, rules, world state) plus every previous exchange of tokens, and gets tokens back. Turn carries previous exchanges, so over turns:
At 60 turns that's 391,200 input tokens, 72% of them repeated history. The static prompt is a rounding error by the end of a long conversation.
Summarise-and-flush, and its optimum#
Instead, keep at most raw exchanges. When the buffer fills, a summarisation call compresses the old summary plus those exchanges into a new summary of tokens (with tokens of instructions), and the buffer empties. The raw history in context then cycles from 0 to exchanges, on average. Approximating for :
The middle term grows with (more raw history per turn); the last shrinks with (fewer summarisation calls). Setting :
Note what's missing: and . The optimal interval doesn't depend on how long the conversation runs or how big the character sheet is. It's a ratio of what a summary costs to what a raw exchange costs to carry. With our numbers, .
Checking it against an exact count#
Exact per-session cost at 60 turns for k = 1…40, counting every token turn by turn, including the cold start before the first summary exists.
The exact discrete optimum is , next to the continuous , and the curve is flat near the bottom: anything from 3 to 5 is within 1% of optimal, and 6 is within about 2%. That flatness is useful in practice. You can choose for narrative reasons (summarise at scene boundaries) without paying much.
Cost per conversation as length grows. Summarisation wins from about 10 turns.
| Turns | Full-history input tokens | Full history $ | Best k | Summarise $ | Saving |
|---|---|---|---|---|---|
| 10 | 25,200 | $0.088 | 5 | $0.083 | 5% |
| 20 | 66,400 | $0.223 | 4 | $0.173 | 22% |
| 40 | 196,800 | $0.638 | 4 | $0.353 | 45% |
| 60 | 391,200 | $1.246 | 4 | $0.534 | 57% |
| 100 | 972,000 | $3.036 | 4 | $0.894 | 71% |
| 200 | 3,544,000 | $10.872 | 4 | $1.796 | 83% |
Summarisation starts paying off at around 10 turns and saves 57% at 60. For the long companion-style conversations where players talk for hundreds of turns, it's the difference between a viable game and one that can't be priced.
How prompt caching changes the answer#
The analysis above assumes every input token costs . With prompt caching, history that was already sent last turn can be read from cache at roughly a tenth of the price. That makes carrying raw history ~10× cheaper, so the denominator shrinks by ~10 and
With a warm cache you should summarise far less often, roughly every 11 exchanges instead of every 4. This is a first-order estimate: it treats every carried token as a cache hit, which only holds if players reply within the cache TTL. Our caching post measures how often that's true.
What the model doesn't price: memory quality#
Every summary loses detail, and a character who forgets the name of your dog is a worse character. Two practical points from shipped titles:
- Summaries should be structured, not prose. A summary that records facts as key–value pairs ("player_promised: return lantern by day 9") survives repeated re-summarisation far better than a paragraph, which drifts a little each time.
- Pair summaries with retrieval. Store the raw exchanges outside the prompt and retrieve the few that matter. Summaries carry the gist; retrieval brings back exact quotes when the player asks "what did I say?". See NPC memory architecture.
Measure the trade-off directly. Run your eval suite at and at , and if quality scores don't move, take the cheaper one.
Reproduce it#
The full script is research/context_budget.py (Python 3.10+, numpy, scipy; fixed seeds, so you'll get the same numbers). This is the core of it:
def summarize_flush(n, k):
tin = tout = hist = 0; has_summary = False
for t in range(1, n + 1):
tin += S + (s if has_summary else 0) + m * hist # prompt for this turn
tout += o
hist += 1
if hist == k and t < n: # compress and flush
tin += (s if has_summary else 0) + k * m + 120
tout += s
hist, has_summary = 0, True
return tin, tout
Run it with python3 research/context_budget.py; results land in research/out/, and the site's tables and charts are rendered from that JSON at build time. Set S, m and s from your own logs. Average them over a real playtest rather than guessing, because m varies a lot between chatty and terse players.
Frequently asked questions#
Why does LLM conversation cost grow quadratically?
If each turn resends all previous turns, turn t carries t−1 exchanges, so total input tokens over n turns grow with n(n−1)/2.
How often should an NPC summarise its conversation?
The cost-optimal interval is k* = √(2(s+q+ρs)/m), where s is summary size, q summariser instructions, m tokens per exchange and ρ the output/input price ratio. Typical values give 3–5 without caching and 10+ with prompt caching.
Does summarisation hurt NPC quality?
It can. Use structured, fact-based summaries plus retrieval of raw exchanges, and test quality scores at different intervals before choosing.