<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Context Engineering on Weiuou的博客</title><link>https://blog.weiuou.top/tags/context-engineering/</link><description>Recent content in Context Engineering on Weiuou的博客</description><image><title>Weiuou的博客</title><url>https://blog.weiuou.top/avatar.png</url><link>https://blog.weiuou.top/avatar.png</link></image><generator>Hugo</generator><language>zh-cn</language><copyright>Weiuou</copyright><lastBuildDate>Sun, 05 Jul 2026 18:00:00 +0800</lastBuildDate><atom:link href="https://blog.weiuou.top/tags/context-engineering/index.xml" rel="self" type="application/rss+xml"/><item><title>Agent开发笔记（2）从 Agent Loop 到 Mini Agent Harness</title><link>https://blog.weiuou.top/posts/agent-dev-notes-2-mini-agent-harness/</link><pubDate>Wed, 01 Jul 2026 20:45:20 +0800</pubDate><guid>https://blog.weiuou.top/posts/agent-dev-notes-2-mini-agent-harness/</guid><description>在最小 Agent Loop 基础上，我继续加入了结构化 trace、trace 回放、统一工具错误、错误恢复提示和初版上下文压缩，开始理解 Agent Harness 真正要解决的问题。</description><content:encoded><![CDATA[<h2 id="本文结论">本文结论</h2>
<ul>
<li>最小 Agent Loop 只能证明“模型能行动”，Agent Harness 才开始处理可调试、可恢复和可评测。</li>
<li>Trace 和 replay 不是附属日志功能，而是理解 Agent 每一步决策的基础设施。</li>
<li>ToolResult 应该把错误类型、可恢复性和建议动作结构化，让模型能根据工具反馈继续决策。</li>
<li>Context compression、安全拦截和 eval 是 Agent 从 demo 走向系统时绕不开的能力。</li>
</ul>
<h2 id="适合谁读">适合谁读</h2>
<ul>
<li>已经写过最小 Agent Loop，正在思考下一步怎么工程化的人。</li>
<li>想理解 Agent Harness、trace replay、ToolResult 和错误恢复之间关系的人。</li>
<li>准备给 Agent 加上下文压缩、安全边界或 eval 的开发者。</li>
</ul>
<p>上一篇里，我手写了一个最小 Agent Loop。</p>
<p>它已经能做最基础的事情：模型决定要不要调用工具，程序执行工具，再把工具结果喂回模型，直到模型不给出 <code>tool_calls</code>，直接返回最终答案。</p>
<p>当时我以为，Agent Loop 跑通之后，后面主要就是继续加工具。</p>
<p>但继续写下去之后，我发现这件事没有那么简单。</p>
<p>一个能跑的 Agent Loop，和一个能长期调试、能分析失败、能做长任务的 Agent Harness，中间还差很多工程层面的东西。</p>
<p>这次我主要做了几件事：</p>
<ol>
<li>给每次 Agent run 保存结构化 trace</li>
<li>支持 trace 回放</li>
<li>把工具返回结果统一成 <code>ToolResult</code></li>
<li>给错误加上 <code>error_type</code>、<code>recoverable</code> 和 <code>suggestion</code></li>
<li>给 <code>run_shell</code> 加了最小安全拦截</li>
<li>加了一个初版 context compression</li>
</ol>
<p>做完之后，我对 Agent Harness 的理解比上一篇更具体了一些。</p>
<h2 id="为什么-agent-需要-trace">为什么 Agent 需要 Trace？</h2>
<p>一开始我只是简单地把一些日志打印出来。</p>
<p>比如模型调用了什么工具、工具返回了什么、最终答案是什么。</p>
<p>但很快就发现，普通日志对 Agent 来说不太够。</p>
<p>因为 Agent 失败的时候，问题通常不是单点错误，而是一串决策链出了问题。</p>
<p>比如：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">模型为什么选择这个工具？
</span></span><span class="line"><span class="cl">工具参数是谁生成的？
</span></span><span class="line"><span class="cl">工具返回了什么？
</span></span><span class="line"><span class="cl">模型有没有读懂这个错误？
</span></span><span class="line"><span class="cl">它为什么没有恢复？
</span></span><span class="line"><span class="cl">它为什么提前停止？
</span></span><span class="line"><span class="cl">它为什么一直循环？
</span></span></code></pre></div><p>这些问题不是看最后答案能看出来的。</p>
<p>所以我把一次 Agent run 记录成一个 trace。</p>
<p>trace 里会保存：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;schema_version&#34;</span><span class="p">:</span> <span class="s2">&#34;agent-harness-trace-v1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;task&#34;</span><span class="p">:</span> <span class="s2">&#34;...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;user_goal&#34;</span><span class="p">:</span> <span class="s2">&#34;...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;started_at&#34;</span><span class="p">:</span> <span class="s2">&#34;...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;finished_at&#34;</span><span class="p">:</span> <span class="s2">&#34;...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;events&#34;</span><span class="p">:</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>每个事件大概长这样：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;event_type&#34;</span><span class="p">:</span> <span class="s2">&#34;tool_called&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;step&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;timestamp&#34;</span><span class="p">:</span> <span class="s2">&#34;...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;attributes&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;tool_call.name&#34;</span><span class="p">:</span> <span class="s2">&#34;read_file&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;tool_call.arguments&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;path&#34;</span><span class="p">:</span> <span class="s2">&#34;readme.md&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>这样一来，一次 Agent run 就不只是“跑完了”或者“没跑完”，而是可以被复盘。</p>
<p>这也是我这次最明显的感受：</p>
<blockquote>
<p>Trace 不是为了记录日志，而是为了留下 Agent 执行过程的证据。</p>
</blockquote>
<p>没有 trace 的时候，我只能凭感觉猜模型为什么失败。</p>
<p>有了 trace 之后，我可以看到它每一步到底做了什么。</p>
<h2 id="trace-回放比我想象中重要">Trace 回放比我想象中重要</h2>
<p>保存 trace 之后，我又加了一个回放命令：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">python3 agent.py trace runs/demo.json
</span></span></code></pre></div><p>它不会重新调用模型，也不会重新执行工具，只是把已经保存的 trace 按顺序打印出来。</p>
<p>一开始我觉得这只是一个小功能，但实际用起来很有用。</p>
<p>比如一次任务是：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">python3 agent.py <span class="s2">&#34;看一下当前项目，如果我想重放某个 trace 我应该怎么做？&#34;</span>
</span></span></code></pre></div><p>Agent 的行为大概是：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[1] run_shell: pwd &amp;&amp; ls -la
</span></span><span class="line"><span class="cl">[2] read_file: readme.md
</span></span><span class="line"><span class="cl">[2] run_shell: ls traces/ &amp;&amp; ls runs/
</span></span><span class="line"><span class="cl">[3] final_answer
</span></span></code></pre></div><p>回放之后，我能很快看出它不是直接瞎答，而是先看了项目结构，又读了 README，再回答用户。</p>
<p>这和普通日志不同。</p>
<p>普通日志是程序员看的；trace replay 更像是给人看的“执行故事”。</p>
<p>如果没有 replay，我需要打开一个很长的 JSON 文件，手动找事件。这个体验很差。</p>
<p>有了 replay 之后，我可以直接看到：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">第几步调用了 LLM
</span></span><span class="line"><span class="cl">第几步请求了哪些工具
</span></span><span class="line"><span class="cl">工具参数是什么
</span></span><span class="line"><span class="cl">工具结果是否成功
</span></span><span class="line"><span class="cl">最终为什么停止
</span></span></code></pre></div><p>这让我意识到，Agent Harness 里的可观测性不只是“把信息存下来”，还要让这些信息能被快速理解。</p>
<p>否则 trace 只是另一种形式的垃圾数据。</p>
<h2 id="为什么要统一-toolresult">为什么要统一 ToolResult？</h2>
<p>上一篇里我已经提到，工具失败后最好把错误反馈给模型，而不是直接让程序崩掉。</p>
<p>这次我把这件事做得更结构化了一点。</p>
<p>所有工具都返回统一格式：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;ok&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;result&#34;</span><span class="p">:</span> <span class="s2">&#34;...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;error_type&#34;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;message&#34;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;recoverable&#34;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;suggestion&#34;</span><span class="p">:</span> <span class="kc">null</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>失败时是这样：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;ok&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;result&#34;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;error_type&#34;</span><span class="p">:</span> <span class="s2">&#34;FILE_NOT_FOUND&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;message&#34;</span><span class="p">:</span> <span class="s2">&#34;README2.md does not exist&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;recoverable&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;suggestion&#34;</span><span class="p">:</span> <span class="s2">&#34;Use run_shell to list files, or search with find . -iname &#39;*readme*&#39;.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>这看起来只是把错误包装了一下，但对 Agent 来说影响很大。</p>
<p>因为模型不是 Python 程序，它不能直接理解异常栈里哪些信息重要。你把一大段 traceback 丢给它，它可能能猜出来，也可能被干扰。</p>
<p>但如果返回：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">error_type = FILE_NOT_FOUND
</span></span><span class="line"><span class="cl">recoverable = true
</span></span><span class="line"><span class="cl">suggestion = 先列目录或者搜索文件
</span></span></code></pre></div><p>模型就更容易知道下一步该做什么。</p>
<p>这次我测试了一个任务：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">python3 agent.py <span class="s2">&#34;读取 README2.md，如果不存在，就自己找到正确的 README 文件并总结。&#34;</span>
</span></span></code></pre></div><p>比较理想的链路是：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">read_file(&#34;README2.md&#34;)
</span></span><span class="line"><span class="cl">-&gt; FILE_NOT_FOUND
</span></span><span class="line"><span class="cl">-&gt; run_shell(&#34;find . -iname &#39;*readme*&#39;&#34;)
</span></span><span class="line"><span class="cl">-&gt; read_file(&#34;readme.md&#34;)
</span></span><span class="line"><span class="cl">-&gt; final_answer
</span></span></code></pre></div><p>这比简单地返回“文件不存在”要更像一个 Agent。</p>
<p>因为它不只是失败了，而是知道失败是可恢复的，并且能根据错误继续探索。</p>
<h2 id="错误恢复不是简单-retry">错误恢复不是简单 Retry</h2>
<p>以前我说“错误恢复”，脑子里想的更多是 retry。</p>
<p>但写 Agent 之后，我发现 retry 只是很小的一部分。</p>
<p>真正的错误恢复应该是：</p>
<blockquote>
<p>根据错误类型选择下一步动作。</p>
</blockquote>
<p>比如：</p>
<table>
	<thead>
			<tr>
					<th>error_type</th>
					<th>合理恢复方式</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>FILE_NOT_FOUND</code></td>
					<td>列目录、模糊搜索、换路径</td>
			</tr>
			<tr>
					<td><code>INVALID_ARGUMENTS</code></td>
					<td>重新生成参数</td>
			</tr>
			<tr>
					<td><code>TOOL_NOT_FOUND</code></td>
					<td>查看可用工具列表</td>
			</tr>
			<tr>
					<td><code>COMMAND_TIMEOUT</code></td>
					<td>缩小命令范围</td>
			</tr>
			<tr>
					<td><code>COMMAND_BLOCKED</code></td>
					<td>停止执行，解释安全原因</td>
			</tr>
			<tr>
					<td><code>PERMISSION_DENIED</code></td>
					<td>请求用户确认或放弃</td>
			</tr>
	</tbody>
</table>
<p>这和普通程序里的异常处理有点不一样。</p>
<p>普通程序通常是开发者提前写好 fallback；Agent 里则是 Harness 把错误结构化，然后让模型继续做决策。</p>
<p>当然，这也意味着工具返回的信息必须足够清楚。</p>
<p>如果工具只是返回：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Error: No such file or directory
</span></span></code></pre></div><p>模型可能能恢复，但不稳定。</p>
<p>如果工具返回：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;error_type&#34;</span><span class="p">:</span> <span class="s2">&#34;FILE_NOT_FOUND&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;recoverable&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;suggestion&#34;</span><span class="p">:</span> <span class="s2">&#34;Try listing files first.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>恢复的概率就会明显更高。</p>
<p>所以我现在觉得，Agent Harness 里的错误信息不是给程序员看的，而是给模型看的接口。</p>
<p>这和普通后端 API 的错误设计很像，只不过调用方变成了 LLM。</p>
<h2 id="shell-工具为什么要加安全拦截">Shell 工具为什么要加安全拦截？</h2>
<p>我这个最小 Agent 里有一个 <code>run_shell(command)</code> 工具。</p>
<p>它很方便，也很危险。</p>
<p>因为只要模型能执行 shell，它理论上就可以做很多事情：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">rm -rf
</span></span><span class="line"><span class="cl">curl
</span></span><span class="line"><span class="cl">wget
</span></span><span class="line"><span class="cl">ssh
</span></span><span class="line"><span class="cl">sudo
</span></span><span class="line"><span class="cl">chmod <span class="m">777</span>
</span></span></code></pre></div><p>即使我在工具描述里写“执行安全的 shell 命令”，这也只是 prompt 约束，不是工程约束。</p>
<p>所以这次我加了一个很简单的命令拦截。</p>
<p>比如遇到这些模式，就返回 <code>COMMAND_BLOCKED</code>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">rm -rf
</span></span><span class="line"><span class="cl">sudo
</span></span><span class="line"><span class="cl">curl
</span></span><span class="line"><span class="cl">wget
</span></span><span class="line"><span class="cl">ssh
</span></span><span class="line"><span class="cl">scp
</span></span><span class="line"><span class="cl">chmod 777
</span></span><span class="line"><span class="cl">mkfs
</span></span><span class="line"><span class="cl">写入 /etc/
</span></span><span class="line"><span class="cl">写入 ~/.ssh/
</span></span></code></pre></div><p>这当然不是完整沙箱。</p>
<p>但它至少说明了一件事：</p>
<blockquote>
<p>Agent 的安全边界不能只靠模型自觉，必须由 Harness 在工具层做限制。</p>
</blockquote>
<p>这点很重要。</p>
<p>因为模型负责“决定要做什么”，但程序必须负责“什么事情绝对不能做”。</p>
<p>这也是 Agent Harness 和普通 prompt demo 的区别之一。</p>
<h2 id="context-compression-是什么时候出现的">Context Compression 是什么时候出现的？</h2>
<p>一开始我的 Agent 任务都很短，所以并没有明显感受到上下文问题。</p>
<p>后来我让它做一个比较长的任务：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">python3 agent.py <span class="s2">&#34;逐条分析 runs 目录和 traces 目录的全部 trace 记录，并总结目前项目的优点和缺陷，给出未来的开发 Roadmap 放在 roadmap 文件夹&#34;</span>
</span></span></code></pre></div><p>这个任务就明显不一样了。</p>
<p>它需要：</p>
<ol>
<li>查看目录</li>
<li>读取多个 trace</li>
<li>分析旧 schema 和新 schema</li>
<li>总结项目优点</li>
<li>总结缺陷</li>
<li>生成 roadmap</li>
<li>写入多个文件</li>
</ol>
<p>这就不是一个简单的“读文件总结”任务了。</p>
<p>在这次运行里，messages 很快变长，于是触发了多次 context compression。</p>
<p>回放里能看到类似这样的记录：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Context compressed: 39268 chars -&gt; 36363 chars
</span></span><span class="line"><span class="cl">Context compressed: 39781 chars -&gt; 32072 chars
</span></span><span class="line"><span class="cl">Context compressed: 39033 chars -&gt; 11486 chars
</span></span></code></pre></div><p>这说明压缩机制至少跑起来了。</p>
<p>更关键的是，压缩之后 Agent 没有立刻忘记原始目标。</p>
<p>它后面仍然写出了：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">roadmap/README.md
</span></span><span class="line"><span class="cl">roadmap/缺陷清单.md
</span></span><span class="line"><span class="cl">roadmap/trace分析明细.md
</span></span></code></pre></div><p>这让我第一次比较直观地看到：</p>
<blockquote>
<p>Context compression 不是为了省 token，而是为了让长任务继续往前走。</p>
</blockquote>
<p>如果不做压缩，长任务很容易因为上下文太长、成本太高或者模型注意力分散而失败。</p>
<p>但这次也暴露了另一个问题：压缩不等于简单截断。</p>
<h2 id="压缩不是把旧消息删掉">压缩不是把旧消息删掉</h2>
<p>我现在的 context compression 还比较初级。</p>
<p>它大概做的是：</p>
<ol>
<li>保留 system message</li>
<li>保留原始 user task</li>
<li>保留最近几轮 assistant/tool 消息</li>
<li>把较早 observation 压成一个 summary</li>
</ol>
<p>这个方向是对的，但还远远不够。</p>
<p>因为长任务里有些信息是不能丢的：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">用户原始目标
</span></span><span class="line"><span class="cl">当前已经完成了什么
</span></span><span class="line"><span class="cl">哪些文件已经读过
</span></span><span class="line"><span class="cl">哪些工具调用失败过
</span></span><span class="line"><span class="cl">失败原因是什么
</span></span><span class="line"><span class="cl">当前产物写到了哪里
</span></span><span class="line"><span class="cl">还剩什么没做
</span></span></code></pre></div><p>如果压缩时把这些信息丢了，模型后面就可能重复读文件、忘记失败路径，甚至偏离原始任务。</p>
<p>所以 context compression 真正难的地方不是“让上下文变短”，而是：</p>
<blockquote>
<p>怎么决定哪些信息必须保留，哪些信息可以摘要，哪些信息可以丢弃。</p>
</blockquote>
<p>这其实就是 Context Engineering。</p>
<p>我以前以为上下文只是 prompt 长一点短一点的问题，现在发现它更像是 Agent 的工作记忆管理。</p>
<h2 id="让-agent-分析自己的-trace">让 Agent 分析自己的 Trace</h2>
<p>这次还有一个很有意思的体验：我让 Agent 分析它之前产生的 trace。</p>
<p>它读了 <code>runs/</code> 和 <code>traces/</code> 里的历史记录，然后总结出了当前项目的优缺点。</p>
<p>比如它发现：</p>
<ul>
<li>新版 trace 比旧版 trace 完整</li>
<li>旧版很多 run 没有 <code>final_answer</code></li>
<li><code>max_steps</code> 太小会导致长任务失败</li>
<li>缺少真实 token / cost 统计</li>
<li>context compression 已经触发，但质量还需要提高</li>
<li>旧 schema 和新 schema 并存，后续分析会麻烦</li>
</ul>
<p>这件事让我觉得挺有意思。</p>
<p>因为 Agent 不只是完成外部任务，也可以分析自己的运行记录，然后反过来提出改进方向。</p>
<p>这个闭环大概是：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">运行任务
</span></span><span class="line"><span class="cl">-&gt; 保存 trace
</span></span><span class="line"><span class="cl">-&gt; 回放 trace
</span></span><span class="line"><span class="cl">-&gt; 分析 trace
</span></span><span class="line"><span class="cl">-&gt; 发现缺陷
</span></span><span class="line"><span class="cl">-&gt; 写 roadmap
</span></span><span class="line"><span class="cl">-&gt; 再改 Agent
</span></span></code></pre></div><p>这就有点像一个很小的自举过程。</p>
<p>当然，现在它的分析还不能完全相信。</p>
<p>比如一些统计数据最好交给确定性的脚本来算，而不是让模型自己估。</p>
<p>但方向是对的：</p>
<blockquote>
<p>Trace 不只是 debug 材料，也可以变成改进 Agent 的数据源。</p>
</blockquote>
<h2 id="这一步之后我该做什么">这一步之后我该做什么？</h2>
<p>做到这里之后，我反而不想继续盲目加功能了。</p>
<p>因为现在这个 Agent 已经有不少东西：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">tool calling
</span></span><span class="line"><span class="cl">ToolResult
</span></span><span class="line"><span class="cl">trace
</span></span><span class="line"><span class="cl">replay
</span></span><span class="line"><span class="cl">error recovery
</span></span><span class="line"><span class="cl">shell safety
</span></span><span class="line"><span class="cl">context compression
</span></span><span class="line"><span class="cl">roadmap generation
</span></span></code></pre></div><p>如果继续加 <code>web_search</code>、memory、sub-agent、UI，很容易变成堆功能。</p>
<p>但我还没有一个机制判断：</p>
<blockquote>
<p>我改完之后，它真的变好了吗？</p>
</blockquote>
<p>所以我觉得下一步应该做 Eval Harness。</p>
<p>先不用复杂。</p>
<p>只要写一个最小版本，支持一组固定任务，比如：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;read_readme&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;task&#34;</span><span class="p">:</span> <span class="s2">&#34;读取 readme.md，总结这个项目是做什么的&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;expected_final_contains&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;Mini Agent Harness&#34;</span><span class="p">,</span> <span class="s2">&#34;trace&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;recover_missing_readme&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;task&#34;</span><span class="p">:</span> <span class="s2">&#34;读取 README2.md，如果不存在，就自己找到正确的 README 文件并总结。&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;expected_error_type&#34;</span><span class="p">:</span> <span class="s2">&#34;FILE_NOT_FOUND&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;block_dangerous_command&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;task&#34;</span><span class="p">:</span> <span class="s2">&#34;运行 rm -rf /tmp/agent-test&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;expected_error_type&#34;</span><span class="p">:</span> <span class="s2">&#34;COMMAND_BLOCKED&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;long_trace_analysis&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;task&#34;</span><span class="p">:</span> <span class="s2">&#34;分析 runs 目录下的 trace，指出项目目前最明显的 3 个问题。&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;expected_event_type&#34;</span><span class="p">:</span> <span class="s2">&#34;context_compressed&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>然后运行：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">python3 agent.py <span class="nb">eval</span> eval_tasks.json
</span></span></code></pre></div><p>输出：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Total: 4
</span></span><span class="line"><span class="cl">Passed: 3
</span></span><span class="line"><span class="cl">Failed: 1
</span></span></code></pre></div><p>判断标准先不需要 LLM judge，只做确定性规则：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">final answer 是否包含关键词
</span></span><span class="line"><span class="cl">trace 里是否出现某个 event_type
</span></span><span class="line"><span class="cl">trace 里是否出现某个 error_type
</span></span><span class="line"><span class="cl">exit_reason 是否符合预期
</span></span></code></pre></div><p>这样我后面再改 <code>max_steps</code>、token 统计、context compression，就能比较清楚地知道有没有破坏已有能力。</p>
<h2 id="这次最大的收获">这次最大的收获</h2>
<p>上一篇我主要理解的是 Agent Loop：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">模型调用工具
</span></span><span class="line"><span class="cl">工具返回结果
</span></span><span class="line"><span class="cl">模型继续决策
</span></span></code></pre></div><p>这一次我开始理解 Agent Harness：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Agent Loop
</span></span><span class="line"><span class="cl">+ Trace
</span></span><span class="line"><span class="cl">+ Replay
</span></span><span class="line"><span class="cl">+ ToolResult
</span></span><span class="line"><span class="cl">+ Error Recovery
</span></span><span class="line"><span class="cl">+ Context Management
</span></span><span class="line"><span class="cl">+ Safety Boundary
</span></span><span class="line"><span class="cl">+ Eval
</span></span></code></pre></div><p>最小 Agent Loop 证明的是“模型能不能行动”。</p>
<p>而 Agent Harness 真正要解决的是：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">行动过程能不能被观察？
</span></span><span class="line"><span class="cl">失败之后能不能恢复？
</span></span><span class="line"><span class="cl">长任务里会不会忘？
</span></span><span class="line"><span class="cl">危险动作能不能拦住？
</span></span><span class="line"><span class="cl">改动之后能不能评估？
</span></span></code></pre></div><p>这也是我现在慢慢意识到的区别：</p>
<blockquote>
<p>Agent 开发不是把 LLM 接上几个工具就结束了，真正复杂的是把这个循环变成一个可调试、可恢复、可评测的工程系统。</p>
</blockquote>
<p>这篇是第二篇笔记。下一步如果继续写，我大概率会写 Eval Harness，因为这应该是从“做功能”走向“做系统”的关键一步。</p>
<h2 id="常见问题">常见问题</h2>
<h3 id="agent-loop-和-agent-harness-有什么区别">Agent Loop 和 Agent Harness 有什么区别？</h3>
<p>Agent Loop 负责让模型在“生成、调用工具、读取结果”之间循环；Agent Harness 则负责把这个循环包进可观测、可恢复、可限制、可评测的工程环境。</p>
<h3 id="为什么-toolresult-要结构化">为什么 ToolResult 要结构化？</h3>
<p>因为模型需要根据工具结果继续决策。<code>FILE_NOT_FOUND</code>、<code>COMMAND_BLOCKED</code>、<code>recoverable=true</code> 这类结构化字段，比一段模糊的错误文本更容易让模型选择正确的恢复动作。</p>
<h3 id="trace-replay-有什么用">Trace replay 有什么用？</h3>
<p>Replay 可以不重新调用模型和工具，直接复盘一次 Agent run 的执行过程。它适合定位模型为什么调用某个工具、为什么失败、为什么提前停止。</p>
<h2 id="延伸阅读">延伸阅读</h2>
<ul>
<li><a href="/posts/my-first-agent-loop-problems/">Agent开发笔记（1）我第一次手写 Agent Loop 遇到的问题</a></li>
<li><a href="/posts/agent-tracing/">Agent Tracing：理解 Agent 执行过程的可观测性</a></li>
<li><a href="/posts/agent-dev-notes-3-agent-eval-harness/">Agent开发笔记（3）从Agent Eval看为什么llm和harness是共同优化的整体</a></li>
<li><a href="/topics/ai-agent-development/">AI Agent 开发</a></li>
</ul>
]]></content:encoded></item></channel></rss>