用户场景分析
场景 A:长调试会话中,旧工具结果消失
用户正在排查一个构建问题,已经和 Agent 交互了 30 轮。第 8 轮时执行了一次 bash 跑了完整构建日志(5000 行),第 12 轮时调用了一个 MCP Jira 工具查询了相关 issue。到了第 25 轮,Agent 需要回溯构建日志里的某个 warning 和 Jira issue 描述来做关联分析,但此时这俩工具结果已经分别变成了:
[summary] bash [exit=0] workdir=/proj lines=5000 chars=120000 — 日志内容全丢
[Old tool result content cleared] — MCP 工具无摘要器,直接清空
Agent 不得不重新执行 bash 编译 + 重新查 Jira,浪费 token 和时间。
场景 B:子 Agent 推理链被打断
用户让主 Agent spawn 一个子 Agent 去分析某个模块的架构。子 Agent 返回了 200 行的分析报告。15 轮后,主 Agent 需要引用子 Agent 的结论,但结果已被替换为 [Old tool result content cleared],Agent 只能重新 spawn 一次。
场景 C:Memo 提取时漏掉关键尾部信息
构建命令输出 3000 行,前 600 字符是正常的编译标语 + 依赖信息,真正致命的 error: linker failed 在第 2800 行。Run 结束后触发 memo 提取,sanitizeProjectedToolContent 硬截取前 600 字符,memo 只看到"构建正常"的假象,漏掉了真正的错误。
问题根因
-
RegisterBuiltinSummarizers 只注册了 7 个内置工具的摘要器,spawn_subagent、diagnose、memo/*、todo/write 以及全部 MCP 工具没有摘要器,压缩时直接返回 [Old tool result content cleared]
-
spawnsubagent/tool.go 和 diagnose/tool.go 未显式声明 MicroCompactPolicy(),默认 Compact,导致子 Agent/诊断结果参与压缩
-
microcompact.go:summarizeOrClear 找不到摘要器时无兜底逻辑,静默清空
-
projection.go:sanitizeProjectedToolContent 对 tool 输出只截头部 600 rune,尾部关键信息(如构建错误)被丢弃
-
pin_checker.go:defaultPinToolNames 只覆盖 write_file + edit,copy_file、move_file 等操作关键文件的工具不被保护
-
codebase/read.go 设为 PreserveHistory,连续读取多个大文件后 token 大量被陈旧内容占据
解决方案
方案 1(核心):为 subagent + diagnose 加 PreserveHistory 策略
internal/tools/spawnsubagent/tool.go:新增 MicroCompactPolicy() 返回 MicroCompactPolicyPreserveHistory
internal/tools/diagnose/tool.go:同上
影响:子 Agent 和诊断结果永远不会被微压缩,只有 24 span 大裁剪时才会移除。
方案 2(核心):注册缺失工具的基础摘要器 + 通用兜底
- 在
internal/tools/micro_compact_summarizers_builtin.go 的 builtinSummarizers 列表中追加:
spawn_subagent:保留 role / goal(截断 80 rune)+ 输出行数字符数
diagnose:保留 [error] 标记 + 输出规模
memo/remember、recall、list、remove:保留操作类型 + 输出规模
todo/write:保留输出规模
- 在
internal/context/microcompact.go:summarizeOrClear 中,当专用摘要器为 nil 时不直接返回清空占位符,而是调用一个通用兜底函数 fallbackSummary,生成最小摘要 [summary] <tool_name> lines=N chars=N
影响:MCP 工具和其他未知工具至少保留规模信息,不会完全清空。
方案 3:Memo 提取链路改为头尾各保留一半
- 修改
internal/context/projection.go:
recentWindowToolContentCharLimit 从单一上限拆为 recentWindowToolContentHeadChars = 300 + recentWindowToolContentTailChars = 300
sanitizeRawToolContent 和 sanitizeProjectedToolContent 改为头尾各取 300 rune,中间标记 [truncated]
- 新增
headRunes / tailRunes 辅助函数
影响:Memo 提取能同时看到工具输出的开头和结尾,不会漏掉尾部的关键信息。
方案 4:PinChecker 扩展工具白名单
pin_checker.go:defaultPinToolNames 追加 copy_file、move_file
NewDefaultPinChecker 接受可选的 extraPatterns ...string 参数
影响:copy_file / move_file 操作关键文件时也享受 pin 保护。
方案 5:codebase/read 改回 Compact 并注册专用摘要器
internal/tools/codebase/read.go:MicroCompactPolicy() 返回 MicroCompactPolicyCompact
micro_compact_summarizers_builtin.go:为 ToolNameCodebaseRead 注册 readFileSummarizer(复用已有实现)
影响:陈旧 read 结果被摘要为 [summary] /path/file.go lines=42 chars=1500,释放 token。
验收标准
| # |
验收项 |
验证方法 |
| 1 |
spawn_subagent 和 diagnose 的 MicroCompactPolicy() 返回 PreserveHistory |
运行现有单元测试 go test ./internal/tools/spawnsubagent/... ./internal/tools/diagnose/... 不失败;检查返回值 |
| 2 |
spawn_subagent、diagnose、memo/*、todo/write 的工具结果在压缩后不再是 [Old tool result content cleared],而是包含工具名和输出规模的结构化摘要 |
go test ./internal/context/... 覆盖 microCompactMessagesWithPolicies 的用例不失败;新增或修改用例验证新摘要格式 |
| 3 |
MCP 工具(如 mcp.github.issue)在无专用摘要器时,压缩后生成 [summary] mcp.github.issue lines=N chars=N 而非清空占位符 |
同上,测试用例中构造 MCP 命名风格的工具消息验证 |
| 4 |
Memo 提取的 tool 内容同时包含头部和尾部,中间有截断标记 |
go test ./internal/context/... -run TestSanitize 相关用例通过 |
| 5 |
copy_file / move_file 操作 go.mod 等关键文件时不被微压缩 |
go test ./internal/context/... -run TestPinChecker 覆盖新工具 |
| 6 |
codebase/read 的旧结果被压缩为类似 [summary] /path/file.go lines=N chars=N 的摘要 |
现有 read_test.go 中 MicroCompactPolicy() 用例更新为 Compact |
| 7 |
所有已有单元测试不回归 |
go test ./internal/context/... ./internal/tools/... |
改动文件一览
internal/context/microcompact.go — 通用兜底摘要器
internal/context/projection.go — Memo 提取头尾保留
internal/context/pin_checker.go — 扩展工具白名单
internal/context/microcompact_summarizers_builtin.go → 注意路径!
internal/tools/micro_compact_summarizers_builtin.go — 追加摘要器注册
internal/tools/spawnsubagent/tool.go — 加 PreserveHistory
internal/tools/diagnose/tool.go — 加 PreserveHistory
internal/tools/codebase/read.go — 改回 Compact
用户场景分析
场景 A:长调试会话中,旧工具结果消失
场景 B:子 Agent 推理链被打断
场景 C:Memo 提取时漏掉关键尾部信息
问题根因
RegisterBuiltinSummarizers只注册了 7 个内置工具的摘要器,spawn_subagent、diagnose、memo/*、todo/write以及全部 MCP 工具没有摘要器,压缩时直接返回[Old tool result content cleared]spawnsubagent/tool.go和diagnose/tool.go未显式声明MicroCompactPolicy(),默认Compact,导致子 Agent/诊断结果参与压缩microcompact.go:summarizeOrClear找不到摘要器时无兜底逻辑,静默清空projection.go:sanitizeProjectedToolContent对 tool 输出只截头部 600 rune,尾部关键信息(如构建错误)被丢弃pin_checker.go:defaultPinToolNames只覆盖write_file+edit,copy_file、move_file等操作关键文件的工具不被保护codebase/read.go设为PreserveHistory,连续读取多个大文件后 token 大量被陈旧内容占据解决方案
方案 1(核心):为 subagent + diagnose 加 PreserveHistory 策略
internal/tools/spawnsubagent/tool.go:新增MicroCompactPolicy()返回MicroCompactPolicyPreserveHistoryinternal/tools/diagnose/tool.go:同上影响:子 Agent 和诊断结果永远不会被微压缩,只有 24 span 大裁剪时才会移除。
方案 2(核心):注册缺失工具的基础摘要器 + 通用兜底
internal/tools/micro_compact_summarizers_builtin.go的builtinSummarizers列表中追加:spawn_subagent:保留role/goal(截断 80 rune)+ 输出行数字符数diagnose:保留[error]标记 + 输出规模memo/remember、recall、list、remove:保留操作类型 + 输出规模todo/write:保留输出规模internal/context/microcompact.go:summarizeOrClear中,当专用摘要器为 nil 时不直接返回清空占位符,而是调用一个通用兜底函数fallbackSummary,生成最小摘要[summary] <tool_name> lines=N chars=N影响:MCP 工具和其他未知工具至少保留规模信息,不会完全清空。
方案 3:Memo 提取链路改为头尾各保留一半
internal/context/projection.go:recentWindowToolContentCharLimit从单一上限拆为recentWindowToolContentHeadChars = 300+recentWindowToolContentTailChars = 300sanitizeRawToolContent和sanitizeProjectedToolContent改为头尾各取 300 rune,中间标记[truncated]headRunes/tailRunes辅助函数影响:Memo 提取能同时看到工具输出的开头和结尾,不会漏掉尾部的关键信息。
方案 4:PinChecker 扩展工具白名单
pin_checker.go:defaultPinToolNames追加copy_file、move_fileNewDefaultPinChecker接受可选的extraPatterns ...string参数影响:
copy_file/move_file操作关键文件时也享受 pin 保护。方案 5:codebase/read 改回 Compact 并注册专用摘要器
internal/tools/codebase/read.go:MicroCompactPolicy()返回MicroCompactPolicyCompactmicro_compact_summarizers_builtin.go:为ToolNameCodebaseRead注册readFileSummarizer(复用已有实现)影响:陈旧 read 结果被摘要为
[summary] /path/file.go lines=42 chars=1500,释放 token。验收标准
spawn_subagent和diagnose的MicroCompactPolicy()返回PreserveHistorygo test ./internal/tools/spawnsubagent/... ./internal/tools/diagnose/...不失败;检查返回值spawn_subagent、diagnose、memo/*、todo/write的工具结果在压缩后不再是[Old tool result content cleared],而是包含工具名和输出规模的结构化摘要go test ./internal/context/...覆盖microCompactMessagesWithPolicies的用例不失败;新增或修改用例验证新摘要格式mcp.github.issue)在无专用摘要器时,压缩后生成[summary] mcp.github.issue lines=N chars=N而非清空占位符go test ./internal/context/... -run TestSanitize相关用例通过copy_file/move_file操作go.mod等关键文件时不被微压缩go test ./internal/context/... -run TestPinChecker覆盖新工具codebase/read的旧结果被压缩为类似[summary] /path/file.go lines=N chars=N的摘要read_test.go中MicroCompactPolicy()用例更新为Compactgo test ./internal/context/... ./internal/tools/...改动文件一览