Library tested: headroom-ai v0.31.0 Goal: Shrink the big junk that LLMs read (JSON, logs, tool dumps, RAG docs) before it hits the model Important: Every LLM answer in this article is mocked . Every token number comes from a real local headroom compression run on my machine. Experiments were performed by Cursor Agents. These are also my personal views and findings from my own setup, not a definitive benchmark. I may have gotten configuration details wrong or missed something that skewed or degraded some of these results Quick summary Headroom sits between your app and the LLM. It compresses tool outputs and other fat context. In my tests: What is Headroom, in plain words? Imagine your AI agent asks a database tool for “sales last week.” The tool returns a giant JSON blob. Most of it is repeated fields, tags, long descriptions, and boring rows. The model still has to read all of it . You pay for those tokens. The answer often only needed a few useful rows. Headroom is a token optimization layer. It tries to: Detect what the content is (JSON, logs, code, text, etc.) Pick a compressor that fits that type Send a smaller version to the LLM Keep originals in a retrieve store ( CCR ), so the model can ask for full detail if needed. Those weird <<ccr:...>> / hash=... strings in compressed output are the lookup keys, not corruption You can use it as: a Python function: compress(messages) a LangChain wrapper: HeadroomChatModel, tool wrappers, memory, retrievers a proxy / MCP server (not the focus of this article) How I tested I did not call OpenAI/Anthropic for answers. Compression: real headroom-ai==0.31.0 LLM replies: a fake MockChatModel that returns fixed text Token counting target model name: gpt-4o (used by Headroom's counters) LangChain: real integrations (HeadroomChatModel, HeadroomToolWrapper, memory, document compressor) That means: Savings numbers = real “The model said …” = fake on purpose (so the article is reproducible) Install pip install headroom-ai langchain-core langchain-community tiktoken # LangChain extra (if you want the documented extra set): # pip install "headroom-ai[langchain]" What does compression look like? (before vs after) I ran before/after captures. Below are the real shapes Headroom produced. Example A — Full chat with a tool call (sales JSON) Tokens: 8,055 to 3,887 ( 51.74% saved , 4,168 tokens) Before (what you would send without Headroom) Same 4 roles. The fat part is only the tool message. [ { "role": "system", "content": "You are a careful assistant. Use tool data." }, { "role": "user", "content": "Summarize sales and top SKUs." }, { "role": "assistant", "content": "", "tool_calls": [ { "id": "call_1", "type": "function", "function": { "name": "fetch_data", "arguments": "{}" } } ] }, { "role": "tool", "tool_call_id": "call_1", "content": "{\n \"query\": \"sales last week\",\n \"total\": 40,\n \"results\": [\n {\n \"id\": 0,\n \"sku\": \"SKU-0000\",\n \"name\": \"Product 0\",\n \"category\": \"Electronics\",\n \"price\": 10.0,\n \"stock\": 100,\n \"tags\": [\"tag-0\", \"tag-1\", \"tag-2\", \"tag-3\", \"tag-4\", \"tag-5\", \"tag-6\", \"tag-7\"],\n \"description\": \"High quality product with durable materials, fast shipping, and a 30-day return policy. High quality product with durable materials, fast shipping, and a 30-day return policy. High quality product with durable materials, fast shipping, and a 30-day return policy. \",\n \"metrics\": {\"views\": 0, \"clicks\": 0, \"conversion\": 0.0}\n },\n {\n \"id\": 1,\n \"sku\": \"SKU-0001\",\n \"name\": \"Product 1\",\n ... 38 more almost-identical objects ...\n }\n ]\n}" } ] After (what Headroom actually returned) Notice: system / user / assistant tool_call stay the same only the tool content changes pretty JSON becomes a compact table-like string repeated long description text becomes a CCR pointer like <<ccr:2facef870b78,string,264B>> [ { "role": "system", "content": "You are a careful assistant. Use tool data." }, { "role": "user", "content": "Summarize sales and top SKUs." }, { "role": "assistant", "content": "", "tool_calls": [ { "id": "call_1", "type": "function", "function": { "name": "fetch_data", "arguments": "{}" } } ] }, { "role": "tool", "tool_call_id": "call_1", "content": "{\"query\":\"sales last week\",\"total\":40,\"results\":\"[40]{category:string,description:string,id:int,metrics.views:int,metrics.clicks:int,metrics.conversion:float,name:string,price:float,sku:string,stock:int,tags:json}\\nElectronics,<<ccr:2facef870b78,string,264B>>,0,0,0,0.0,Product 0,10.0,SKU-0000,100,[tags...]\\nApparel,<<ccr:2facef870b78,string,264B>>,1,12,3,0.01,Product 1,11.37,SKU-0001,99,[tags...]\\nHome,<<ccr:2facef870b78,string,264B>>,2,24,6,0.02,Product 2,12.74,SKU-0002,98,[tags...]\\n...\"}" } ] How to read the after format in plain words: [40]{category:string,...} means "there are 40 rows; here are the column types" each next line is one row as CSV-ish values <<ccr:...>> means "full description text is stored elsewhere; ask to retrieve if needed" So the model still sees SKU, price, stock, metrics. It just does not re-read the same marketing paragraph 40 times. Wait, is [80]{level:string,...} a bug? No. That string is the real compressed output. It looks broken the first time you see it, but it is Headroom's default csv-schema format (SmartCrusher's lossless compaction renderer). What you are seeing is not corrupted JSON. Headroom deliberately rewrote a fat array of objects into: one shape declaration dense CSV-like rows optional CCR hash markers for anything it parked off-prompt Decode the declaration line From my JSON-logs experiment, the tool content became: {"logs":"[80]{level:string,meta.region:string,meta.attempt:int,msg:string,request_id:string,service:string,ts:string} INFO,us-east-1,0,ok path=/api/items/0,req-00000,checkout,2026-07-16T14:30:00Z ... FATAL,...,payment-service DB pool exhausted...,req-00067,... ..."} Why bother? Pretty JSON repeats "level":, "service":, braces, quotes, and indentation on every object. Models already understand CSV. Paying those field names 80 times is waste. Headroom also supports other renderers (json, markdown-kv), but csv-schema is the default because it is the token-cheapest of the lossless formats. Decode the hashes (CCR) You will also see markers like: <<ccr:2facef870b78,string,264B>> and footers like: [301 lines compressed to 7. Retrieve more: hash=1d4fbe6309448a101b916c1e] or LangChain-style sentinels: {"_ccr_dropped": "<<ccr:baf8f8ba3c6b 27_rows_offloaded>>"} These are not garbage . They are CCR : Compress, Cache, Retrieve. Compress: shrink what the model sees now Cache: store the original blob locally, keyed by that hash Retrieve: if the model needs the missing detail, it calls a tool Headroom injects: { "name": "headroom_retrieve", "parameters": { "hash": "2facef870b78", "query": "optional BM25 search inside the cached blob" } } In proxy / wrapped-client mode, Headroom intercepts that tool call, returns the original (about 1ms from local cache), and the turn continues. Your app often never sees the retrieve hop. How it gets used in practice: So the hash is a pointer , not the content. The compressed prompt stays small; the truth stays available. Important: experiment scripts measure compression only (mocked LLM answers). They prove the markers appear and tokens drop. A live model actually calling headroom_retrieve is the next step when you wire Headroom's proxy or CCR-enabled client, not something which I have tried. Example B — Plain logs (needle in a haystack) Tokens: 4,527 to 160 ( 96.47% saved ) Before (preview) 2026-07-16 INFO ok 0 xxxxxxxxxxxxxxxxxxxx 2026-07-16 INFO ok 1 xxxxxxxxxxxxxxxxxxxx 2026-07-16 INFO ok 2 xxxxxxxxxxxxxxxxxxxx 2026-07-16 INFO ok 3 xxxxxxxxxxxxxxxxxxxx ... hundreds more INFO lines ... 2026-07-16 FATAL payment DB timeout pool exhausted replica_lag_ms=8421 ... more INFO lines ... After (full output, this is the entire compressed message) 2026-07-16 INFO ok 77 xxxxxxxxxxxxxxxxxxxx 2026-07-16 INFO ok 78 xxxxxxxxxxxxxxxxxxxx 2026-07-16 INFO ok 79 xxxxxxxxxxxxxxxxxxxx 2026-07-16 FATAL payment DB timeout pool exhausted replica_lag_ms=8421 2026-07-16 INFO ok 80 xxxxxxxxxxxxxxxxxxxx 2026-07-16 INFO ok 81 xxxxxxxxxxxxxxxxxxxx 2026-07-16 INFO ok 82 xxxxxxxxxxxxxxxxxxxx [294 lines omitted: 1 ERROR, 300 INFO] [301 lines compressed to 7. Retrieve more: hash=1d4fbe6309448a101b916c1e] This is the clearest “wow” example. The FATAL line stays. Most INFO noise becomes one summary footer plus a retrieve hash (hash=1d4fbe63...). That hash is the CCR cache key: keep the tiny window in-prompt; fetch the full 301 lines only if the model asks. Example C — JSON logs inside a tool message Tokens: 5,658 to 3,126 ( 44.75% saved ) Before (pretty objects) { "logs": [ { "ts": "2026-07-16T14:30:00Z", "level": "INFO", "service": "checkout", "request_id": "req-00000", "msg": "ok path=/api/items/0", "meta": {"region": "us-east-1", "attempt": 0} }, { "ts": "2026-07-16T14:30:07Z", "level": "FATAL", "service": "checkout", "request_id": "req-00067", "msg": "payment-service DB pool exhausted timeout replica_lag_ms=8421", "meta": {"region": "us-east-1", "attempt": 1} } ] } After (schema + dense rows; FATAL still visible) This is the string that looks “wrong” in the text until you know the grammar. It is correct, see the decoder section above. {"logs":"[80]{level:string,meta.region:string,meta.attempt:int,msg:string,request_id:string,service:string,ts:string} INFO,us-east-1,0,ok path=/api/items/0,req-00000,checkout,2026-07-16T14:30:00Z INFO,us-east-1,1,ok path=/api/items/1,req-00001,checkout,2026-07-16T14:30:01Z ... FATAL,us-east-1,1,payment-service DB pool exhausted timeout replica_lag_ms=8421,req-00067,checkout,2026-07-16T14:30:07Z INFO,us-east-1,2,ok path=/api/items/68,req-00068,checkout,2026-07-16T14:30:08Z ..."} Same story: keep the signal, drop repeated JSON punctuation and field names. The model still sees level, msg, request_id, just once in the header, then as CSV cells. Example D — LangChain tool wrapper crush (compress_tool_result_with_metrics) Tokens: 10,001 to 3,297 ( 67.03% saved ) Items: 50 to 24 kept in the visible JSON, plus a CCR drop marker Before (start of payload) { "query": "sales last week", "total": 50, "results": [ { "id": 0, "sku": "SKU-0000", "name": "Product 0", "category": "Electronics", "price": 10.0, "stock": 100, "tags": ["tag-0", "tag-1", "tag-2", "tag-3", "tag-4", "tag-5", "tag-6", "tag-7"], "description": "High quality product with durable materials, fast shipping, and a 30-day return policy. ...", "metrics": {"views": 0, "clicks": 0, "conversion": 0.0} } // ... 49 more rows ... ] } After (kept rows + offload marker) { "query": "sales last week", "total": 50, "results": [ {"id": 0, "sku": "SKU-0000", "name": "Product 0", "category": "Electronics", "price": 10.0, "...": "..."}, {"id": 1, "sku": "SKU-0001", "name": "Product 1", "category": "Apparel", "price": 11.37, "...": "..."}, {"id": 12, "sku": "SKU-0012", "name": "Product 12", "...": "..."}, {"id": 40, "sku": "SKU-0040", "name": "Product 40", "...": "..."}, {"id": 49, "sku": "SKU-0049", "name": "Product 49", "...": "..."}, {"_ccr_dropped": "<<ccr:baf8f8ba3c6b 27_rows_offloaded>>"} ] } This is the LangChain-agent friendly shape: still valid-ish JSON, fewer rows, and an explicit note that 27 rows were offloaded to CCR. One-sentence takeaway Headroom does not rewrite your system prompt or delete the tool call. It shrinks the tool result body (and similar fat blocks), usually into csv-schema ([N]{cols} + rows) and/or CCR hash pointers, or keeps errors in logs and summarizes the rest. Weird-looking does not mean wrong; those hashes are how the model can pull originals back. Part 1: The core API, compress() This is the simplest path. from headroom import compress import json rows = [{"id": i, "name": f"Product {i}", "desc": "quality item " * 20} for i in range(100)] payload = json.dumps({"results": rows}) messages = [ {"role": "system", "content": "You are a careful assistant."}, {"role": "user", "content": "Summarize sales and top SKUs."}, { "role": "assistant", "content": "", "tool_calls": [{ "id": "call_1", "type": "function", "function": {"name": "fetch_data", "arguments": "{}"}, }], }, {"role": "tool", "tool_call_id": "call_1", "content": payload}, ] result = compress(messages, model="gpt-4o") print(result.tokens_before, "->", result.tokens_after) print("saved %", round(100 * result.compression_ratio, 2)) print(result.transforms_applied) My real result for big sales JSON Before: 20,041 tokens After: 9,573 tokens Saved: 10,468 tokens ( 52.23% ) Transforms: router:protected:user_message, router:mixed:0.01 Runtime: about 1.08 seconds for that case So: more than half the tokens gone, and the user question stayed protected. Part 2: Where Headroom performed best 1) JSON tool outputs Big arrays of similar objects are Headroom’s happy place. SmartCrusher keeps a smaller set of rows / signal and offloads the rest (CCR style). User-message JSON (forcing user compression): result = compress( [{"role": "user", "content": "Please analyze:\n" + big_json}], model="gpt-4o", compress_user_messages=True, target_ratio=0.3, ) My result: 15,995 to 7,627 tokens 52.32% saved Practical takeaway: turn optimization on for fat JSON tool traffic. 2) Plain logs when the log router wakes up result = compress( [{"role": "user", "content": plain_logs}], model="gpt-4o", compress_user_messages=True, protect_recent=0, # important in my run target_ratio=0.2, ) My result: 4,527 to 160 tokens 96.47% saved Transform: router:log:0.04 That is the “needle in a haystack” story: keep the FATAL line, drop endless INFO noise. 3) Logs stored as JSON arrays If your logging tool returns JSON, SmartCrusher helps a lot even when plain-text log detection fails. My result: 10,558 to 5,786 tokens 45.20% saved 4) Multi-turn agent history (savings stack) I simulated 3 tool turns. LLM replies were mocked. Compression was real each turn. Cumulative tokens saved across turns: 20,945 Mocked answers still “made sense” for the demo (sales summary, FATAL DB timeout advice, create_order explanation). That does not prove answer quality on a live model, only that the pipeline runs end-to-end. Part 3: Where it did not help (also real) These are as important as the wins. Recent code stayed protected (0% saved) code_search_default: 1402 -> 1402 (0.0%) transforms: ['router:protected:recent_code'] Headroom often refuses to crush fresh code on purpose. That is safer for coding agents, but it means “code dump in context” is not always a token win. Tiny JSON (0% saved) small_json: 56 -> 56 (0.0%) No point compressing 20 characters. Some “realistic” structured text logs (0% saved) My pretty timestamps + service= + request_id= logs on the default tool path: 8261 -> 8261 (0.0%) transforms: ['router:protected:user_message'] Same family of content as the 96% log win, but detection/settings differed. On Windows, Headroom also warned it used a pure-Python content detector by default. Lesson: measure your real payloads. Do not assume every log file gets 90% off. Plain repetitive English text on default tool path (0% saved) 2046 -> 2046 (0.0%) Default live-zone / protection rules mattered more than “this text looks repetitive to a human.” Part 4: LangChain integration (all the common cases) Case A: Wrap a chat model from langchain_core.language_models.chat_models import BaseChatModel from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage from langchain_core.outputs import ChatGeneration, ChatResult from headroom.integrations import HeadroomChatModel from headroom import compress class MockChatModel(BaseChatModel): model_name: str = "mock-gpt-4o" @property def _llm_type(self): return "mock-chat" def _generate(self, messages, stop=None, run_manager=None, **kwargs): text = "Total revenue is led by Electronics and Apparel." return ChatResult(generations=[ChatGeneration(message=AIMessage(content=text))]) llm = HeadroomChatModel(MockChatModel()) # use llm.invoke(messages) as usual Measured compression for the same sales tool context: 12,045 to 5,777 tokens 52.04% saved Mocked reply: “Total revenue is led by Electronics and Apparel. Top SKUs look like SKU-0001 style items from the compressed rows.” So LangChain wrapping + Headroom compression is a clean fit for tool-heavy chats. Case B: Wrap tools (agent sweet spot) I used classic LangChain Tool objects with HeadroomToolWrapper (more reliable in my run than @tool StructuredTool wrapping). from langchain_core.tools import Tool from headroom.integrations import HeadroomToolWrapper, compress_tool_result_with_metrics search_tool = Tool( name="search_database", func=lambda query: big_json_string, description="Search sales DB", ) wrapped = HeadroomToolWrapper(search_tool, min_chars_to_compress=500) out = wrapped("sales last week") Database tool (real metrics): Tool metrics collector after 2 calls: total invocations: 2 total compressions: 1 total chars saved: 49,356 search_database compressed; plain fetch_logs text did not JSON logs via compress_tool_result_with_metrics: 8,411 to 2,276 tokens 72.94% saved items 120 to 41 Mocked agent line used in the demo: “I used the compressed tool outputs. Sales look healthy; checkout FATAL is a DB pool timeout.” Case C: Memory history wrapper from langchain_community.chat_message_histories import ChatMessageHistory from headroom.integrations import HeadroomChatMessageHistory base = ChatMessageHistory() history = HeadroomChatMessageHistory( base, compress_threshold_tokens=800, keep_recent_turns=2, model="gpt-4o", ) My run after 12 bulky turns: messages in history: 24 total chars: 16,952 compression_count: 0 total_tokens_saved: 0 So the API is there, but my synthetic “context-data-N” spam did not trigger compression the way JSON tools did. Another reminder: wire it up, then measure . Case D: RAG / document compressor from headroom.integrations import HeadroomDocumentCompressor compressor = HeadroomDocumentCompressor( max_documents=4, min_relevance=0.1, prefer_diverse=True, ) kept = compressor.compress_documents(docs, query) Query: “What is Python used for in AI and web development?” It dropped cooking, sports, gardening, travel, which is exactly what you want for this query. How the pipeline thinks (simple picture) Your agent / LangChain app | | tool JSON, logs, RAG chunks, files v Headroom router |-- JSON -> SmartCrusher |-- logs -> Log compressor |-- code -> code-aware path (often protected if "recent") |-- text -> other compressors / no-op / protect rules v Smaller messages (+ CCR retrieve option) v LLM provider (in this article: mocked) Two ideas that showed up in my transforms list: Protection (router:protected:user_message, router:protected:recent_code): keep some content untouched Crushing (router:smart_crusher, router:log, router:mixed): shrink the rest That mix is why results vary by content type. Pros Huge wins on fat JSON tools: I saw about 52% on full chat compress and about 78% on direct tool crush. Extreme wins on compressible logs: 96.47% in the best log case. LangChain-native pieces: chat model, tools, memory, retriever compressor. Works without changing your whole stack: compress(messages) is enough to start. Local / open approach: you can test offline with mocked LLMs (like I did). CCR mindset: compression can offload rows instead of forever deleting truth. Multi-turn stacking: my 3-turn sim saved 20,945 tokens across compress calls. RAG filtering helps quality and cost: 12 docs to 4 relevant ones, 67% fewer tokens. Cons / challenges Not every payload compresses. Code and tiny messages often save 0%. Detection is picky. One log format got 96%; a more “realistic” format got 0% on the default path. Defaults protect recent content. That is good for safety, bad if you expected automatic crushing everywhere. You may need flags like compress_user_messages=True or protect_recent=0. Memory wrapper did not save tokens in my synthetic chat. Do not assume “I wrapped it” equals savings. Compression takes time. My big JSON case took about 1.08s. Usually cheaper than paying for 10k extra tokens, but not free. tiktoken / vocab loading can stall the first time; Headroom may fall back to estimates unless cache is warm. Where Headroom is the best fit Use it when your LLM bill is driven by inputs that are huge and repetitive : Coding / ops agents with big tool returns SQL / search tools returning hundreds of rows JSON APIs, Elasticsearch-like dumps, inventory feeds SRE assistants reading long logs (especially if log routing triggers) RAG pipelines that retrieve too many chunks Multi-tool LangGraph / LangChain agents where tool spam fills the context every turn Skip or be careful when: Prompts are already small You need every code token untouched (and protection already keeps them) Legal/medical text where you must validate compression with evals first You cannot accept any risk of dropping a rare but important row (unless you use retrieve/CCR well) Simple decision guide Is the expensive part TOOL OUTPUT or RAG DOCS? | |-- JSON arrays / DB rows -----> YES, start here (I saw 50-78% saves) |-- JSON logs -----------------> YES (I saw ~45-73%) |-- Plain logs ----------------> YES, but tune flags; measure twice |-- Recent source code --------> Maybe little/no save (protected) |-- Tiny messages -------------> No Full LangChain “agent-shaped” pattern I recommend from langchain_core.tools import Tool from headroom.integrations import HeadroomChatModel, HeadroomToolWrapper from headroom import compress # 1) wrap model (use your real ChatOpenAI in production) llm = HeadroomChatModel(your_chat_model) # 2) wrap fat tools db_tool = Tool(name="search_database", func=search_db, description="DB search") wrapped_tools = [HeadroomToolWrapper(db_tool, min_chars_to_compress=1000)] # 3) optional: compress message lists yourself between graph nodes result = compress(messages, model="gpt-4o") messages = result.messages print("saved", result.tokens_saved) In production, replace MockChatModel with ChatOpenAI / ChatAnthropic. Keep the same wrappers. Cost intuition (using my numbers, not a vendor invoice) Suppose input costs about $2.50 per 1M tokens (example price only). Saving 10,468 tokens on one fat JSON call is about $0.026 once Saving 20,945 tokens over 3 agent turns is about $0.052 once That looks small, until the agent runs thousands of times a day. Then it becomes rent money. Latency side: my compress calls were often 30ms to 270ms , with the biggest JSON near 1s . Still usually worth it versus shipping 2x tokens to a frontier model. Final verdict Headroom is a strong token optimization library for LangChain apps that drown in tool JSON and retrieved docs. In my local run with headroom-ai 0.31.0 : best everyday win: JSON tools (about 52–78%) best peak win: plain logs with log routing (about 96%) best product pattern: wrap tools + compress between agent turns biggest caveat: protection + detection mean some inputs save 0% If your agent’s context is mostly clean chat text, Headroom will not wow you. If your agent’s context is mostly tool sludge , it can pay for itself quickly. Repro notes All numbers in this article were produced by local scripts that: Build mocked tool payloads Run real compress() / LangChain Headroom wrappers Use MockChatModel for answers Write results.json (aggregate metrics) and before_after.json (actual prompt text before/after) Library version recorded: 0.31.0 . LLM mode: fully_mocked . If you want to show stakeholders the “look,” open before_after.json and compare before_messages vs after_messages for json_tool_chat, plus plain_logs.after_full. Further reading GitHub: https://github.com/headroomlabs-ai/headroom Docs: https://headroomlabs-ai.github.io/headroom/ LangChain guide: https://headroomlabs-ai.github.io/headroom/langchain/ I Tried To Cut My LangChain Agent Tokens With Headroom — Here Is What Actually Happened was originally published in Towards AI on Medium, where people are continuing the conversation by highlighting and responding to this story.