从 Agentic Loop 到 Repo Map,七种策略与六类陷阱
引言:128K vs 10MB 的硬冲突
2026 年的 LLM 上下文窗口已达到 128K ~ 1M token(≈ 0.5MB ~ 4MB 文本),但 LLM 想要处理的真实数据规模远远超过这个量级:
| 真实场景 | 数据量级 | 与 200K context 的比值 |
|---|---|---|
| 一个 10MB 的代码文件 | ~2.5M token | 12 倍 |
| 一份 50MB 的日志 | ~12M token | 60 倍 |
| 一个代码仓库的全量源码 | 数百 MB ~ 数 GB | 千倍 ~ 万倍 |
这是几乎所有 agent 系统的通病。本文盘点主流开源项目如何应对,并提炼出一套可落地的工程模式。
一、七种主流应对策略(建立坐标系)
应对上下文超限,业界的方法论可归纳为七类。它们常常组合使用,很少单独生效:
| # | 策略 | 一句话定义 | 典型使用者 |
|---|---|---|---|
| 1 | 硬截断(Truncate) | 工具输出只保留前 N 字节/行,剩余换成[truncated]指针 | OpenCode、Vercel AI SDK 应用层 |
| 2 | 结构化摘要(Summarize) | 用 LLM 把工具输出重写成短摘要 | Claude Code/compact、LangChain SummarizationMiddleware |
| 3 | 动态剪枝(Prune) | 按"陈旧度 / 引用次数"删除已"消费过"的旧工具消息 | OpenCode DCP 插件、MemGPT |
| 4 | 外部化 + 按需检索(Offload + RAG) | 大文本存文件/向量库,prompt 里只放指针 + 检索片段 | LangChain offload、Letta OS-style 虚拟内存 |
| 5 | 替换为引用(Reference Replacement) | 工具消息替换为短 metadata(行数 / 字节 / 路径) | Claude CodeFile unchanged去重机制 |
| 6 | 分块延迟回填(Chunked Backfill) | 工具返回立刻切片 + 嵌入;LLM 想看更多时再调检索 | OpenCode@enowdev/mnemosyne、LangChain Deep Agents |
| 7 | 上下文重置(Periodic Reset) | 不压缩,而是定期丢弃整个对话历史,从结构化文档重建 | CoordClaw |
二、主流方案的实际坐标
2.1 CoordClaw——以外化记忆做根因规避
CoordClaw 的设计哲学与其他项目有根本性差异。它不试图压缩或检索,而是让对话历史根本不必要长期保留。
核心机制:
- 每轮上下文完全重置——Agent 下次启动时重新跑
task_start.py(T2 标准动作),只加载"角色定义 + 上一轮工作日志" - 工作日志外化——通过
task_report.py(T3)编写结构化工作日志到项目目录,它是项目记忆,而不是对话历史 - 配套
context_optimization配置项——team.json中可配置保留轮数、丢弃/压缩策略,压缩历史工具消息 llm_error阻断机制——team.json中llm_error.enabled+endcode配置,在 LLM 报错超阈值时阻断,防止对话失控- 点对点消息——Agent 之间通过
chat_manager.py send精确路由,非广播,避免上下文污染扩散
为什么有效:真相在文件里,不在 prompt 里。LLM 看不到"上一次说了什么",但能看到"上一次的结论写在哪个文件里"——这是主动遗忘换取可审计。
代价:每轮需要花 token 重建上下文;Agent 不能做"基于对话氛围"的连续推理。
2.2 OpenCode——原生机制 + 插件生态
OpenCode 的官方钩子提供手动触发点:
experimental.session.compacting—— 上下文压缩钩子,允许插件在 LLM 生成续接摘要前注入自定义上下文或完全替换压缩提示词tool.execute.before/tool.execute.after—— 工具调用拦截
但 OpenCode默认不主动压缩。真正活跃的是它的第三方插件生态:
| 插件 | 功能 | 状态 |
|---|---|---|
opencode-dynamic-context-pruning | 按"已无引用 / 超过轮数"自动移除 obsolete tool outputs | 官方生态收录 |
@enowdev/mnemosyne | 组合插件:命令过滤 + 上下文剪枝 + 持久记忆 + 自动代码索引 | npm 已发布 |
opencode-mnemosyne | 本地持久记忆(基于 SQLite + 向量搜索),跨会话保留 | 社区维护 |
2026 年的事实:OpenCode 的 token 优化方向是"插件化裁剪",而非"runtime 内置智能压缩"。这是一个清晰的设计分工——核心 runtime 保持精简,社区围绕它做策略创新。
2.3 Claude Code——三件套 + 隐式预读
Claude Code 的 Read 工具默认最多读2000 行,单行超过2000 字符会被自动截断。它暴露三个相互配合的工具:
| 工具 | 作用 | LLM 何时调用 |
|---|---|---|
| Read | 分页读窗口(offset + limit) | 已知位置,读具体内容 |
| Grep | 按模式找位置 | 不知道在哪,让 grep 定位 |
| Glob | 按文件名 pattern 找文件 | 不知道文件叫啥 |
LLM 的典型工作流:
Glob("**/*.ts") → 找到 50 个 Grep("handleAuth", path="src/") → 精确定位到 src/auth.ts:42 Read(file_path, offset=42, limit=50)核心技巧:Read 返回的内容带显式行号——“我在第 42 行看到 function handleAuth()”,下次 LLM 可以精确地说"修改第 50 行的 return 语句"。
预读不变量(pre-read invariant):Edit / Write 工具强制要求目标文件此前被 Read 读取过——否则报错。防止 LLM 盲目覆盖。
File unchanged去重:同一文件被读过且未修改(通过 mtime 判断),第二次 Read 直接返回"File unchanged"字符串——deduplication 节省 token。官方测算命中率约18%,每次省约25K tokens。
2.4 Claude Code 的/compact:自动压缩与手动触发
当 Claude Code 接近上下文窗口限制(约 95%)时,会自动压缩对话历史。/compact命令可手动触发这一过程。
压缩后,以下内容易丢失:
- 会话早期的指令(如"不要碰这个文件")
- 中间决策(为什么选择方案 A 而非 B)
- 50 条消息前讨论的具体代码片段
而以下内容通常保留:
- 当前任务和即时上下文
- 最近修改的文件名
- 最近的错误及解决方案
关键洞察:项目根目录的CLAUDE.md在压缩后会被重新加载——它是唯一保证能幸存任何压缩的地方。
2.5 LangChain / LangGraph——Middleware 抽象
LangGraph 把"上下文管理"做成可插拔 middleware。Deep Agents 项目(基于 LangGraph)提供了SummarizationMiddleware和FilesystemMiddleware等组件。
# 概念示例(基于 LangGraph 中间件模式)app.add_middleware(SummarizationMiddleware(trigger={"messages":50},# 触发阈值keep={"messages":10},# 保留多少summarization_model="gpt-4o-mini",))这种设计的真正价值:把策略和 runtime 解耦。同一份 LangGraph 应用可以挂不同 middleware:“开发环境保留全部” / “生产环境三级压缩” / “演示模式 50% 截断”。
2.6 Aider——Repo Map(代码地图)
Aider 不分页读取,而是自动生成仓库地图:用 tree-sitter 抽出所有文件的类签名、函数签名、关键调用关系,喂给 LLM 一个"代码地图"。
src/auth/auth.service.ts: class AuthService +login(email: str, password: str) -> Token # line 35 +validateToken(token: str) -> User | null # line 230 +hashPassword(plain: str) -> str # line 1500LLM 看地图选位置,再精确读具体文件。地图大小固定(默认约 1,024 tokens),不随代码量线性增长——这是它能处理整个代码仓库的关键。
局限:只对结构化代码文件有效(.ts / .py / .go 等 50+ 语言)。对散文、日志、配置文件无效。
2.7 MemGPT / Letta——OS 风格虚拟内存
把 LLM 的 context window 类比为 RAM,大文档类比为磁盘:
| OS 概念 | Letta 等价物 |
|---|---|
| RAM | Core Memory(始终保留在上下文中的关键信息) |
| 磁盘缓存 | Recall Memory(可搜索的近期历史) |
| 冷存储 | Archival Memory(长期向量数据库存储) |
LLM 在两套内存之间主动换页——这是 OS 虚拟内存思想在 LLM 上的应用。优势是 LLM 显式掌控记忆,劣势是 LLM 要学会这个换页 API(认知负担)。
2026 年的现状:MemGPT 已演变为商业平台Letta,开源核心 + 商业云服务。对于生产环境,Letta 是更成熟的选择;MemGPT 原始仓库更适合研究和自定义。
2.8 Clawith——数字员工的 Aware 系统
Clawith(由 dataelement 团队开发的企业级 AI 员工框架)的创新是Aware 自主感知系统,三组件协同:
| 组件 | 作用 |
|---|---|
| Focus | 当前注意力焦点——结构化工作记忆列表 |
| Trigger | 触发新任务的信号——六种类型(cron / once / interval / poll / on_message / webhook) |
| Heartbeat | 周期性自我检查——默认 15 秒一次轻量扫描 |
这套机制不直接解决"上下文超限",而是让 Agent 主动管理注意力——Focus 决定"现在看什么",Heartbeat 周期性评估"是否需要换页",Trigger 在"该换页时主动发起"。
三、工具层设计:让 Agentic Loop 健康运转
主流方案的工程实现都收敛到同一个事实:LLM 必须分页读取大文件。这种"LLM 始终只处理一小块"的模式叫Agentic Loop或Iterative Retrieval:
┌─────────────────┐ │ LLM 拿到当前页 │ ← context window 里只有这一段 └────────┬────────┘ │ reasoning ▼ ┌─────────────────────────────┐ │ 决定下一步: │ │ A. 读下一段(offset+=N) │ │ B. grep 换位置 │ │ C. 已收集够,输出结论 │ └────────┬────────────────────┘ │ tool call ▼ ┌─────────────────┐ │ read_file 返回 │ ← 又是 200 行 └────────┬────────┘ │ 回到 LLM └──── 循环3.1 read_file 的契约设计
一个健康的read_file工具应返回:
read_file(path:string,offset?:number,// 1-based 起始行limit?:number// 读多少行(默认 200,最大 2000)):{content:string,// 该窗口内容(每行带行号)total_lines:number,// 文件总行数(让 LLM 知道剩余多少)start_line:number,// 本次起始行号encoding:string,// 文件编码truncated:boolean,// 是否被截断}建议的扩展字段(推荐设计,非所有工具统一实现):
next_offset?: number—— 建议的下一次 offset,避免 LLM 陷入 offset 计算循环bytes_total?: number—— 总字节数,辅助 LLM 评估文件规模
3.2 配套工具——三个最少必须有
| 工具 | 作用 | 为什么必须有 |
|---|---|---|
read_file | 分页读窗口 | 主力 |
grep | 按模式找位置 | 效率工具——大多数时候是找特定模式,不是顺序读 |
outline | 看文件结构(类/函数/章节大纲) | 给 LLM 全局地图,避免"读了一段不知身在何处" |
只有 read_file 会导致 LLM 盲目翻页;只有 grep 会让 LLM 缺乏全局感;三个配套才能形成健康工作流。
3.3 LLM 的工作流示例
假设架构师 Agent 审查src/auth/auth.service.ts(2400 行):
[Round 1] outline(path="src/auth/auth.service.ts") → { classes: [AuthService], functions: [login, validateToken, hashPassword] } [Round 1] reasoning: "login() 在 line 35,先看它 + 周围 100 行" read_file(path, offset=1, limit=100) [Round 2] reasoning: "看完 login(),跳到 validateToken() 在 line 230" read_file(path, offset=230, limit=100) [Round 3] reasoning: "重点关注 hashPassword 部分,在 line 1500-1600" read_file(path, offset=1500, limit=100) [Round 4] reasoning: "已收集够证据,写工作日志并交付" write_worklog(content="...") → done每一轮 LLM context 里只看到 ~100 行,但逻辑上看完了 4 个关键区段。
四、六类陷阱(实战中会撞到的)
光说优势不够,这些坑决定了你工具设计的好坏:
| # | 陷阱 | 反模式表现 | 解决方案 |
|---|---|---|---|
| 1 | 翻页循环 | LLM 卡在"再 offset 几行确认一下",N 轮无结论 | max_steps上限 + 工作日志记录已读区段 |
| 2 | 早期放弃 | 前几页不像预期就跳走,错过关键章节 | 提供 outline 工具给全局地图 |
| 3 | 丢失全局视野 | 只看局部,忘了"全局目标在哪一段" | outline + 段摘要工具 |
| 4 | 重复读取 | 同一窗口被反复读 | File unchangeddeduplication + 已读区段记忆 |
| 5 | 撑爆 window | 某段意外读到 10MB | 工具内置硬上限(单行 2000 字符截断、limit 上限 2000) |
| 6 | 多级压缩细节流失 | 第一级压缩保留的细节在第二级被丢弃 | 用 reference replacement 而非 summarize |
其中#6是工业界最隐蔽的问题——Claude Code 的自动压缩设计巧妙,但学术研究反复指出"经过多级压缩后,关键决策细节会显著丢失"。
补充:Claude Code 的 Read 工具存在一个已知边界情况——在某些场景下会尝试读取整个文件而非严格遵守 2000 行限制,导致超出 25,000 token 上限而报错。这提醒我们:即使工具文档承诺了限制,实际实现也可能有漏洞,生产环境必须做二次校验。
五、提示词纪律——告诉 LLM 怎么读
光有好工具不够,LLM 需要"工作纪律"。建议在 Agent 系统 prompt 或角色卡里明示:
阅读大型文档的工作纪律: 1. 拿到文件路径后,先评估大小(read_file 返回的 total_lines) 2. 超过 1000 行:先用 outline 拿到结构,再 grep 定位关键区段,最后 read_file 取窗口 3. 超过 5000 行:禁止"从头顺序翻页",必须 grep + outline 组合 4. 每次 read_file 后,记录本段要点到工作日志,避免重复读 5. 累计读 5 次以上仍未得出结论,回退向用户澄清而非继续翻页这条纪律直接解决了陷阱 1(翻页循环)和陷阱 2(早期放弃)。
六、场景适配:什么场景用什么方案
并非所有场景都适合 Agentic Loop。下表给出真实工程选择:
| 场景 | 推荐策略 | 理由 |
|---|---|---|
| 找特定关键字 / 函数 | Agentic Loop + grep | 极高效,token 节省 80%+ |
| 审查代码逻辑漏洞 | Agentic Loop + outline | LLM 可自主定位 |
| 通读散文 / 报告理解全局 | 先 LLM 摘要预处理 + 再读 | 一次性摘要比翻页更合适 |
| 写整篇论文 summary | 专门 transformer 工具 | 不该让 LLM 翻页 |
| 大型仓库全局理解 | Aider Repo Map 思路 | 固定大小 + 全局感 |
| 长对话历史保留 | LangGraph middleware | 策略可插拔 |
| 长期运行的数字员工 | Clawith Aware 系统 | 主动注意力管理 |
| 严格可审计的多 Agent 协作 | CoordClaw 外化记忆 | 真相在文件里 |
七、给工程团队的落地建议
7.1 工具层(必须做)
read_file建议返回next_offset—— 没这个字段,LLM 必然进入 offset 计算浪费循环- 单行 > 2000 字符自动截断(加
...truncated...标记,不报错) File unchanged机制做 deduplication(基于 mtime 或哈希)- 二进制文件直接拒绝(不暴露内容细节)
- 强制预读不变性:Edit / Write 前必须 Read 一次
7.2 提示词层(强烈建议)
- 在系统 prompt 里写入"阅读纪律"
- 对不同角色给不同阅读风格——审查员严格(outline 必用)、快速决策者宽松(允许更大 limit)
7.3 监控层(生产必做)
- 监控"连续 read_file 调用次数"——超过阈值算 agent 进入循环
- 监控"重复读取"——同一 offset 范围被读多次算浪费
- 监控"中途放弃"——读完 < 30% 就停止算早期放弃
7.4 架构层(进阶)
- 记忆外化:重要结论写文件而非留对话(CoordClaw 模式)
- 可插拔压缩策略:开发环境保留全部 / 生产环境多级压缩
- Agentic Loop 与单次读取并存:简单查询走单次,复杂任务走 Loop
结论:核心心智模型
把上下文管理想成操作系统:
| OS 概念 | LLM 等价物 |
|---|---|
| RAM(有限、快) | Context Window(128K ~ 1M token) |
| Disk(无限、慢) | 文件系统 + 向量数据库 |
| 虚拟内存(按需换页) | Agentic Loop + grep + outline |
| 进程间通信 | 工具调用 + 工作日志 |
| 文件系统缓存 | Session 内已读区段记忆 |
主流开源项目的差异,本质上是"在这个心智模型下,谁来管理换页"的回答:
| 项目 | 换页策略 | 核心思想 |
|---|---|---|
| CoordClaw | 用户(Agent 自己)主动写文件,让 OS 接管 | 上下文重置 + 工作日志外化 |
| OpenCode + DCP 插件 | 插件按 LRU 自动剪枝 | 社区创新,核心保持精简 |
| Claude Code | Read + Grep + Glob 三件套显式换页 | 应用层控制,人类可审计 |
| Aider | Repo Map 提供文件系统索引 | 固定大小地图,按需寻址 |
| Letta (MemGPT) | LLM 本身学会系统调用换页 | LLM 自主管理三层内存 |
| Clawith | Focus / Trigger / Heartbeat 自主感知 | Agent 主动管理注意力 |
没有最好的方案,只有最适合场景的方案。一个工程团队真正需要决定的,是:让 LLM 学会"换页",还是让它"忘了也不心疼"。
附录:开源项目链接索引
| 项目 | 链接 |
|---|---|
| CoordClaw | https://github.com/CoordClaw/CoordClaw |
| OpenCode | https://opencode.ai |
| OpenCode 插件生态 | https://opencode.ai/docs/ecosystem/ |
| opencode-dcp | https://github.com/monotykamary/opencode-dynamic-context-pruning |
| Claude Code | https://docs.claude.com/en/docs/claude-code |
| LangGraph | https://langchain-ai.github.io/langgraph/ |
| Aider | https://aider.chat |
| Letta (原 MemGPT) | https://docs.letta.com |
| Clawith | https://github.com/dataelement/Clawith |