从“能跑”到“能复盘”:今天我的 Agent Harness 做了两件关键的事 从“能跑”到“能复盘”今天我的 Agent Harness 做了两件关键的事系列博客第二篇 · 2025-06-29每天都在迭代这次是把“能跑”变成“好懂”一、写在前面之前我发布了博客介绍了 Mini Agent Harness 的最小闭环 —— 用户输入模型思考工具执行输出结果。闭环跑通了但跑通不等于好用。今天我一共做了两个比较大的改动分别对应两个让我很不爽的痛点系统提示词和工具工作流散落在各个 Engine 里改一个规则要改两个文件而且以后加 memory 会更乱。Trace 记录虽然有了JSON 格式但读起来太费劲排查失败原因要来回翻效率极低。于是我完成了这两项优化。这篇文章就是过程中的真实思考和踩坑记录。二、第一件事抽离 ContextBuilder让上下文构造有“专人负责”2.1 优化前的混乱场面我的架构本来是这样AgentRuntime - DeepSeekChatEngine (自己维护 DEFAULT_SYSTEM_PROMPT) - DeepSeekToolEngine (自己维护 TOOL_SYSTEM_PROMPT)看起来还行但问题是两个 Engine 里的 system prompt 内容高度相似但写法不同改一个必须同步另一个。工具使用规则比如“先用 list_files 再读文件”是硬编码在 ToolEngine 里的ChatEngine 根本不知道这些规则。以后要加“项目摘要”“历史记忆”根本不知道该往哪里塞。这就是典型的职责不清——Engine 既负责调用模型又负责拼凑上下文。按照单一职责原则Engine 只应该做“调用模型并处理返回”至于“这次调用给模型看什么”应该由另一层来决定。2.2 动手改造ContextBuilder 登场我新建了src/context/ContextBuilder.ts它的唯一任务是根据当前任务模式direct 还是 tool和运行时信息生成一个BuiltContext对象。typeBuiltContext{systemPrompt:string;userPrompt:string;workspaceRoot:string;mode:ExecutionMode;toolWorkflow:string[];};其中toolWorkflow是本次新增的一个小设计——它是一个字符串数组用于告知模型“推荐的工具调用顺序”。比如1. 路径不清楚时先调用 list_files 2. 需要定位内容时用 grep 3. 已知路径后再 read_file 4. 用户要求创建/修改文件时才 write_file 5. 工具返回 okfalse 时先看 error.suggestion 再继续这些规则看起来简单但我在实际测试中发现模型即使是 DeepSeek在没有明确指引时经常胡乱猜测文件路径或者直接尝试写入文件而不先查看环境。加入这几条规则后工具调用的准确率明显提升。2.3 改造后的流转现在流程变成了AgentRuntime - TaskPolicy (判断模式) - ContextBuilder.build() (生成 BuiltContext) - Engine (直接使用 context.systemPrompt 和 context.userPrompt) - ModelClient / ToolRegistryEngine 不再自己拼接 prompt只负责消费上下文。这样一来以后任何上下文相关的改动加记忆、加摘要、加 token 预算都只改ContextBuilder一个地方Engine 保持不变。2.4 一个小插曲toolWorkflow 要不要写入 systemPrompt我最初纠结过是把toolWorkflow作为一个独立字段传给 Engine还是直接拼入 systemPrompt。最后我选择了后者 —— 直接在ContextBuilder里把工作流规则转成自然语言拼入 systemPrompt因为模型只认文本给它结构化数据它也用不上除非我们做 function calling但那是另一回事。这样 Engine 拿到的是一个完整的 systemPrompt 字符串直接传给模型简单省事。三、第二件事Trace Report让 JSON 不再晦涩难懂3.1 之前有 Trace但等于没有项目一直有Trace Logger和Trace Store每次运行会生成一个runs/{runId}.json里面记录了所有事件run_started,text_delta,tool_call,tool_result,error,done。但原始的 JSON 数据格式如下{type:tool_call,payload:{tool:read_file,args:{path:README.md}},timestamp:...}{type:tool_result,payload:{ok:false,error:file not found},timestamp:...}对于简单的成功案例尚可应付但一旦出现失败我就需要在几十行 JSON 数据中来回翻找定位工具参数、错误信息以及时间顺序过程非常痛苦。3.2 我想要的是什么我想要一份可读的复盘报告能让我一目了然地看到这次任务成功还是失败如果失败是哪一步出了问题调用了哪些工具成功率如何时间线是怎样的而且最好能自动生成无需我手动整理。3.3 动手TraceReport.ts我新增了src/trace/TraceReport.ts并注册了一个 npm scripttrace:report:ts-node src/trace/TraceReport.ts使用方法npm run trace:report--runs/run_xxx.json它会读取指定的 JSON 文件分析事件流然后生成一个同名的runs/run_xxx.report.md文件。3.4 报告长什么样生成的 Markdown 报告大致结构如下# Trace Report - run_xxx ## 任务概览 - runId: run_xxx - mode: tool - startedAt: ... - finishedAt: ... - eventCount: 15 - toolCallCount: 2 - errorCount: 1 ## 用户任务 读取 README2.md 并总结 ## 最终结果 未能读取文件 ## 失败判断 - 类型TOOL_EXECUTION_ERROR - 细节read_file: 文件不存在 - 建议请检查工具参数必要时先读取或搜索可用路径。 ## 工具调用统计 - 工具调用次数2 - 工具结果次数2 - 工具失败次数1 ## 时间线 1. run_started 2. tool_call read_file 3. tool_result read_file failed 4. done这份报告让我5 秒钟就能定位问题而无需翻阅 5 分钟的 JSON 日志。3.5 一个启发失败分类的重要性在实现报告功能时我尝试对失败原因做简单分类TOOL_NOT_FOUND、TOOL_EXECUTION_ERROR、MODEL_ERROR、UNKNOWN。后来发现失败分类直接指向优化方向如果TOOL_NOT_FOUND很多 → 说明 ContextBuilder 没有把可用工具清单说明白。如果路径错误很多 → 说明需要强化list_files工作流。如果工具失败后模型不尝试恢复 → 说明需要 Error Recovery 机制。这让我意识到Trace Report 不仅仅是给开发者看的日志它还是 Harness 自我进化的“体检报告”。未来我甚至可以让 Eval 模块自动分析报告给出优化建议虽然这是后话了。四、上下文和Trace今天这两次改动虽然代码量不大加起来不到 300 行但涉及到Agent 架构的一些更深的理解4.1 上下文是 Agent 的“任务说明书”可以看出Agent 的智能不仅来自模型也来自你如何组织它看到的信息。一个好的上下文构造层能显著减少模型“乱猜”的概率。很多开源框架将其称为PromptBuilder或MemoryManager但本质都一样在模型调用前把该给的信息整理好。4.2 Trace 不是日志是可观测性的骨架普通日志回答“程序崩没崩”Trace 回答“Agent 为什么这么做”。而 Trace Report 则将这种“为什么”以人类友好的方式呈现出来让开发者可以快速复盘从而决定下一步的优化方向。没有 Trace 的 Agent 就像没有仪表盘的汽车——能开但不敢开快。4.3 架构分层让“以后再说”变得可行以前总担心“现在设计不好以后加功能会很乱”。但现在只要把职责分清楚比如 ContextBuilder 只负责构造Engine 只负责执行以后加 memory、加摘要、加 token 预算都非常自然 —— 因为它们都属于“上下文构造”的范畴直接放进 ContextBuilder 就完事了。五、下一步的计划更细的 Trace 事件增加context_built,engine_started,model_request,model_response等事件让 Report 更丰富。错误恢复机制当工具返回okfalse时自动让模型根据error.suggestion的建议重试而不是直接结束。工具扩展search_web,run_shell已经在路上。多轮对话记忆把历史对话摘要也放进 ContextBuilder。预计下次更新将包含其中一项欢迎持续关注。六、一点感想写给也在造轮子的你如果你也在自己写 Agent 框架我强烈建议先别追求功能多先追求看得清。一个能跑但经常出错的 Agent 没什么用但一个能让你快速定位问题的 Agent你会越用越顺手越改越好。今天这两件事本质上都是在提升可观测性Context Builder 让“给模型看了什么”变得可观测Trace Report 让“发生了什么”变得可观测。没有可观测性优化就是瞎猜。本文首发于 CSDN作者 [你的名字]转载请注明出处。源码仍在本地迭代稳定后会开源欢迎留言交流。