最近一段时间我在深度使用 [[Claude Code]] 做开发工作,用得越多,账单就越触目惊心。一次稍微复杂一点的任务,比如让它读取几个文件、搜索一下代码、跑一下测试,轻轻松松就能烧掉超过 100K 的 Token。更让人无奈的是,这 100K 里面有相当大一部分是重复冗余的内容——工具调用的输出、日志、搜索结果,它们被原封不动地塞进上下文,再一次次地发给模型,而模型其实只需要其中很小一部分关键信息。

直到我发现了 [[Headroom]] 这个工具,才意识到这个问题其实有解。

Headroom AI 上下文压缩示意图

上下文膨胀是 AI 开发成本的隐形黑洞

在聊 Headroom 之前,先说说这个问题为什么值得认真对待。大语言模型的计费方式是按 Token 数量来的,而 Token 数量 = 输入 Token + 输出 Token。在 AI Agent 的典型工作流里,输出其实占比不大,真正的大头是输入——也就是每一轮对话时发给模型的上下文。

每一次工具调用(比如 cat 一个文件、grep 搜索结果、执行 shell 命令),返回的内容都会追加到上下文里。假设你读了一个 500 行的日志文件,这 500 行会原封不动地贡献给接下来每一轮对话的输入 Token。如果后续还有 10 次工具调用,这个文件的内容就被重复计费了 10 次。这就是上下文膨胀的本质:大量历史信息反复出现,而真正需要的只是其中的关键结论。

Headroom 的出发点正是解决这个问题:在内容进入上下文之前,先对它做一次智能压缩,把冗余去掉,只保留模型真正需要的语义信息。按照官方的数据,压缩率可以达到 60%~95%,对于日志、命令行输出这类结构化但冗余度高的内容,效果尤为显著。

在上下文中经常容易出现的冗余

  • 工具调用输出的 JSON 通常带有缩进和换行,压缩后通常可以减小一倍以上
  • 应用日志,时间戳,主机名,日志级别等在每一行都重复出现,并且存在大量重复相似内容
  • RAG Chunks,只是简单地将搜索到的文档连接起来,混杂了大量的无关文本
  • 对话历史,随着每一轮会话线性增长,较早的上下文通常价值不高,但是也不会被剔除

还有一种很容易被忽略的浪费,是工具输出外层的“包装纸”。一次搜索结果真正有价值的可能只是 text 字段里的几行匹配,但它通常会被包在 tool_use_idtypecontent 这类结构字段里。单看一次不算多,但 Agent 一轮任务里可能调用十几次工具,这些几乎不变的包装字段会被模型反复读取、反复计费。

Token 浪费带来的后果也不只是账单变贵。上下文窗口被无关内容塞满之后,模型会更早遗忘最开始的设计约束;请求体越大,模型侧处理时间也越长。换句话说,上下文膨胀同时影响成本、记忆和交互体验。

Headroom 是什么,它如何工作

[[Headroom]] 本质上是一个运行在本地的智能压缩代理层,它坐在 AI 客户端(Claude Code、[[Cursor]]、[[Aider]] 等)和 API 端点之间,对每一条即将发出的请求做上下文压缩。

它的核心逻辑是:识别上下文中哪些内容是”低信息密度”的——比如冗长的日志、重复的工具输出、已经过时的中间状态——然后用语义等价但更精简的形式替换它们。原始内容并不会丢失,而是被存储起来,如果后续需要完整内容,可以通过哈希值检索回来。

Headroom 支持三种核心 MCP 工具(当以 MCP Server 模式接入时):

  • headroom_compress:将任意文本内容压缩存储,返回一个检索哈希。
  • headroom_retrieve:通过哈希取回原始完整内容,支持关键词过滤。
  • headroom_stats:查看本次会话的压缩统计,包括总压缩次数、节省 Token 数、估算费用节省等。

这个设计很聪明——AI 本身参与了压缩决策。在 MCP 模式下,是 AI 主动调用 headroom_compress 来压缩它认为不再需要频繁引用的内容,而不是盲目地截断上下文。这意味着压缩发生在模型理解之后,损失的信息量被控制在最低限度。

Headroom 的核心优势

  • 节省 Token 费用
  • 兼容所有 Coding Agent
  • 完全本地运行
  • 可逆压缩器
  • 无需任何代码修改
  • 可以跨 Agent 共享记忆

核心实现原理:先路由,再压缩,最后可恢复

如果把 Headroom 只理解成一个“自动总结工具”,其实会低估它。它更像一条本地运行的上下文处理流水线:先判断内容类型,再选择不同的 Compressor,压缩后把原文放进本地缓存,最后只把精简后的信息交给模型。官方文档里把这条链路概括成 CacheAligner -> ContentRouter -> IntelligentContext,再配合 CCR,也就是 Compress-Cache-Retrieve,让被压缩的原始内容随时可以被找回来。

CacheAligner 解决的是另一个经常被忽略的问题:提示词缓存。很多模型服务商会对相同的 prompt 前缀做 KV cache 或 prompt cache,但只要系统提示词前面混入了当前日期、session id、临时路径这类动态信息,缓存就很容易失效。Headroom 会尽量把这些动态字段移动到消息尾部,让前缀保持稳定。这样它不仅减少 Token 数量,也尽量提高 Anthropic、OpenAI、Google 等服务商侧缓存命中的概率。

这里还有一个更细的保护机制,可以理解为 PrefixCacheTracker。Claude Code 这类客户端本身也会在消息中放入 cache control 断点,告诉上游模型哪些前缀已经被缓存。如果 Headroom 为了压缩而改写这些已经缓存的内容,反而会让缓存失效,省下的 Token 可能还抵不过重新写入缓存的成本。所以更合理的策略是:已经被 Provider 缓存命中的前缀尽量冻结,只压缩新增的、还没有进入缓存收益区的内容。这样 Headroom 不是和模型厂商的缓存机制打架,而是在缓存之上继续做优化。

ContentRouter 则是压缩质量的关键。不同内容不能用同一种方法处理:JSON 工具输出、源码、构建日志、搜索结果、diff、HTML、普通长文本,它们的信息结构完全不同。Headroom 会先检测内容类型,再把内容交给对应的 Transform。这个设计比单纯按 Token 截断安全得多,因为它知道什么是结构,什么是噪音,什么必须保留。

Compressor 家族如何各司其职

Headroom 的 Compressor 不是单一算法,而是一组面向不同内容的压缩器。理解这一点之后,就能明白为什么它在日志、搜索结果和 JSON 输出上特别有效,而在源码上会更保守。

SmartCrusher 主要处理 JSON 和数组型工具输出。比如一次 API 返回 1000 条记录,传统做法要么全部塞给模型,要么粗暴截断前几十条;SmartCrusher 会做更细的筛选:保留开头和结尾的一部分,保留错误项、异常值、与当前问题相关的记录,以及能代表整体分布的样本。对于调试场景,这一点尤其重要,因为真正有价值的往往不是平均样本,而是 error、warning、outlier 和少数状态变化。

CodeAwareCompressor 面向源码,核心是用 tree-sitter 解析 AST。它不会把代码当作普通文本切碎,而是优先保留 import、函数签名、类定义、类型注解、装饰器、错误处理结构,然后对函数体、注释和冗长 docstring 做压缩。这样压缩后的代码仍然保留“骨架”,模型能看懂模块结构和调用边界,不容易因为半截函数或缺失括号产生误判。对 Python、JavaScript、TypeScript 的支持更完整,对 Go、Rust、Java、C、C++ 这类语言则更偏向函数体级别的压缩。

Kompress 或通用文本压缩器则负责普通文本、日志、搜索结果这类不适合 AST 或 JSON 统计压缩的内容。Headroom 当前的 Kompress-base 是基于 Hugging Face 模型的文本压缩器,会给词或片段打重要性分数,然后保留高信息密度部分。除此之外,搜索结果会保留命中的行、相关文件和文件多样性;日志压缩会保留错误、warning、stack trace、测试摘要和分段标题;diff 压缩会保留变更行和必要上下文。也就是说,它不是简单把每段文本压成摘要,而是在不同数据结构里找“任务相关的信息锚点”。

如果用一个更直观的表格来看,可以这样理解:

Compressor 适合内容 保留重点 典型收益
SmartCrusher JSON 数组、API 返回列表 首尾样本、错误项、异常值、统计摘要
LogCompressor 构建日志、测试日志 ERROR、WARN、stack trace、汇总行 很高
SearchCompressor grep、ripgrep、搜索结果 高相关匹配、文件多样性、关键命中行 很高
DiffCompressor Git diff 变更行和少量必要上下文 中高
CodeAwareCompressor 源代码 import、签名、类型、类和函数骨架 中高
Kompress 普通文本、长文档 高信息密度片段、语义锚点 中高

CCR 让激进压缩变得可接受

很多人第一次听到上下文压缩时会担心:万一压掉了关键信息怎么办?Headroom 的答案是 CCR。它在压缩时把原始内容存到本地缓存里,给压缩后的片段附上一个 hash 或 marker。模型如果发现当前压缩结果不够用,就可以通过 headroom_retrieve 按 hash 取回完整原文,或者带上 query 只检索原文里相关的部分。

这让 Headroom 的压缩策略可以更积极一些。普通压缩一旦删掉内容就真的丢了,所以只能很保守;CCR 把“删除”变成“暂时移出上下文”,用本地缓存兜底。对于 AI Agent 来说,这个区别很大:它平时只背着轻量上下文往前走,遇到需要追溯细节的时候再回头拿原文。

从实现上看,CCR 也解释了为什么 MCP 模式体验更自然。headroom_compress 负责把大块内容压缩并返回可检索的标识,headroom_retrieve 负责按标识或关键词把原文捞回来,headroom_stats 则负责把这套机制的效果量化出来。模型不是被动接受一个被截断的世界,而是多了一个“外部记忆入口”。

Context Manager 负责最后的取舍

即使每一块工具输出都压缩过,长任务跑久了之后,对话历史仍然会越来越大。这时 Headroom 还会做 message 级别的上下文管理。基础策略类似 rolling window:保留系统提示词和最近几轮对话,旧消息按顺序移出;更高级的 IntelligentContext 会给消息打分,考虑最近性、语义相关性、错误信号、后续引用关系、Token 密度等因素,再决定哪些内容可以被移出当前窗口。

这里有一个细节我觉得很实用:工具调用和工具结果会作为成对单元处理,避免只留下 tool call 但没有 tool result,或者只留下结果却找不到来源的孤儿消息。对于 Agent 框架来说,这种结构完整性比压缩率本身还重要,否则省下来的 Token 很可能会换来更难排查的模型幻觉。

Read Lifecycle:过时的文件读取不该继续占位

WebClip 里提到的 ReadLifecycle 是我觉得很值得单独拿出来讲的点。AI 编程助手经常反复读取同一个文件:先读一遍,改完之后又读一遍,后来可能还会再读相关片段。问题是,第一次读取到的内容在文件被修改后就已经过时了;如果后续又读取了同一个文件,旧的读取结果也被新结果覆盖了。可在普通上下文里,这些旧内容仍然占着位置。

Read Lifecycle Manager 的思路是追踪文件的读取和编辑事件。一旦发现某段 Read 输出已经 stale,也就是文件在读取之后被修改过,或者被后续读取 superseded,就可以把原来的几千字符替换成一条很短的标记:

[Read content stale: src/auth.py was modified after this read. Retrieve original: hash=a1b2c3]

这条标记比完整文件内容短得多,但仍然保留了两个关键信息:这段内容为什么不再可靠,以及如果真的需要,应该通过哪个 hash 找回原文。对编程 Agent 来说,这比盲目保留旧文件内容更安全,因为旧内容不仅浪费 Token,还可能误导模型基于过时版本继续推理。

这套实现的边界

当然,Headroom 并不是越激进越好。对于天然信息密度很高的内容,比如安全审计报告、精心编写的设计文档、短而关键的配置文件,压缩收益可能有限,甚至不应该压缩。源码压缩也更适合“理解结构”和“定位问题”,如果任务是逐行修改某个函数,最好还是让模型读取完整文件片段。

我更倾向于把 Headroom 看成上下文预算管理器,而不是万能省钱插件。它最擅长处理那些“不得不给模型看,但完整看又很浪费”的内容:长日志、批量搜索结果、RAG 片段、API 返回列表、历史会话,以及重复出现的工具输出。真正的信息密度判断仍然需要和任务结合,MCP 模式之所以更舒服,也正是因为它把一部分压缩决策交还给了模型自己。

我也会给自己留几条安全底线:用户输入不要改,代码原文不要轻易改,错误信息要尽量完整保留,压缩失败就直通原文。压缩工具最怕为了省 Token 破坏因果顺序或者漏掉关键报错,所以 Headroom 这类工具的价值不只是“压得更狠”,而是知道什么时候不该压。

安装

通过 Python pip 安装

# 安装完整版本(包含所有功能)
pip install "headroom-ai[all]"

# 验证安装
headroom --version

all 中包含了 proxy 代理服务器模式,memory 跨 Agent 共享记忆,ml 压缩模型,code 代码压缩,mcp 模块集成 MCP 服务器

安装完成之后,相关的配置文件在 ~/.headroom

数据库文件

  • memory.db — 主记忆存储
  • memory_graph.db — 知识图谱
  • memory_vectors.db — 向量索引

四种接入方式,灵活适配不同场景

Headroom 提供了四种不同颗粒度的接入方式,可以根据自己的使用习惯来选择。

CLI 包装模式

最简单的方式,用 headroom wrap 包装原有的 Agent 启动命令:

headroom wrap claude

这样启动的 Claude Code 会自动经过 Headroom 的压缩层,完全透明,不需要修改任何配置。适合快速体验,或者不想动现有配置的用户。

后台任务

如果你想 Headroom 一直在后台运行,可以执行如下的命令,创建永久的后台服务

headroom install apply --preset persistent-service

MCP Server 模式

将 Headroom 作为 MCP Server 接入 Claude Code:

headroom mcp install

安装后,[[Claude Code]] 就可以直接调用 headroom_compressheadroom_retrieveheadroom_stats 这三个工具。这种模式的优势是 AI 可以主动决定哪些内容需要压缩,而不是被动地让代理层统一处理,理论上压缩精准度更高。我目前在用的就是这个模式,感受是对大文件、长日志的效果特别好。

反向代理模式

这是对现有工具链侵入性最小的方式,适合 Cursor、Aider 这类不原生支持 MCP 的工具:

headroom proxy --port 8787

启动后,只需要把 AI 工具的 API Base URL 指向 localhost:8787,Headroom 就会自动拦截所有请求并压缩上下文。同时,localhost:8787/dashboard 提供了一个实时数据面板,可以直观地看到节省了多少 Token 和费用。

这个 Dashboard 不只是展示一个总节省率,它更适合用来判断压缩是否健康。比如你可以看每个请求压缩前后的 Token 对比、缓存命中率、各个 Compressor 的调用分布、流水线耗时,以及 CCR 被检索的次数。如果 headroom_retrieve 被频繁调用,说明模型经常觉得压缩后的内容不够用,可能需要调低压缩强度或者切换策略;如果缓存命中率很低,就要回头检查系统提示词或工具列表是否一直在抖动。

如果你已经用了自定义的 API 转发地址(比如套了一层中转),需要额外配置目标地址:

export ANTHROPIC_TARGET_API_URL="https://your-custom-endpoint.com/anthropic"
export ANTHROPIC_API_KEY="sk-xxxx"

Headroom 会把 ANTHROPIC_BASE_URL 自动设置为本地端口,再把压缩后的请求转发到 ANTHROPIC_TARGET_API_URL 指定的地址。层级关系是:Claude Code → Headroom 本地代理(压缩)→ 你的自定义 API 端点。

Python 代码集成

如果你在自己写 AI 应用,也可以直接在代码里调用:

from headroom import compress

compressed = compress(messages)

这种方式适合有自研 Agent 框架、需要在代码层面精细控制压缩时机的场景。

headroom 命令使用

headroom 也提供了几个查看统计数据的命令

# 查看统计数据,默认 7 天
headroom perf
# 查看最近 24 小时数据
headroom perf --hours 24
# 导出 CSV 格式数据
headroom perf --format csv --hours 24 > stats.csv
# 更新 headroom
headroom update
# 在浏览器打开 Dashboard
headroom dashboard
# 检查 headroom 配置
headroom doctor
# 查看记忆数据
headroom memory stats
# 列出所有记忆
headroom memory list
# 启动代理服务器
headroom proxy --port 8787 7 天的

进阶配置和实践建议

从 WebClip 的经验看,Headroom 可以粗略分成两种使用思路:一种是 Token 优先,尽量压缩工具输出和历史上下文;另一种是 cache 优先,更强调保持前缀稳定,尽可能吃到 Provider 的缓存折扣。个人日常开发大多可以先用默认的 Token 优先策略,等到长时间重构、连续多轮对话特别多的时候,再考虑切换到更偏缓存的模式。

我会重点观察三个指标。第一是总体 Token 节省率,它决定这个代理是否值得常驻;第二是缓存命中率,它反映 CacheAligner 和前缀保护是否生效;第三是 CCR 检索次数,它反映压缩是否过度。理想状态下,Token 明显下降,缓存命中稳定,而 CCR 检索次数不高,说明模型大部分时候能直接用压缩后的上下文完成任务。

另外,短消息没有必要压缩。少于几百 Token 的内容,本来就没有太多冗余,压缩带来的收益可能还抵不过额外处理成本。源码压缩也应该谨慎开启:如果只是理解模块结构,CodeAwareCompressor 很合适;如果要逐行修改关键函数,还是让模型读取完整局部代码更稳。

实际使用中的几点体会

用了一段时间之后,有几点感受想分享一下。

首先是 MCP 模式确实比代理模式”聪明”一些。代理模式是无差别压缩,它不知道哪些内容对当前任务更重要;而 MCP 模式下,AI 自己会判断什么时候压缩、压缩哪些内容。我遇到过代理模式把某个重要的报错信息压缩掉的情况,而 MCP 模式几乎没有出现过这个问题。

其次是对于日志类内容,压缩效果非常显著。一个几百行的构建日志,真正有用的往往只是最后几行的报错信息,Headroom 能很好地识别这种模式。但对于代码文件,效果相对有限,因为代码本身的信息密度就很高,压缩空间不大。

另外值得注意的是,headroom_retrieve 这个检索功能很实用。当你需要追溯之前被压缩的内容时,可以通过哈希找回原始数据。在长任务中,这个功能防止了”信息被压缩后就丢失”的焦虑感。

最后

Headroom 解决的是一个真实存在且被很多人忽视的问题:AI Agent 的上下文膨胀。它不是在算法层面提升模型能力,而是在工程层面降低使用成本,这个切入点很务实。对于每天重度使用 [[Claude Code]] 或其他 AI 编程工具的开发者来说,接入 Headroom 带来的费用节省是实打实的。

从更长远的角度看,随着 AI Agent 越来越长地运行复杂任务,上下文管理会成为一个越来越重要的工程课题。Headroom 目前的四种接入方式已经覆盖了大多数主流场景,而 MCP 模式和 AI 主动参与压缩的设计思路,可能也会成为未来这个方向的标准范式。如果你还没有关注过 Token 消耗这个问题,不妨用 headroom_stats 跑一次,看看数字,可能会让你大吃一惊。

reference