Agent Harness 有了“眼睛”和“手”:更细的 Trace 事件 + 自动错误恢复
Agent Harness 有了“眼睛”和“手”:更细的 Trace 事件 + 自动错误恢复
系列博客第三篇 · 2026-06-29
从“看得清”到“自己会修”
一、前情回顾
前两天的成果:
- Day 1:跑通最小闭环,用户输入 → 模型 → 工具 → 输出,能跑了。
- Day 2:抽离 ContextBuilder 统一管理上下文,新增 Trace Report 把 JSON 变成可读的复盘报告。
到了 Day 2 结束时,我手上有这样一个 Agent Harness:
- 能跑 —— 基础闭环完整。
- 能复盘 —— Trace Report 让我知道哪里出错了。
但复盘完发现问题后,还是得手动改代码、重新跑。而且 Trace 本身也不够细——我只能看到“调了哪个工具、成功还是失败”,但看不到“ContextBuilder 给了模型什么信息”“用了哪个 Engine”“工具耗时多少”。
所以 Day 3 的目标很明确:
- 让 Trace 更细,把执行过程中的每个关键节点都记录下来。
- 新增两个实用工具,让 Agent 能搜网页、能跑 shell 命令。
- 让系统学会自己修,遇到常见错误时自动恢复,而不是直接放弃。
二、让 Trace 长出“骨架”:context_built 和 engine_started
2.1 之前的 Trace 长这样
之前的事件流:
run_started → tool_call → tool_result → done
但如果我想知道:
- 这次任务用的是哪个 Engine?(Chat 还是 Tool?)
- ContextBuilder 给模型注入了什么规则?
- 每次工具调用耗时多久?成功了还是失败了?
抱歉,JSON 里没有。
2.2 本次新增的两个关键事件
我往 src/runtime/events.ts 里加了两个新事件:
context_built
记录 ContextBuilder 构造完上下文后的快照:
{
"type": "context_built",
"payload": {
"mode": "tool",
"workspaceRoot": "C:/Users/ts/Desktop/zcy/mini-agent-harness",
"systemPromptLength": 2048,
"toolWorkflowSteps": 5
}
}
engine_started
记录这次执行用的是哪个 Engine、最大轮数是多少:
{
"type": "engine_started",
"payload": {
"engine": "DeepSeekToolEngine",
"maxTurns": 5
}
}
2.3 增强 tool_result:把关键指标抽出来
以前 tool_result 只记录原始返回,现在我会额外抽取:
{
"type": "tool_result",
"payload": {
"tool": "read_file",
"ok": false,
"durationMs": 312,
"errorCode": "FILE_NOT_FOUND"
}
}
这样一来,Trace Report 就能直接统计:
- 工具调用总次数
- 失败次数和失败率
- 总耗时
- 最常见的错误类型
2.4 现在的事件流更丰满了
run_started
-> context_built ← 新增:知道模型看到了什么
-> engine_started ← 新增:知道用了哪个引擎
-> text_delta / tool_call / tool_result / error
-> done
Trace Report 里也多了几个关键字段:
toolFailureCounttotalToolDurationMsengine
2.5 一个小小的感悟
Trace 事件的设计,本质上是在回答一个问题:
如果这次执行出了岔子,我需要哪些信息才能快速定位?
你越能精准地回答这个问题,你的系统就越容易调优。很多开源框架的 Trace 做得非常重,但我选择逐步加事件——每次只加当前阶段最需要的那几个,保持轻量,但足够用。
三、给 Agent 装上两只“新手”:search_web 和 run_shell
之前 Agent 只有三个本地工具:read_file、grep、write_file。它只能在项目文件夹里读读写写,出不去。
但很多时候,我需要它去外面看看:
- “搜索一下 DeepSeek MLA 是什么”
- “在项目里跑一下
npm run check,告诉我有没有类型错误”
所以这次我加了两个新工具。
3.1 search_web:让 Agent 能上网查资料
实现方式很简单——通过 DuckDuckGo 的 HTML 搜索,不需要申请 API Key,完全免费。
输入:搜索关键词 + limit(默认 5 条)
输出:title / url / snippet 结构化列表
我在 ContextBuilder 的 toolWorkflow 里加了一条规则:
如果用户问题涉及实时信息或公开知识,优先使用 search_web 获取参考资料。
举个例子:
npm run dev -- "搜索一下 DeepSeek MLA 是什么,然后总结"
Agent 会先调用 search_web 获取搜索结果,再根据搜索结果生成总结。
3.2 run_shell:让 Agent 能执行命令
这个工具让 Agent 可以在 workspaceRoot 下执行任意 shell 命令:
输入:shell 命令 + timeout(默认 30 秒)
输出:stdout / stderr / exitCode
但 shell 命令很危险,所以我在 shellSafety.ts 里维护了一个黑名单:
const BLACKLIST = [
'rm -rf',
'sudo',
'chmod 777',
'dd if=',
'mkfs',
// ...
];
任何匹配黑名单的命令都会被拒绝执行,并返回友好的错误提示。
同时,run_shell 还有两个保护机制:
- 超时限制:默认 30 秒,防止命令卡死。
- 输出大小限制:最多返回 1MB,防止日志爆炸。
3.3 策略层同步更新
TaskPolicy 里新增了对 shell / web / npm / git 等关键词的识别,如果用户输入包含这些词,就会自动走 Tool Engine,而不是 Chat Engine。
四、让 Agent 学会“自己修”:Error Recovery
这部分是我今天最兴奋的一个改动。
4.1 之前失败就失败了,没有第二次机会
以前工具执行失败后,ToolResult 里虽然有 error.suggestion,但能不能恢复完全看模型自己——它得读懂建议、想清楚怎么补救、再发起新调用。
现实是:大部分情况下模型就直接放弃了。
4.2 系统级恢复:失败后自动“补一刀”
我新增了 src/recovery/ErrorRecovery.ts,它的职责很简单:
当工具返回 ok=false 且 error.recoverable=true 时,系统自动判断能不能帮模型“补一刀”。
目前支持三种恢复场景:
| 失败场景 | 自动恢复动作 |
|---|---|
read_file / grep / write_file 路径错误 |
自动调用 list_files 列出目录内容 |
list_files 参数无效 |
回退到 workspace 根目录列表 |
TOOL_NOT_FOUND(工具不存在) |
自动调用 list_files 帮助重新规划 |
4.3 恢复后的闭环
恢复流程是这样的:
tool_result (failed)
-> tool_error (记录失败详情)
-> recovery_started (如果有恢复计划)
-> recovery_result (执行恢复,成功或失败)
-> 把「原始失败信息 + 恢复结果」一起喂回模型
模型拿到的是这样的上下文:
你刚才调用
read_file("README2.md")失败了,因为文件不存在。
我帮你执行了list_files,当前目录下有这些文件:
- README.md
- package.json
- src/
请根据这个信息重新规划你的操作。
这样模型就不用“猜”了,它拿到了真实的环境信息。
4.4 防止无限恢复的保险机制
- 每次 run 最多自动恢复 3 次,超过就停止。
- 同一种失败不会重复恢复(比如同一路径失败两次,第二次不再尝试)。
4.5 Trace 里的恢复记录
新增了三个 Trace 事件:
tool_error:记录工具失败详情和suggestionrecovery_started:记录准备执行哪种恢复recovery_result:记录恢复是否成功
Trace Report 里也会新增:
recoveryAttemptCountrecoverySuccessCount
这样一来,我可以清楚地知道:系统帮模型救回了多少次失败。
五、一个实际的运行例子
今天我用这个命令测试了一下:
npm run dev -- "在项目里运行 npm run check,告诉我有没有类型错误"
流程是这样的:
- TaskPolicy 识别到
npm run check,判定为 tool 任务。 - ContextBuilder 构造上下文,注入了工具工作流规则。
- DeepSeekToolEngine 启动,调用
run_shell("npm run check")。 - 命令执行成功,返回 stdout。
- 模型根据输出,告诉我“没有类型错误”。
- 所有事件(
context_built、engine_started、tool_call、tool_result)都记录在 trace JSON 里。 - 我运行
npm run trace:report -- runs/run_xxx.json,生成复盘报告。
整个过程不到 10 秒,Agent 自己完成了从“理解任务”到“执行命令”到“反馈结果”的闭环。
六、记录
6.1 Trace 是越挖越深的
一开始我只记录 tool_call 和 tool_result,已经觉得够了。但随着问题变复杂,我发现需要 context_built 来确认模型“看到了什么”,需要 engine_started 来确认“用了什么策略”。Trace 不是一次性设计好的,它是随着你对系统理解的深入,逐步生长的。
6.2 让系统自己修,比让模型自己悟更可靠
Error Recovery 让我意识到,有些失败的修复是“机械的”——路径错了就列一下目录,工具不存在就看一看可用清单。这些逻辑用代码写死比等模型“读懂 suggestion”要可靠得多、快得多。
这就像开车:你可以让司机自己看路牌找路(模型读 suggestion),也可以直接给导航(系统自动恢复)。导航更稳。
6.3 工具不是越多越好,而是越“安全”越好
新增 run_shell 的时候,我花了一半时间在写黑名单、超时、输出限制。一个工具如果会炸掉系统,那它再强大也等于零。安全是工具的第一性。
更多推荐




所有评论(0)