编码代理的上下文管理分为两个问题。本文涵盖第一个：在单次运行中，什么填满了窗口，为什么其中大部分被浪费在代码读取上，以及基于相关性的剪枝如何改变了经济性。

本文是《黑暗工厂日记》系列的一部分，该系列记录了构建 Arianna——一个完全自主的软件开发工厂——的过程。

上下文管理是控制什么进入编码代理的工作记忆、什么保留、什么应该被移除或存储到别处的问题。在 Arianna 中，我把这看作两个独立的问题：

- **单次运行内部**：代理读取文件、执行命令，并在其上下文窗口中积累状态。填满窗口的大部分内容是代码读取，而非推理。问题是：如何让工作集与当前任务保持相关。

- **跨多次运行**：即使有完美的读取优化，足够长的运行仍会积累过时的决策、被放弃的计划和过期的状态。解决方案是：将工作拆分为每个任务使用一个全新的上下文窗口，在迭代之间将追踪信息持久化到磁盘。

本文涵盖第一个问题。下一篇日记将涵盖第二个。

## 读取问题

编码代理的上下文窗口是其单次运行的工作记忆。代理读取、搜索、检查和推理的所有内容都进入那个窗口并留在那里。在运行过程中，填满窗口的大部分内容不是推理。而是代理读过一次就不再需要的代码。

SWE-Pruner 论文在多个代理编码基准上测量了这一点。读取类操作（打开文件、搜索、输出内容）消耗了大约67-76%的所有token，具体取决于模型和配置。其余部分在推理、规划和工具调用之间分配。

73% 代码读取 | 15% 推理与规划 | 12% 工具调用与输出

这个比例之所以重要，是因为上下文窗口空间是有限的。在200K窗口的 Sonnet 上，它很快就会被填满。即使在3月14日正式上线、拥有1M token的 Opus 4.6 上，问题也没有消失。只是需要更长时间才会显现。而在实践中，大部分代理工作并不运行在最昂贵的模型上。我在 Arianna 中使用 Sonnet 和其他更便宜的 worker 来完成大部分任务。上下文纪律必须在整个机队中有效，而不仅仅是在空间最充裕的模型上。

## 基于相关性的代码读取

当人类打开一个大型代码文件时（对于那些还这样做的人来说），他们不会以同等权重阅读每一行。他们扫描名称，跳转到看起来可疑的分支，忽略显然暂时无关的辅助函数，然后快速缩小范围。相关性过滤几乎在瞬间完成。

SWE-Pruner 做了类似的事情，但以模型的形式实现。它是一个建立在 Qwen3-Reranker 之上的0.6B参数模型。架构包含三个部分：

- **Token 评分**：模型读取源代码的同时接收一个自然语言查询（比如"认证中间件如何验证token？"）。它对每个token与该查询的相关性进行0到1的评分。

- **CRF 头部**：在评分器之上添加的条件随机场层强制保证结构完整性：如果函数签名得分高，其函数体也会被保留。不会有孤立的括号或半个类定义。论文报告称，剪枝输出的AST正确率为87.3%，而像 LLMLingua-2 这样的token级压缩方法只有0.29%。

- **行级聚合**：token评分被汇总到行级别。低于阈值的行变成 `(filtered N lines)` 占位符，保留行号以便代理仍能引用特定位置。

整个模型小到可以在消费级硬件上运行。0.6B参数，bfloat16，Flash Attention 2。不是前沿模型。而是一个把一件事做好的专用工具。

## 实际效果

从一月中旬起，我就在本地工作站上运行 SWE-Pruner。它驻留在我的 NVIDIA RTX 3060（12GB）上，使用约10GB显存，7×24小时保持加载。我的编码代理的每一次代码读取都经过它。

```
$ nvidia-smi
| NVIDIA GeForce RTX 3060     | 10999MiB / 12288MiB |  35%  Default |
| PID 1464  swe-pruner        |                9820MiB              |
```

这是一个真实的剪枝案例。我对一个81行的 TypeScript 文件（557个token）提出了问题"文章如何按类别排序和过滤？"。剪枝器保留了排序逻辑、类别过滤函数以及为它们提供数据的数据加载代码。它去掉了导入语句、类型声明和无关的导出。557个token减少到450个（减少19%），每一行都与问题相关。

在更大的文件上，缩减幅度更大。一个453行的 Python 部署文件，针对聚焦查询（"限流是如何工作的？"）进行剪枝后，从3,358个token减少到298个（减少91%）。这很激进，但查询范围很窄，文件的大部分内容确实与限流无关。

SWE-Pruner 论文报告称，在 SWE-Bench Verified 基准上实现了23-54%的token缩减，同时保持或略微提高了解决率。在日常使用中，我观察到的范围类似：在小文件和宽泛查询上为15-30%，在大文件和聚焦查询上更高。代理看到的是当前任务所需的内容，而不是文件碰巧包含的所有内容。

## 以云推理方式提供服务

如果你有闲置的 GPU，本地推理运行得很好。但并非人人如此。这就是我构建 Winnow 的原因——它将同一个模型包装在具有自动扩缩容能力的云推理服务中。

架构很直接。一个 FastAPI 应用在 CPU 容器上处理 HTTP、认证、限流和计费。GPU 容器（Modal 上的 T4）使用动态批处理运行实际推理：在一个50毫秒的窗口内积累最多8个请求，然后执行一次前向传播。GPU 容器在空闲时缩容至零，在请求到达时启动。对于8K token的文件，典型延迟在100毫秒以内。

流程为：`read_file` → `code + focus question` → `pruned code / filtered lines` → `Claude Code / Cursor / Codex`

后端由 `Winnow MCP Server`（FastAPI on Modal CPU）→ `SWE-Pruner on T4 GPU` 驱动。

Claude Code、Cursor、Codex 和其他编辑器作为客户端连接到 Winnow MCP 服务器（npm 上的 `winnow-mcp`）。当代理读取文件时，MCP 服务器拦截调用，将代码和聚焦问题发送到推理 API，并返回带有 `(filtered N lines)` 占位符的剪枝输出。代理不知道也不关心剪枝发生了。它只是看到了更少的垃圾。

Modal 在这里值得称赞。CPU 和 GPU 容器的分离、批处理原语和缩容至零的行为使得部署变得简单。模型权重也发布在 HuggingFace 上，供任何想要自托管的人使用。

## 为什么这对工厂很重要

SWE-Pruner 论文报告称，在 SWE-Bench Verified 上实现了23-54%的token缩减，同时保持或略微提高了解决率。在 Arianna 中，节省的感觉更大，因为运行中早期步骤的陈旧上下文的成本高于新鲜读取。代理不仅仅在不相关的代码上浪费token。它还必须协调矛盾的状态：已经改变过的旧函数签名、被放弃的先前计划。这种协调税是真实存在的，并且不会出现在每次任务都从头开始的基准测试中。

一个0.6B的模型提升更大模型的有效性能，这值得关注。剪枝器并不像 Claude 或 GPT 那样理解代码。它不需要。它只需要足够好地对相关性进行评分，以便在昂贵的模型需要处理之前移除噪声。小模型作为大模型的过滤器。

上下文窗口会继续增长。Opus 4.6 有1M token。模型的能力现在大约每两周就有一次跃升。但读取问题不是一个大小问题。它是一个相关性问题。即使窗口是无限的，一个对所有内容等权重读取的代理，推理效果也会比选择性读取的代理更差。

这涵盖了 Arianna 中两个上下文管理问题的第一个。优化读取可以在单次运行中保持工作集的整洁。但再多的读取剪枝也解决不了第二个问题：足够长的运行会积累过时的决策、被放弃的计划和过期的状态——剪枝无法移除它们，因为那是代理自己放进去的。这个问题的解决方案是架构层面的，与读取完全无关。那是下一篇日记的内容。
