◉CLAUDE LABJP
●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/API & SDK
⬡ API & SDK/2026-05-19Advanced

Hierarchical Chat History Summarization with Claude API: A 3-Tier Design That Cut Tokens by 70%

Working TypeScript design for compressing long in-app chat histories into three tiers — recent turns kept verbatim, mid-range episodes summarized with Haiku 4.5, and long-range memory distilled to JSON by Sonnet 4.6. Includes seven weeks of production data showing input tokens down 70% and monthly API cost down from $480 to $145.

claude-api83chat-historysummarizationtoken-optimizationproduction111

✦ Premium Article

In mid-April 2026, the in-app AI chat I run inside one of my wallpaper apps started silently doubling its per-session API cost. I did not notice it from the monthly graph. I noticed it from a review that said "the assistant feels slower these days," which finally pushed me to open the dashboard. The cause was simple: heavy users had long histories, and every single turn was sending the whole transcript back to the model. Sessions with 40 turns easily passed 18,000 input_tokens.

Running apps on your own means working through a fair number of server-side cost incidents. The Claude API version of that felt different, though, because nothing was broken: users are not complaining about quality, but input_tokens climbs in a straight line because history is linear. I spent a few days rewriting the design around hierarchical compression rather than trimming, and over seven weeks of running totals the average input tokens dropped by 70%.

This article is the implementation note for that hierarchical summarizer. It splits the history into three tiers — Tier 1 keeps the recent turns verbatim, Tier 2 compresses mid-range episodes with Haiku 4.5, and Tier 3 distills long-range memory into a JSON object using Sonnet 4.6. Trimming alone could not reach the compression ratio and the contextual fidelity we needed. The code is adapted from the production version that runs on Cloudflare Workers in TypeScript.

How the History Problem Takes Shape in an Indie App

Every chat-style integration that appends turns to the messages array eventually hits this wall. Short sessions are fine. The top 5% of my long-tail users were burning 38% of the monthly API budget, and because that app monetizes via AdMob rather than subscriptions, every long session was a net cost. That structural mismatch made it impossible to ignore.

Why Naive Trimming Broke User Trust

The first thing I tried was a fixed cap: keep only the last 10 turns. That works on cost, but it throws away context the user expects me to remember. Color preferences from earlier in the session, themes the user rejected, the project they are slowly building — these still need to be referenced 20 turns later. Cheap trimming creates the "the assistant keeps forgetting" feeling. I got that exact complaint multiple times in App Store reviews.

"Compressed Memory" Instead of "Forgetting"

Hierarchical summarization separates the recent flow from the long-range context. Keep recent turns raw, replace older ones with summaries, and reduce the oldest into an abstracted entity set. If trimming is forgetting, then hierarchical summarization is "compressed memory" — keep the outline, let the detail go. That is roughly what you do yourself when you recall yesterday's meeting, and I'd rather build toward that than toward an assistant that starts every session from nothing.

The Three-Tier Architecture at a Glance

The production design partitions history into three tiers.

TierContentCompressionTarget tokens
Tier 1Last 6 turnsVerbatim~2,500
Tier 2Mid-range episodes (summarized)Haiku 4.5, ~200 tokens each800–1,200 total
Tier 3Long-range entities and intentsJSON via Sonnet 4.6~600

Why Tier 1 Is Verbatim

Tier 1 preserves conversational continuity. Six turns is the sweet spot for my wallpaper app because users who say "redo your last suggestion" or "go back to the idea three turns ago" almost always refer to something inside that window. Fewer than six and the conversation breaks; more and token savings hit diminishing returns.

Why Haiku 4.5 Handles Tier 2

Tier 2 is mid-range memory. Once history grows past the Tier 1 budget, the overflow is sliced into six-turn episodes and Haiku 4.5 condenses each to roughly 200 tokens. Haiku is the right model for summarization-style inference. Measured with the recall probes described further down, Haiku summaries land about three points below Sonnet summaries while costing roughly one sixth per call.

Why Sonnet 4.6 Handles Tier 3

Tier 3 is long-range memory. After three episodes accumulate, I run them through Sonnet 4.6 to distill stable preferences, rejected ideas, the user's ongoing intent, and named anchors into a small JSON object. Distillation is harder than summarization, and Haiku occasionally drops important entities here, so the extra spend on Sonnet is worth it.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦Full TypeScript implementation of a Tier 1–3 hierarchical summarizer with concrete threshold numbers
✦Cost breakdown of using Haiku 4.5 for episodic summaries and Sonnet 4.6 for distillation, with seven weeks of running totals
✦A recall-probe harness that measures what compression dropped by position, plus the one-line seam fix that took boundary recall from 63.5% to 86.5%
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

⬡ API & SDK2026-05-26
Stabilizing Claude API Structured Responses in Production — Notes on tool_use, JSON Schema, and Layered Validation
Getting Claude to return JSON takes a few lines. Keeping that JSON usable in production is a different problem. Here is the layered design I landed on after running a wallpaper classification pipeline through Claude API, built around tool_use, JSON Schema, and domain validation.
⬡ API & SDK2026-05-22
Why tool_result could not be submitted Keeps Coming Back, and How to Build a Recovery Handler That Actually Holds
Run a Claude agent long enough and one day it starts: 'tool_result could not be submitted', back to back, and retries change nothing. The error message hides four completely different root causes. Here is what I learned debugging it on the always-on agent jobs I run as an indie developer, with the TypeScript recovery handler I now ship in production.
⬡ API & SDK2026-05-10
Bulletproof JSON Output with Claude API Prefill: A Four-Layer Defense Pattern from Indie SaaS
How I went from late-night JSON parse failures to a 99.98% parse success rate across thousands of monthly Claude API requests, and what a 1,081-sample truncation benchmark revealed about the repair code I had originally published here.