设计原则
agent 读到的每一个 token 都是要计费的输入。CtxCtl 的存在是因为两大上下文污染源——整文件 倾倒和原始命令日志——都可以避免。本页解释这个工具背后的设计决策:压缩究竟保留了什么, 以及节省在哪里会自然趋缓。
上下文按 token 计费,体量就是预算
Section titled “上下文按 token 计费,体量就是预算”编程 agent 会话的 token 大多花在重复阅读上:为找一个函数读整个文件,为找一条错误翻完整份 构建日志。基准测试 背后的脚本化 agent 实验中,仅使用内置 read + bash 工具的 base 组,每个会话平均烧掉 26,284 个未缓存输入 token——而且这还是在 provider 前缀缓存 命中率已经高达 81.7% 的基础上。
经济账毫不留情:
- 输入 token 在它出现的每一轮都被计费,而不是只算一次。
- 大上下文还会拉低回答质量——干草堆越大,针越难找。
- provider 的提示词缓存只折扣重复且稳定的前缀,前提是前缀保持不变。
CtxCtl 直接打击前两点,并为第三点做了工程设计。
切片,而非摘要
Section titled “切片,而非摘要”ctxctl 读取文件的这一半是一个 tree-sitter 符号引擎,铁律只有一条:用 AST 定位,从原始 源码字节切片。
这条流水线刻意保持简单且无状态:
- 用 tree-sitter 把文件解析成 AST(解析后即丢弃,不缓存任何东西)。
- 抽取符号——名称、种类、行区间、规范化签名——形成大纲(outline)。
- 把请求的符号解析到它的字节区间。
- 返回该区间的逐字节原始源码。
没有任何改写、转述或摘要。切片原样保留注释、格式和缩进,因此它仍然是合法源码:你的 agent 可以直接引用它、重新解析它,或与后续编辑做 diff,中间不需要任何翻译步骤。
ctxctl outline src/server.rsctxctl symbol src/server.rs --name handle_request# src/server.rs [12 symbols, ~2.1 KB -> ~410 tokens, saved ~80%] fn handle_request L:42-58 pub async fn handle_request(&self, id: u64) struct RequestHandler L:12-40 pub struct RequestHandler { const MAX_RETRIES L:60 const MAX_RETRIES: u32 = 3;# handle_request src/server.rs:42-58 (58 tokens, saved ~85%)pub async fn handle_request(&self, id: u64) -> Result<String, Error> { let row = self.db.get(id).await?; Ok(row.to_string())}大纲给出地图(此例中比原文件小约 80%);切片只给你点名要的那块地盘。当连完整函数体都嫌多时,
--compact 返回 AST 剪枝视图——签名加一条 // ... [N lines omitted] 折叠标记——
--signature 则只返回声明行。工作流见探索陌生代码。
exec 压缩流水线
Section titled “exec 压缩流水线”命令输出是另一条消防水管。ctxctl exec 包裹一条命令,把它的输出送进一条固定的压缩流水线:
- 保留关键行:匹配配置模式的行——默认
error、warning、failed、panic、fatal(大小写不敏感)。诊断位置行(--> src/foo.rs:12:34,rustc/cargo 风格)被隐式保留, 因此被保留的错误头信息始终带着file:line上下文。 - 保留头部与尾部:开头和结尾各若干行(默认各 5 行),保住命令回显和最终结论/摘要。
- 折叠中段:压成一行
... [N lines omitted]标记,但只在超过折叠阈值后触发 (默认 20 行)。 - 压缩失效时警告:如果折叠执行了却只省下不到 10%——通常是
--keep模式过宽—— 会追加一条确定性警告行。
ctxctl exec "cargo build"$ cargo builderror[E0308]: mismatched types --> src/main.rs:12... [34 lines omitted]warning: unused variable: `x` --> src/server.rs:88 = note: 2 warnings emittedSaved ~70% (1,240 -> 372 tokens)压缩随子进程产出增量流式进行,内存始终以头尾窗口加保留匹配为上界——输出几个 GB 的命令也 撑不爆内存。被包裹命令的退出码原样透传。调优细节见压缩命令输出。
字节稳定喂养提示词缓存
Section titled “字节稳定喂养提示词缓存”省一次 token 只是及格线。CtxCtl 的核心契约是输出字节稳定:正文绝不包含时间戳、计数器、 随机值、机器相关路径或 PID。相同输入 + 相同配置 → 字节级一致的输出。
这个性质与 provider 提示词缓存相乘——Anthropic 缓存命中约有 ~90% 折扣,OpenAI 约 ~50%。 确定性的工具意味着相同的运行产生相同的前缀,昨天的缓存 token 今天依然有效。带时间戳、进度 计数器这类易变内容的输出会让缓存失效,悄悄把省下的钱还回去。
连节省统计本身也遵守这条规则:saved N% 是由 cl100k_base BPE 分词器确定性计算的函数——
从不依赖外部测量。
无状态即设计
Section titled “无状态即设计”没有索引、没有缓存、没有守护进程、没有会话状态。每次调用都是解析后丢弃。代价是一些重复 解析,换来的是:
- 零安装、零过期——会话中途被编辑的文件,下一次调用就是新鲜解析。
- 没有任何东西会损坏、需要迁移或在 worktree 与分支间清理。
- 配置文件只保存默认行为偏好,在任何地方都以同一套优先级解析。
ctxctl 收益最小时
Section titled “ctxctl 收益最小时”诚实面对边界比一个漂亮的头条数字更重要。节省幅度与你的 provider 已有的缓存质量成反比:
- 对 provider 缓存激进的模型——deepseek-v4-flash 在基准中相对内置工具削减了 −33% 会话成本——相对成本收益反而缩水,因为基线本身已被折扣打过折;尽管未缓存(全额计费) 输入仍然下降。
- 弱缓存模型的头条赢面更大:solar-pro4 节省了 47% 会话成本。
两种情况下 ctxctl 都减少了被当作新输入计费的部分;这份缩减有多少转化为美元,取决于你 provider 的缓存质量——见基准测试的注意事项。另外两条诚实的边界:
- 基准中工具调用密集的会话大约花了 2× 墙钟时间;你用秒数换美元和缓存压力。
- 微小输入会让账反过来算:小文件上 JSON 信封可能超过文件本身,此时
saved%合理地为 0。