<?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>Checkpoint on Weiuou的博客</title><link>https://blog.weiuou.top/tags/checkpoint/</link><description>Recent content in Checkpoint 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>Tue, 07 Jul 2026 20:35:39 +0800</lastBuildDate><atom:link href="https://blog.weiuou.top/tags/checkpoint/index.xml" rel="self" type="application/rss+xml"/><item><title>Agent开发笔记（5）Context Manager 和 Recovery Checkpoint</title><link>https://blog.weiuou.top/posts/agent-dev-notes-5-context-manager-recovery-checkpoint/</link><pubDate>Tue, 07 Jul 2026 20:35:39 +0800</pubDate><guid>https://blog.weiuou.top/posts/agent-dev-notes-5-context-manager-recovery-checkpoint/</guid><description>在 Sandbox 和 Permission 之后，我给 Mini Agent Harness 加上了 TaskState、Context Pack、checkpoint / resume 和工具结果压缩，让 Agent 在长任务中不只安全执行，还能记住进度并从失败后恢复。</description><content:encoded><![CDATA[<p>上一篇我主要做的是 Agent harness 的 Sandbox 和 Tool Permission。</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></code></pre></div><p>所以我加了 shell policy、项目目录边界、风险等级、approval 和安全 eval。今天没有继续堆安全规则，而是换了一个方向：</p>
<blockquote>
<p>Agent 已经能安全地做事之后，它怎么在长任务里不迷路？如果中途失败或中断，它怎么知道之前做到哪一步？</p>
</blockquote>
<p>这就是 Context Manager 和 Recovery Checkpoint 要解决的问题。</p>
<h2 id="为什么-context-不等于-chat-history">为什么 Context 不等于 Chat History？</h2>
<p>最直接的做法当然是把所有历史消息都塞回模型。用户说了什么，模型想了什么，调用了什么工具，工具返回了什么，全部保留。这在短 demo 里能工作，但长任务里很快会出问题。token 成本爆炸。工具输出、文件内容、测试日志、目录列表都会迅速撑大上下文；上下文污染。旧的失败尝试、过期假设、重复工具结果和无关输出会混在一起，模型反而更难判断下一步；恢复困难。进程中断之后，如果系统只保存一坨聊天历史，恢复时还要重新从里面推断进度</p>
<p>所以今天的核心判断是：</p>
<blockquote>
<p>Chat history 是原始过程，Context Pack 是当轮推理材料，TaskState 是当前任务状态。</p>
</blockquote>
<p>这三者不能混成一个东西。</p>
<h2 id="今天加了什么">今天加了什么？</h2>
<p>这次主要新增了两个模块：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">agent/state.py
</span></span><span class="line"><span class="cl">agent/context_manager.py
</span></span></code></pre></div><p><code>agent/state.py</code> 里定义了 <code>TaskState</code>，类似于：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">已阅读入口与核心循环：agent.py、agent/core.py
</span></span><span class="line"><span class="cl">已阅读工具与沙箱模块：agent/tools.py、agent/permissions.py、agent/sandbox.py
</span></span><span class="line"><span class="cl">已阅读上下文模块：agent/state.py、agent/context_manager.py、context_compressor.py
</span></span></code></pre></div><p>也就是说：</p>
<blockquote>
<p>trace 记录事实，state 记录语义进度。</p>
</blockquote>
<h2 id="context-pack-长什么样">Context Pack 长什么样？</h2>
<p><code>agent/context_manager.py</code> 里实现了：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">build_context_pack</span><span class="p">(</span><span class="n">task_state</span><span class="p">,</span> <span class="n">recent_trace</span><span class="p">,</span> <span class="n">tool_summaries</span><span class="p">,</span> <span class="n">max_chars</span><span class="o">=</span><span class="mi">12000</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span></code></pre></div><p>我的实现方式是一个 Markdown 文本，大概包含：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">User Goal
</span></span><span class="line"><span class="cl">Current Plan
</span></span><span class="line"><span class="cl">Completed Steps
</span></span><span class="line"><span class="cl">Open Questions
</span></span><span class="line"><span class="cl">Important Facts
</span></span><span class="line"><span class="cl">Files Touched
</span></span><span class="line"><span class="cl">Commands Run
</span></span><span class="line"><span class="cl">Error History
</span></span><span class="line"><span class="cl">Resolved Errors
</span></span><span class="line"><span class="cl">Recent Tool Calls
</span></span><span class="line"><span class="cl">Recent Error
</span></span><span class="line"><span class="cl">Next Action
</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">系统提示
</span></span><span class="line"><span class="cl">用户原始目标
</span></span><span class="line"><span class="cl">新的 Context Pack
</span></span><span class="line"><span class="cl">最近 1-2 轮压缩后的 assistant / tool 消息
</span></span></code></pre></div><p>这让我对上下文管理的理解更具体了：</p>
<blockquote>
<p>Context Manager 不是简单地“删掉旧消息”，而是把历史过程转成当前任务材料。</p>
</blockquote>
<h2 id="工具结果为什么要压缩">工具结果为什么要压缩？</h2>
<p>今天还做了工具结果摘要。完整工具结果仍然保存进 trace，但进入模型上下文之前会压缩。规则大概是：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">stdout / stderr 超过 4000 字符：保留 head + tail + summary
</span></span><span class="line"><span class="cl">read_file 内容超过 8000 字符：保留路径、行号范围和片段
</span></span><span class="line"><span class="cl">搜索结果超过 5 条：保留前 5 条和省略计数
</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">trace 保存完整结果
</span></span><span class="line"><span class="cl">context 只放压缩结果
</span></span></code></pre></div><p>这是我今天觉得很重要的设计分界。如果为了省 token，把 trace 里的完整工具结果也删掉，后面就没法复盘。如果为了复盘，把完整工具结果都塞回 context，长任务又会被噪声淹没。所以 trace 和 context 必须分离。</p>
<h2 id="checkpoint--resume-是怎么做的">checkpoint / resume 是怎么做的？</h2>
<p>现在每个任务会保存到：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">runs/{task_id}/trace.jsonl
</span></span><span class="line"><span class="cl">runs/{task_id}/state.json
</span></span><span class="line"><span class="cl">runs/{task_id}/context_pack.md
</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 resume &lt;task_id&gt;
</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">读取 state.json
</span></span><span class="line"><span class="cl">读取 trace.jsonl
</span></span><span class="line"><span class="cl">写入 resume_started 事件
</span></span><span class="line"><span class="cl">从 trace 重建最近 1-2 轮压缩后的消息
</span></span><span class="line"><span class="cl">重新构造 Context Pack
</span></span><span class="line"><span class="cl">从旧 trace 最大 step 后继续执行
</span></span><span class="line"><span class="cl">每次工具结果或最终回答后重新保存 checkpoint
</span></span></code></pre></div><p>这里有个关键点：</p>
<blockquote>
<p>resume 不是从头重跑，也不是把完整 trace 全部塞给模型。</p>
</blockquote>
<p>它是从 <code>state.json</code> 和 <code>trace.jsonl</code> 里恢复任务现场，再给模型一个短的、结构化的上下文包。</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;读取 readme.md，总结一句。&#34;</span> --task-id demo_resume --max-steps <span class="m">1</span>
</span></span><span class="line"><span class="cl">python3 agent.py resume demo_resume --max-steps <span class="m">10</span>
</span></span></code></pre></div><p>第一条命令故意让任务在 1 步后停下。第二条命令从 checkpoint 恢复，最终继续完成。trace 里能看到：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">resume_started
</span></span><span class="line"><span class="cl">task_state_updated
</span></span><span class="line"><span class="cl">context_pack_built
</span></span><span class="line"><span class="cl">checkpoint_saved
</span></span><span class="line"><span class="cl">tool_result_compressed
</span></span></code></pre></div><p>这说明恢复路径确实跑到了。</p>
<h2 id="为什么要加-error_history-和-resolved_errors">为什么要加 error_history 和 resolved_errors？</h2>
<p>一开始 <code>TaskState</code> 只有 <code>last_error</code>。如果任务最终完成，<code>last_error = None</code> 是合理的。但这会带来一个问题：中间发生过的错误会消失。比如一个任务可能经历过一些不熟悉环境导致的小错误，而这些错误很多时候llm可以自己纠正，例如使用了不允许的命令，shell编写错误等，最后llm自己恢复成功了。此时 <code>last_error = None</code>，但这不代表任务过程中没有错误。</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_history
</span></span><span class="line"><span class="cl">resolved_errors
</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;last_error&#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_history&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;max_steps: 达到最大循环次数 1，任务未完成。&#34;</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;resolved_errors&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;max_steps: 达到最大循环次数 1，任务未完成。&#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>这对 eval 很重要。恢复能力不是“没有错误”，而是错误出现后，系统保留了状态，并能继续推进。而且这样也更有助于后续对harness修改后的效果评估，可能升级后中途出现的小错误明显变少了，但是如果只有<code>last_error</code>则不能直观的看到这个优化的效果。</p>
<h2 id="eval-结果和暴露的问题">Eval 结果和暴露的问题</h2>
<p>今天跑过完整 13 条 eval。结果是：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">total: 13
</span></span><span class="line"><span class="cl">passed: 10
</span></span><span class="line"><span class="cl">failed: 3
</span></span><span class="line"><span class="cl">pass_rate: 76.9%
</span></span><span class="line"><span class="cl">security: 4/4
</span></span></code></pre></div><p>Context Manager 相关统计里能看到：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">state_updates: 51
</span></span><span class="line"><span class="cl">context_packs_built: 106
</span></span><span class="line"><span class="cl">tool_results_compressed: 10
</span></span><span class="line"><span class="cl">resume_events: 1
</span></span><span class="line"><span class="cl">checkpoint_saves: 51
</span></span><span class="line"><span class="cl">tasks_with_compression: 13
</span></span><span class="line"><span class="cl">tasks_with_resume: 1
</span></span></code></pre></div><p>Eval结果基本符合预期，三个预期失败的，不过有个明显问题：trace 现在非常密，一次较长任务里可能出现很多模型调用，function calling，上下文压缩等等。如果在这些节点都添加checkpoint的话，很明显会拖累整体的运行效率，后续考虑把 checkpoint 策略改成必须checkpoint和可选checkpoint，减少不必要的性能消耗。</p>
<h2 id="核心收获">核心收获</h2>
<p>今天最大的收获不是“加了一个 resume 命令”，而是实现了最基本的Context Manager，给我的harness添加了类似于Codex中的session恢复能力；同时实现了state的。如果说前几篇是在让 Agent “能做事”和“安全做事”，那今天这一步是在让它开始具备长期任务系统的样子。</p>
<h2 id="相关笔记">相关笔记</h2>
<ul>
<li><a href="/posts/agent-dev-notes-2-mini-agent-harness/">Agent开发笔记（2）从 Agent Loop 到 Mini Agent Harness</a></li>
<li><a href="/posts/agent-dev-notes-3-agent-eval-harness/">Agent开发笔记（3）从Agent Eval看为什么llm和harness是共同优化的整体</a></li>
<li><a href="/posts/agent-dev-notes-4-code-agent-sandbox-tool-permission/">Agent开发笔记（4）Code Agent 的 Sandbox 和 Tool Permission</a></li>
<li><a href="/posts/agent-development-common-problems/">Agent开发中的常见问题</a></li>
<li><a href="/topics/ai-agent-development/">AI Agent 开发</a></li>
</ul>
]]></content:encoded></item></channel></rss>