Dialogue · Design notes · Cache TTL
← Design notes

Is a longer cache TTL worth it?

A live room re-sends the same large prefix every turn — the megaprompt plus each host's profile, then the whole running transcript. Prompt caching pays for that prefix once and reads it cheap afterward. But the cache expires, and the only question that matters for a human-in-the-loop room is: does it survive the gap between turns? This is the study, the design we shipped, and the measured saving. (Companion to the Prompt caching explainer — that's the mechanic; this is the economics.)

Recorded 2026-06-04 · case: the “Early Adopter” room (megaprompt-humanloop, claude-sonnet-4-6, 10 turns)

Switching the prefix from a 5-minute to a 1-hour TTL cut the room's API cost $1.78 → $0.75 — a ~58% saving on the same conversation. Shipped as the default.

1 · The only thing that changes is the write price

Reads are identical across both TTLs. The cache's clock resets on every hit. So the lever is just the write multiplier — and whether the next turn lands before the clock runs out.

Token class× base inputSonnet 4.6 $/Mtoknote
Base input (fresh)1.0×$3.00uncached
Cache write · 5-min TTL1.25×$3.75cheaper to write
Cache write · 1-hour TTL2.0×$6.001.6× the 5-min write
Cache read (hit)0.1×$0.30same for both TTLs
Output$15.00unaffected

A read is 12.5× cheaper than re-writing the same tokens under 5-min. So the whole game is converting expired re-writes into cheap reads. The cost of 1-hour: a write premium of 0.75 × prefix, paid only on a genuinely cold start.

2 · Why it bites here — the human-pause dead band

A human reads six panel replies, thinks, and types a long paragraph. Those gaps mostly land between 5 minutes and 1 hour — the band where a 5-min cache is already dead but a 1-hour cache is still warm. In the Early Adopter room, 6 of 9 gaps fall in that band.

3 · The ledger, turn by turn

From the room's event-log.json. Under the live 5-min scheme, writes mean a full prefix re-write (cache_read 0); reads are warm hits. The 1-hour column is the same conversation re-priced: the flip tag marks a turn that was a full re-write under 5-min but becomes a cheap read under 1-hour.

TurnGap before5-min cache1-hour cache
1 · kickoffwrite 26,686write 26,686 cold start
23h 12mwrite 56,569write 56,569 >1h — cold either way
347mwrite 58,643read 56,569 +w 2,074 flip
44m 51sread 58,643read 58,643
51m 33sread 58,804read 58,804
66m 26swrite 59,550read 59,081 +w 469 flip
78m 52swrite 60,193read 59,550 +w 643 flip
811m 05swrite 60,996read 60,193 +w 803 flip
911m 34swrite 61,986read 60,996 +w 990 flip
108m 54swrite 62,729read 61,986 +w 743 flip
totalswrite 447,790 · read 117,447write 89,415 · read 475,822

Only turns 4–5 (sub-5-min gaps) hit the cache under 5-min — every other turn re-wrote the whole ~60k prefix. The room is unusually TTL-sensitive because it re-sends the entire conversation as one cached prefix, so an expiry is a total re-write, not a partial miss.

4 · The saving

Both bars are drawn on the same dollar scale (5-min total = full width), so the 1-hour bar is literally shorter by the amount saved.

5-min TTL $1.78
writes $1.68
1-hour TTL $0.75
writes $0.54 $0.14
cache writes cache reads output
Component5-min1-hourwhat moved
Cache writes$1.6792$0.5365447,790 → 89,415 tok
Cache reads$0.0352$0.1427re-writes became reads
Input + output$0.0694$0.0694unchanged
Total$1.7838$0.7486−$1.04 · −58%

The 5-min run's recorded cost was $1.783816 — the reconstruction matches to the cent, which is what validates the 1-hour projection. (The $0.75 is a projection until the room takes its first live turn under the new setting and the log records actual ephemeral_1h_input_tokens.)

5 · The decision rule

This isn't universal — it's a property of the gap distribution. Match the TTL to how the room is actually used.

Inter-turn cadenceUseWhy
reliably < 5 min5-minfree refresh on every hit; the 2× write premium buys nothing
mostly 5 min – 1 hour1-hourthe dead band — 5-min keeps expiring, 1-hour stays warm
reliably > 1 houreitherboth cold; 1-hour just overpays the write premium

6 · What we shipped

Both cache breakpoints in lib/run_room.py now run through one helper at a single CACHE_TTL constant. 1-hour TTL is generally available on Sonnet 4.6 — no beta header.

# lib/run_room.py CACHE_TTL = "1h" def _cache_control(ttl=CACHE_TTL): return {"type": "ephemeral", "ttl": ttl} # BP1 persona / system prefix — build_system_blocks() → 1h # BP2 rolling dialogue tail — _messages_with_cache() → 1h

Both breakpoints at 1h

The persona prefix and the growing dialogue must both survive the pause — the dialogue is the bulk of the prefix by mid-conversation. Holding only the prefix would still re-write the transcript every slow turn.

No live 5-min tail

A 5-min tail only helps once there's genuinely volatile per-turn content (RAG, fresh context). There is none today — marking the dialogue 5-min would re-expire it. The helper keeps the ordering valid for a future tail.

The legacy AI-AI batch coordinator (the archived run_dialogue.py) ran at 5-min by design: it drove turns machine-paced, back-to-back, so gaps stayed under 5 minutes and the cheaper write multiplier was correct there. Opposite regime.

7 · A smarter, adaptive switch?

The community asks this too — Anthropic's docs name this exact "user may not respond in 5 minutes" scenario, and a public Claude Code issue tracked ~17% cost waste when the default regressed 1h→5m. Anthropic exposes no auto-adaptive knob, so adaptivity is the app's job. Three options considered:

Bottom line: default the prefix to 1-hour for deliberative human rooms. Near-zero downside, ~58% saving. Revisit adaptivity only if many short, rapid-fire sessions appear.

Sources