
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 URLhttps://taotoken.net/apiAPI Key在控制台的 API Keys 页面创建形如sk-...Model ID具体调用的模型标识比如claude-sonnet-4-5或你账号下可用的其他模型 ID获取路径很直接先到官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content注册登录然后进控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite创建 API Key。如果你打算长期跑编码类 Agent可以顺带看一下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite它更适合高频、长时间的编码场景。这里要强调一个设计原则Harness 的状态层不依赖模型。你的.agent/progress.json、handoff.md、checkpoints/这些文件格式是固定的换任何模型都能读。模型只是执行者状态才是资产。很多长任务项目失败就是因为把状态和某次对话绑死了一换会话就全丢。如果你用的是 Claude Code 这类工具接入时同样填这三件套Base URL 填https://taotoken.net/apiKey 填控制台创建的 KeyModel ID 填你选定的模型。具体接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite里面有各客户端的配置示例。想先验证模型是否调通可以直接用模型对话页面https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite发一条测试消息确认返回正常再进 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或者应用 diffgit 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.logpnpm test tests/auth/callback.test.ts 21 | tee -a .agent/verification.log第六步生成新的 handoff artifact 再继续。新会话基于当前状态写一份新的handoff.md然后才开始执行next里的动作。这六步看起来繁琐但它解决了一个核心问题恢复不是简单接着跑而是先确认恢复点可信。跳过验证直接继续等于在不确定的地基上盖楼。如果你用 API 方式驱动 Agent恢复时的请求体大致长这样import os from openai import OpenAI client OpenAI( base_urlhttps://taotoken.net/api, api_keyos.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( modelclaude-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(funexpected 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_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite更适合高频场景只是验证模型调用用模型对话页面https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite就够接入和排障细节看文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewriteKey 在控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite创建。先把.agent/这套状态文件跑起来比换任何模型都更能提升长任务的稳定性。