Skip to content

13.6 外置化:把上下文搬出窗口

上下文窗口不是硬盘,是工作台。工作台上只该放正在用的东西。

读完你能做什么:用文件系统、子代理、脚本、笔记四种载体把信息移出上下文窗口,并算清每种外置手段的压缩比与它自身的开销。


一、核心原则

Anthropic 的表述是:好的上下文工程,是找到能达成目标的最小高信号 token 集合。

「最小」的实现路径只有两条:不放进去,或者放进去后再拿出来。前者永远比后者便宜(见 13.3:拿出来的手段都有代价)。

所以外置化的判断顺序是:

text
这段信息现在这一步用得上吗?
├─ 用得上 → 放进上下文
└─ 用不上 → 它在窗口之外能被找到吗?
            ├─ 能 → 留一个指针(文件路径、查询语句、URL)
            └─ 不能 → 先把它变成能被找到的形态,再留指针

「留指针」这件事本身还有额外收益:路径与命名带有语义。tests/test_utils.pysrc/core_logic/test_utils.py 对模型意味着不同的东西。目录层级、命名规范、时间戳都是免费的元数据。


二、四种外置载体

载体存什么取回方式压缩比自身开销
文件系统原始资料、中间产物、索引Read / Grep / Glob取决于读多少近乎零
子代理探索过程与中间结论返回摘要20~50×子代理的启动上下文 + 无缓存首轮
脚本确定性计算与转换stdout高,且确定零(代码不进上下文)
结构化笔记跨压缩、跨会话的状态主动读回写入时的少量 token

三、文件系统:即时检索(just-in-time)

预先把所有可能相关的资料塞进上下文,是检索时代的思路。Agent 时代的做法是:保留轻量标识符,运行时按需加载。

Claude Code 本身就是混合模式的示范:CLAUDE.md 无条件前置加载(少量、高价值、每次都用),而 globgrepheadtail 让它在需要时才把具体内容拉进来。

3.1 用命令行做窗口外的数据处理

处理大文件时,Bash 管道是最省上下文的工具:

bash
# ❌ 把 20MB 日志读进上下文
cat app.log

# ✅ 先看形状
wc -l app.log && head -5 app.log

# ✅ 再定向取
grep -n 'ERROR' app.log | tail -50

# ✅ 大 CSV:先看结构,再聚合,全程不进上下文
head -1 data.csv
awk -F, 'NR>1 {sum[$3] += $5} END {for (k in sum) print k, sum[k]}' data.csv

原则:让计算发生在窗口之外,只把结论带进来。

3.2 建索引,而不是读全文

对于反复要查的大型资料,先生成一次索引,之后每次只读索引:

bash
# 一次性:为每份文档生成结构化摘要,落盘
for f in docs/*.md; do
  echo "## $f" >> .index/summary.md
  head -20 "$f" | sed 's/^/    /' >> .index/summary.md
  echo "" >> .index/summary.md
done

之后的会话只读 .index/summary.md(几千 token),定位到具体文件后再精读那一份。

详细战术见 03.3 长文档处理战术


四、子代理:最高压缩比的手段

子代理在自己的上下文窗口里工作,可能消耗几万 token 做探索,最终只返回一两千 token 的结论。

4.1 压缩比的量化

一个真实形状的任务:「在 200 个文件里找出哪些实现了缓存失效逻辑」。

做法主会话新增 token相对成本
主会话直接 Grep + 逐个 Read 15 个候选文件≈ 22,000100%
主会话 Grep,只读最像的 3 个≈ 5,00023%
派子代理探索,返回带文件引用的结论≈ 6003%

主会话侧压缩到 1/35。但账不能只算这一边。

4.2 子代理自己的开销

开销项说明
启动上下文自己的系统提示词、工具集,通常还有 CLAUDE.md
首轮无缓存子代理从零建缓存,首次调用没有命中
TTL 更短子代理用 5 分钟 TTL,即使你在订阅计划上
模型单价若不指定,可能沿用主会话的贵模型

所以任务规模必须大于子代理的启动开销,否则是负收益。经验阈值:预计要读超过 5,000 token 的原始资料,才值得派子代理。

4.3 三个降低子代理开销的配置

markdown
---
name: scanner
description: 在大型代码库中快速定位相关文件。用于需要广泛搜索但不需要深度分析的场景。
model: haiku
tools: Glob, Grep, Read
---

你只做定位,不做分析。返回:文件路径 + 匹配行号 + 一句话说明。
不要粘贴大段代码。不要给改进建议。
  1. 指定便宜模型。 扫描定位类任务用 Haiku,成本降到 Opus 的约 1/25。
  2. 收窄工具集。 工具定义也是子代理的常驻成本。
  3. 在提示词里限定返回格式。 子代理返回多少,主会话就付多少。

内置的 Explore 与 Plan 代理会跳过 CLAUDE.md 与 git status,启动上下文更小,适合纯探索。

4.4 什么时候不该用子代理

场景原因
任务需要完整的对话上下文子代理看不到你们之前聊了什么。这种情况用 fork
任务很小(读一两个文件)启动开销大于收益
需要多轮交互式澄清子代理是一次性的
代理团队(agent teams)滥用官方数据:计划模式下的代理团队约消耗标准会话 7 倍 token

五、结构化笔记:跨越压缩边界的状态

压缩和 /clear 会带走对话历史,但带不走磁盘上的文件。所以长任务的状态应该写在文件里,而不是指望它活过压缩。

markdown
# 对账模块重构 · 进度

## 已完成
- [x] 抽出 `ReconcileEngine` 接口
- [x] 迁移支付宝渠道(`src/channels/alipay.ts`

## 进行中
- [ ] 迁移微信渠道 —— 卡在 `WxPayAdapter` 的异步回调签名不兼容

## 关键约定(不要违反)
- 所有金额用 `Decimal`,禁止 `number`
- 渠道适配器不得直接读数据库,必须走 `RepositoryPort`

## 已知坑
- `tests/fixtures/wx.json` 里的 mock 数据是旧格式,改之前先跑 `npm run gen:fixtures`

配套习惯:

text
> 继续之前,先读 PROGRESS.md
> 这一阶段做完后,把结论追加到 PROGRESS.md 再继续

这份文件的三个作用,每一个都独立成立:

  1. 跨压缩持久化:压缩后读一次就恢复状态。
  2. 跨会话持久化/clear 后新会话读一次就接上。
  3. 降低压缩损失:关键约定在文件里有权威版本,摘要丢了也不致命。

Auto memory(MEMORY.md)是这个模式的内置版本,压缩后会从磁盘重新注入。API 侧有对应的 memory 工具,可以和 context editing 联动:上下文接近清除阈值时,模型会收到提示,先把重要信息写进记忆文件,再让旧的工具结果被清掉。


六、把它们串起来:一次长任务的标准形状

text
阶段 0  准备
        └─ 写 PROGRESS.md,列出目标、约束、验收标准

阶段 1  勘察(子代理)
        └─ 派 Haiku 子代理扫描,返回文件清单 + 一句话说明
           主会话新增 ≈ 0.6k

阶段 2  制定方案(主会话,计划模式)
        └─ 只读子代理点名的 3~5 个文件
           把方案写进 PROGRESS.md

阶段 3  执行(主会话)
        ├─ 每完成一个子任务,更新 PROGRESS.md
        ├─ 测试输出经 Hook 过滤,只回失败部分
        └─ 到任务边界时 /compact,并指定保留重点

阶段 4  换任务
        └─ /clear(零成本),下一个任务从 PROGRESS.md 起步

每一步都在做同一件事:让窗口里只有当前这一步需要的东西。


延伸阅读

基于 VitePress 构建 · 内容采用原作者授权