1. 长任务 Agent 为什么总在第三小时开始迷路
先说结论:Long-running Agent 失败,绝大多数时候不是模型上下文窗口不够大,而是任务状态、工作证据和完成标准没有被外部化。你给它 200K 甚至 1M token,它照样会在第三小时把过期计划、失败路径和当前目标搅在一起,然后自信地宣布"已完成"。
我试过让一个代码 Agent 连续跑一个跨模块重构任务,前 40 分钟非常顺,改文件、跑测试、修报错,节奏像模像样。到第 90 分钟左右开始出现典型症状:它重新去改一个两小时前已经改过的文件,理由是"发现这里可能有问题";它把一次已经验证失败的方案又试了一遍;最后它说"任务完成",但pnpm test根本没跑过。这不是模型笨,是 Harness 没托住它。
一个短任务 Agent 像在白板前解一道题,写完擦掉就完事。一个长任务 Agent 更像接手一个真实项目:读代码、改文件、跑测试、回看错误、修下一处、整理证据交给人。时间一拉长,四类故障必然出现:
- 状态漂移:当前目标被中途的探索带偏,Agent 自己都说不清现在在干什么。
- 证据丢失:改了什么、跑过什么命令、结果如何,全散在对话历史里,翻不回来。
- 验收自嗨:Generator 自己写、自己评、自己宣布通过,缺少独立判断。
- 交接断裂:会话一断,新会话只能从零开始猜,重复劳动。
这四类问题对应的解法,就是本文要讲的 Long-running Agent Harness 工程模式:把状态外部化成 Checkpoint 和 progress file,把阶段切换做成 Context Reset,把会话之间的接力做成结构化的 Handoff Artifact,把验收交给独立的 Evaluator。
一个反直觉但很关键的结论:长任务里,记住一切反而会降低质量。上下文越长,噪声越多,模型越容易把"曾经试过但失败"的路径当成"当前可行"的方案。真正有效的做法是保留任务状态,丢掉不再需要的过程噪声。这也是 Long-running Harness 和普通 agent loop 的分水岭——普通 loop 依赖对话自然延续,Long-running Harness 依赖外部状态接力。
下面按可跟做的顺序展开:先讲 TaoToken 接入前置,再给可复制的配置片段,然后演示一次中断后恢复的完整验证动作,最后把常见报错逐个排掉。
2. TaoToken 前置准备:把模型接入和 Harness 状态层分开
在动手写 Harness 之前,先把模型调用这一层固定下来。原因很简单:长任务里最不该出问题的就是"模型能不能稳定调通",如果每次恢复会话还要折腾鉴权和 Base URL,排障成本会翻倍。
TaoToken 在这里扮演的是统一的模型接入层。它提供 OpenAI 兼容的 API 形态,你可以用同一套 SDK 调用不同模型,Base URL 指向https://taotoken.net/api,鉴权用 API Key。对 Long-running Harness 来说,这一点很重要:Harness 的状态层(Checkpoint、progress file、handoff artifact)应该和模型供应商解耦,换模型不该动状态结构。
你需要准备三样东西,我把它叫做"接入三件套":
- Base URL:
https://taotoken.net/api - API Key:在控制台的 API Keys 页面创建,形如
sk-... - Model ID:具体调用的模型标识,比如
claude-sonnet-4-5或你账号下可用的其他模型 ID
获取路径很直接:先到官网https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=注册登录,然后进控制台https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite创建 API Key。如果你打算长期跑编码类 Agent,可以顺带看一下 Coding Planhttps://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite,它更适合高频、长时间的编码场景。
这里要强调一个设计原则:Harness 的状态层不依赖模型。你的.agent/progress.json、handoff.md、checkpoints/这些文件,格式是固定的,换任何模型都能读。模型只是执行者,状态才是资产。很多长任务项目失败,就是因为把状态和某次对话绑死了,一换会话就全丢。
如果你用的是 Claude Code 这类工具,接入时同样填这三件套:Base URL 填https://taotoken.net/api,Key 填控制台创建的 Key,Model ID 填你选定的模型。具体接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite,里面有各客户端的配置示例。想先验证模型是否调通,可以直接用模型对话页面https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite发一条测试消息,确认返回正常再进 Harness 开发。
前置准备做完,你应该有一个能稳定返回的 API 调用。接下来才是 Harness 本身。
3. 可复制配置:Checkpoint 落盘结构与 Handoff Artifact 模板
这一节给可直接复制的配置。核心是三个文件:progress.json(运行中状态)、handoff.md(阶段交接包)、以及 checkpoint 的落盘约定。
先看工作区目录结构,这是最小可行版本:
workspace/ ├─ .agent/ │ ├─ task.md # 任务规格,开始前写入 │ ├─ progress.json # 当前进度,运行中持续更新 │ ├─ handoff.md # 阶段交接包 │ ├─ verification.log # 验证命令与结果 │ └─ checkpoints/ # checkpoint 引用或元数据 ├─ src/ ├─ tests/ └─ ...progress.json的字段要稳定、粒度要小、结论要可验证。下面是一个可直接用的模板:
{ "current_goal": "修复 OAuth callback 缺少 state 导致 500", "active_files": [ "src/auth/callback.ts", "tests/auth/callback.test.ts" ], "completed": [ "复现失败测试", "新增 state 缺失分支", "补充单元测试" ], "blocked": [], "last_verification": { "command": "pnpm test tests/auth/callback.test.ts", "status": "passed", "timestamp": "2026-01-15T10:32:00Z" }, "next": "运行完整 auth 测试套件,确认没有破坏 refresh token 流程" }注意blocked、last_verification、next这三个字段。长任务失败后,下一轮 Agent 最浪费时间的不是找不到代码,而是不知道上一次为什么停在这里。把停顿原因显式化,恢复时能省掉大量猜测。
handoff.md是阶段结束时的交接包,结构固定,回答六个问题:
# Handoff Artifact ## Goal - 修复登录失败:OAuth callback 在缺少 state 参数时返回 500 ## Constraints - 不修改数据库 schema - 不引入新依赖 - 必须保留现有 public API ## Completed - 定位到 `auth/callback.ts` 缺少 state guard - 新增 state 校验分支 - 补充 2 个单元测试 ## Failed Attempts - 尝试在 middleware 层拦截 state,导致内部 callback 测试失败 ## Workspace Changes - modified: `src/auth/callback.ts` - modified: `tests/auth/callback.test.ts` ## Verification - `pnpm test tests/auth/callback.test.ts` 通过 - `pnpm lint` 失败:历史文件 `legacy.ts` 有未修复问题,和本次修改无关 ## Next Step - 跑完整 auth 测试套件,确认没有破坏 refresh token 流程Checkpoint 的落盘约定,建议用元数据文件而不是直接塞大文件:
{ "checkpoint_id": "ckpt-20260115-1032", "created_at": "2026-01-15T10:32:00Z", "git_commit": "a1b2c3d", "git_diff_ref": ".agent/checkpoints/ckpt-20260115-1032.diff", "task_state_ref": ".agent/progress.json", "verification_ref": ".agent/verification.log" }这里的关键设计是:Checkpoint 只记录环境快照的引用,任务意图放在 progress.json,证据放在 verification.log。三者分离,恢复时各取所需。如果你把意图也塞进 checkpoint,恢复出来的只是一个"不知道为什么被改成这样"的工作区。
Context Reset 的触发条件也要写进配置,别靠 Agent 自己"觉得差不多了"。建议在 Harness 里放一个轻量调度器,观察这些信号:
| 触发信号 | 动作 |
|---|---|
| 上下文占用超过阈值 | Context Compaction |
| 阶段切换(调研→实现→测试) | Context Reset |
| 同一错误连续出现 3 次 | 重新规划 |
| 关键假设被新证据推翻 | 重新规划 |
| 需要独立验收 | 切到 Evaluator 新会话 |
这套配置不华丽,但已经能解决大多数长任务失败的根因:状态不外部化、交接不可读、验证不可复现。
4. 验证请求:一次中断后恢复的完整动作
配置写完,必须验证它真的能恢复。下面演示一次完整的中断恢复流程,你可以照着跑一遍。
第一步,制造一次中断。让 Agent 跑到某个 milestone,比如刚完成src/auth/callback.ts的修改并跑通单元测试,然后手动杀掉会话。此时工作区应该有:更新过的progress.json、追加了记录的verification.log、以及一个 checkpoint 元数据。
第二步,从 checkpoint 恢复工作区。用 git 回到对应 commit,或者应用 diff:
git checkout a1b2c3d # 或者 git apply .agent/checkpoints/ckpt-20260115-1032.diff第三步,读取任务状态。新会话启动后,第一件事不是读代码,而是读.agent/:
cat .agent/progress.json cat .agent/handoff.md tail -n 20 .agent/verification.log确认三件事:当前目标是什么、已完成哪些、下一步是什么。如果progress.json里next字段清晰,这一步应该 1 分钟内完成。
第四步,核对文件变化。看 git diff 是否符合 progress.json 里active_files的描述:
git diff --stat如果 diff 里出现了 progress.json 没记录的文件,说明状态和实际不一致,需要先对齐再继续。
第五步,重跑最近一次验证命令。这一步不能省。恢复点可信不代表验证结果可信,环境可能变了:
pnpm test tests/auth/callback.test.ts把输出追加到verification.log:
pnpm test tests/auth/callback.test.ts 2>&1 | tee -a .agent/verification.log第六步,生成新的 handoff artifact 再继续。新会话基于当前状态写一份新的handoff.md,然后才开始执行next里的动作。
这六步看起来繁琐,但它解决了一个核心问题:恢复不是简单接着跑,而是先确认恢复点可信。跳过验证直接继续,等于在不确定的地基上盖楼。
如果你用 API 方式驱动 Agent,恢复时的请求体大致长这样:
import os from openai import OpenAI client = OpenAI( base_url="https://taotoken.net/api", api_key=os.environ["TAOTOKEN_API_KEY"], ) with open(".agent/progress.json") as f: progress = f.read() with open(".agent/handoff.md") as f: handoff = f.read() resp = client.chat.completions.create( model="claude-sonnet-4-5", messages=[ {"role": "system", "content": "你是长任务 Agent,只基于给定的任务状态继续工作,不要重新规划已完成部分。"}, {"role": "user", "content": f"当前进度:\n{progress}\n\n交接包:\n{handoff}\n\n请执行 next 字段描述的动作。"}, ], ) print(resp.choices[0].message.content)跑通这一步,你就有了一个可交接、可回放的长任务单元。实测下来,恢复后重复劳动的比例会明显下降,因为 Agent 不再靠记忆猜"我干到哪了"。
5. 常见报错排查:401、local proxy failed、reading choices、OAuth
长任务 Harness 跑起来后,报错集中在几个地方。逐个对照排查。
401 Unauthorized。最常见,通常是 API Key 没读到或格式不对。检查环境变量是否真的注入:
echo $TAOTOKEN_API_KEY如果为空,说明 shell 没加载。注意 Key 不要写进代码提交到 git,用.env加.gitignore。另外确认 Base URL 是https://taotoken.net/api,末尾不要多加/v1之类的路径,具体以接入文档为准。
local proxy failed / connection refused。这类报错通常出现在本地网络配置或客户端代理设置上。先确认你的运行环境能正常访问外网 API,再检查客户端里是否误填了代理地址。如果你在 Claude Code 或类似工具里看到这个错,去检查工具的配置文件里 Base URL 是否被改成了本地地址。正确做法是直接填https://taotoken.net/api,不要经过任何本地转发。
reading 'choices' of undefined。这个报错说明响应体结构和你预期的不一样,通常是请求根本没成功,返回的是错误对象而不是标准 completion。排查顺序:先打印完整响应,再确认 model ID 是否正确、Key 是否有该模型权限。代码里加一层防御:
data = resp.model_dump() if hasattr(resp, "model_dump") else resp if "choices" not in data: raise RuntimeError(f"unexpected response: {data}")OAuth 相关报错。注意区分两种:一种是你的 Agent 任务本身在处理 OAuth 代码(比如本文示例的 callback 修复),这类报错属于业务逻辑,看verification.log里的测试输出;另一种是客户端工具自身的登录鉴权,这类要回到控制台确认 Key 状态。两者不要混在一起排查,否则会绕远路。
恢复后状态不一致。如果progress.json说的文件和 git diff 对不上,优先相信 git diff,然后手动修正 progress.json。状态文件是给人(和下一轮 Agent)读的,不是真相来源,工作区才是。
Context Reset 后 Agent 重新规划已完成部分。这是 handoff artifact 写得不清楚。检查Completed和Next Step字段是否明确,system prompt 里是否强调"不要重新规划已完成部分"。必要时把已完成清单直接放进 user message。
排错时记住一个原则:先确认模型调用通,再确认状态文件对,最后才怀疑 Agent 逻辑。顺序反了,会在错误的地方花大量时间。
6. 把长任务拆成可交接单元:从最小版本开始
如果你现在要给一个代码 Agent 增加长任务能力,不必一上来做完整平台。从一个最小版本开始,跑通再迭代。
运行策略按这个顺序落地:
开始前写入task.md,把目标、约束、完成标准写清楚。每完成一个关键步骤更新progress.json,字段保持稳定。每次测试或构建输出追加到verification.log,不要只记结论,命令和原始输出一起留。每个 milestone 生成handoff.md,作为阶段交接包。上下文污染或阶段切换时做 Context Reset,新会话先读.agent/再读代码。Evaluator 只基于 diff、日志和完成标准做判断,不共享 Generator 的长上下文。
这套流程的核心结论可以压缩成几句话:长任务不是靠更长上下文硬撑,而是靠状态外部化;Compaction 和 Reset 解决不同问题,前者整理记忆,后者切断噪声;Handoff Artifact 是交接合同,不是聊天摘要;Checkpoint 只能恢复环境,不能恢复意图,必须和 task state、progress file、git diff、验证日志一起用。
最后给一个实用判断:如果失败后从头重来更便宜,就不要设计复杂恢复;如果失败后无法解释或无法重做,就必须设计恢复和审计。按任务风险分级启用 Harness 强度,比无脑堆角色更划算。
需要长期跑编码类 Agent 的话,Coding Planhttps://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite更适合高频场景;只是验证模型调用,用模型对话页面https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite就够;接入和排障细节看文档https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite,Key 在控制台https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite创建。先把.agent/这套状态文件跑起来,比换任何模型都更能提升长任务的稳定性。