Codex Agent 在大仓库里失去上下文:6 个原因 + scope 收敛方案

50 万 + 行的项目里 Codex 跑到一半就晕了。修法是先界定 working set、预喂目录摘要、把 AGENTS.md 钉在 prompt 头部。

你把 Codex Agent 指向一个 50 万行的 monorepo,让它”加一个 API endpoint、串通 service + DB、再加个测试”。两分钟后它读了 40 个随机文件、plan 列表被自动 summarize 了两次、现在它在改错的 package——它忘了本来该动的是哪个 app 的路由层。输出看着像能跑又像跑不通,你也说不清它在哪儿走偏的。小仓库 Codex 像魔法;大仓库它就迷路。

根因很少是”模型对大代码就笨”。是 agent 构建的 working set(文件读 + plan + AGENTS 规则 + 推理)超出了上下文窗口,自动 summarizer 把结构锚点丢了。修法是 agent 开跑前先把 working set 界定好:scope 到子树、预喂结构摘要、把相关 AGENTS.md 作为钉死的段落强制加载。

常见原因

按大仓库失忆的命中率排序。

1. 没 scope —— agent 扫整个仓库

你说”找找认证在哪儿接的”。Agent 全 monorepo 跑 ripgrep,4000 个 match,读 50 个随机文件,原任务直接滚出上下文。

如何判断:transcript 里出现了无关 package 的读(编辑 api-server/ 时读 marketing-site/)。

2. Plan 列表被自动 summarize 掉

上下文用过 70% 后 runner 会压缩早期 turn。原本 12 步 plan 变成”用户想加个 endpoint”,agent 忘了 6-12 步。

如何判断:重 prompt”列出剩余 plan”——答案比你写的少,或者比原文更模糊。

3. AGENTS.md / 约定掉出窗口

AGENTS.md 是第 1 turn 加载的,到第 30 turn 已被 summarize 成”follow conventions”。Agent 在没有具体约定的状态下生成。

如何判断:输出违反了 AGENTS.md 明确写过的约定。重 prompt”引用相关 AGENTS.md 规则”——agent 用同义改写或编造,就说明规则不在上下文里了。

4. 冗长 tool 输出灌满上下文

pnpm tsc --noEmit 4000 个类型错误的 dump、含完整文件内容的 30 文件目录列表、1 万行测试日志——每一个一次 turn 就能烧光窗口。

如何判断:某一次 tool 调用占了总 token 的 > 30%。事后 wc -l 跑可疑 tool 的 stdout。

5. Agent 反复读同一个文件

在没有”我已经读过哪些”上下文缓存时,agent 会反复 re-issue 已经读过文件的 read。每次重读都耗窗口、零新信息。

如何判断:transcript 搜重复的 Read <path> 调用。同一个 path 读 3+ 次 = 严重浪费。

6. 跨 package 编辑但没有依赖图

任务动 3 个 package,agent 不知道依赖图。它反复读 package.jsontsconfig.json*.lock 自己摸索——每次摸索都吃窗口。

如何判断:transcript 里有大量来自不同目录的 Read package.json / Read tsconfig.json

动手前先确认

  • 大致摸一下仓库规模:tokeicloc 给基线(代码行数、文件数)。
  • 确认任务实际需要的子树——用一句话写下来。
  • 把当前 AGENTS.md 内容存一份;可能需要瘦身才能 in-context 加载。

需要收集的信息

  • 仓库总文件数和 LoC(find . -type f -name "*.ts" | wc -lcloc .)。
  • 任务触及的具体子树。
  • 当前模型的上下文窗口(gpt-5.5 long:1M;gpt-5.4:200k;gpt-5.5 标准:400k)。
  • AGENTS.md、CLAUDE.md、根 README.md 的 token 长度(wc -w × 1.3 ≈ token)。
  • 任务相关的内部术语 glossary(项目代号、包简称)。

最短修复路径

按收益从高到低。

Step 1:prompt 里界定 working scope

任何 plan 之前:

Working scope:
- 只编辑:packages/api-server/、packages/api-types/
- 只读参考:packages/db-client/(只读,不改)
- 不要碰:monorepo 里其他任何东西

如果某个问题需要在 scope 外做改动,停下来先问。

这条能把 agent 的搜索空间在大多数 monorepo 上砍 80-90%。

Step 2:预喂结构摘要

Agent 开始扫之前,先给它你生成好的目录树摘要:

tree -L 3 packages/api-server -I 'node_modules|dist|.next' > /tmp/tree.txt

然后 prompt 里:

仓库结构(读这个,不要再列):

[贴 tree 输出]

关键文件:
- packages/api-server/src/routes/index.ts — 路由注册
- packages/api-server/src/services/ — 业务逻辑
- packages/api-types/src/index.ts — 共享类型

Agent 直接跳过结构发现阶段进入正题。

Step 3:把 AGENTS.md 钉成 task 的 header

不要指望自动 summarize 能保留它,把相关切片嵌进来:

[AGENTS.md 摘录 —— 适用于 packages/api-server]

约定:
- 路由通过 routes/index.ts 里的 registerRoute() 注册
- Services 通过 barrel 文件导出
- 所有 handler 返回 { ok: boolean, data?: T, error?: AppError }

[摘录结束]

任务:...

这条能撑过 summarize,因为它在 user message 里而不是 tool 输出里。

Step 4:用目录级摘要代替文件读

探索阶段优先用摘要:

跑:ls packages/api-server/src/services/
跑:head -1 packages/api-server/src/services/*.ts  (每个文件的第一行 / docstring)
锁定目标 service 之前不要读完整内容。

知道 30 个 service 名 + 各自一行 doc 大概 500 token,全读 30 个要 5 万 token。

Step 5:限制冗长 tool 输出

把吵的命令包一层:

pnpm tsc --noEmit 2>&1 | tee /tmp/tsc.log | head -100
echo "(完整输出在 /tmp/tsc.log)"

或者让 agent 只 grep 它要的:

grep -E "error TS|src/api-server/" /tmp/tsc.log | head -50

100 行而不是 4000 行。

Step 6:拆成 commit checkpoint 的子任务、各自全新上下文

长任务跑一串 agent 调用:

调用 1:在 packages/api-types 加新类型。Commit。
调用 2:在 packages/api-server 加 route + handler。Commit。
调用 3:加集成测试。Commit。

每次调用一个全新上下文,步骤之间的 commit 是持久状态。

Step 7:必要时切长上下文模型变体

如果 scope 完还是要 30 万 + token 上下文:

codex agent run --model gpt-5.5-long task.md

但要明白:长上下文是原因 #1 和 #5 的创可贴,解决不了原因 #2(plan summarize)和原因 #4(冗长输出淹没注意力)。

怎么确认已经修好

  • 把同一任务重跑,看 transcript:只在 scope 内读、没有跨 package 乱跑。
  • 中途重 prompt”你在第几步”和”为这步引用 AGENTS.md 规则”——两个都应该精确而不是同义改写。
  • 本地的话开 top / nvidia-smi——token 吞吐应该稳定、不该卡在巨量 tool 输出上。

长期预防

  • monorepo 上每个 agent 任务开头都有显式 Working scope: 段。
  • 根目录维护一份 repo-map.md:目录树摘要 + 关键文件指针;让 agent 先读它。
  • Monorepo 每个 package 一份 AGENTS.md——最近的那份生效,单任务的 doc 量小很多。
  • 探索阶段先 ls + head -1 摘要,再做完整文件读。
  • Shell tool 输出用 wrapper 限到 100 行,溢出导到 /tmp/*.log
  • 多 package 工作拆成 commit checkpoint 的子任务;3-package 改动绝不一个超长 prompt。

常见坑

  • 把 AGENTS.md 贴一次就以为 200 turn 后还在——它不会在,summarize 会把它吃掉。
  • 相信 --max-tokens 1M 万能——注意力质量在硬上限之前很早就掉了。
  • 不 scope 就用长上下文模型——花 10 倍钱拿迷路的输出。
  • tsc --noEmit 不加 head / tail 帽——一条坏命令能炸掉 40% 窗口。
  • 重 prompt”你读过哪些文件”——答案是不完整的,因为 read list 本身已经被 summarize 了。

常见 FAQ

Q:我的仓库 20 万行,真的需要 scope 吗?

要。20 万行全读也是 ~500 万 token。你永远只能覆盖整个 codebase 的一小部分,早点 scope 让”小部分”是有意为之而不是随机的。

Q:怎么快速做结构摘要?

tree -L 3 -I 'node_modules|dist|.next|coverage' > repo-map.txt

两条命令就有一份 1-2k token 的图,胜过 50 次随机文件读。

Q:Agent 无视了我”不要碰其他 package”的规则,怎么办?

把那条规则搬到 AGENTS.md(自动加载)、钉在任务消息顶端,再加 verifier:“完成前跑 git diff --name-only,确认所有文件都在 scope 内”。Verifier 把静默违反变成可见错误。

Q:开 Codex 的 caching 有用吗?

有——prompt cache 让 scoping 更便宜,但防不住上下文溢出。Cache 加速重复读同内容,但内容依然占窗口。

相关阅读

标签: #Codex #agent #排查 #context-window #monorepo