1. 从「失败清零」到「可续跑」:Harness 的检查点卡在哪一步
你在本地把 LongHorizon-Harness 跑起来,任务拆到第 40 步时终端抛出一个连接超时,前面的抓取结果全在内存里,进程一退,进度归零。这时候你会发现,检查点机制写得再漂亮,只要模型后端每次连接的鉴权和地址不一致,恢复链路就断在第一跳。所以先把后端入口固定下来:到 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=longhorizon_checkpoint 申请一个 Key,后面所有 Harness 的执行步骤、验证步骤、恢复步骤都走同一个 Base URL,检查点里记录的base_url才不会今天记 A、明天跑 B。
LongHorizon-Harness 这类长任务框架,本质上是给现成 agent 套一层执行循环:规划 → 执行 → 验证 → 落检查点或回滚 → 继续循环,直到目标达成。它不训练模型,也不替换 Claude Code、Codex 这些执行体,它管的是"这几十小时怎么不断电"。原文强调过一点:每一步用全新上下文执行,避免前文污染;每一步只接受"已验证的进度";失败不从头再来,从最近一个检查点恢复。这三个特性听起来是调度问题,实际落地时会立刻变成配置问题——因为"全新上下文执行"意味着每一步都要重新建立一次模型连接,如果这一步的连接配置散落在环境变量、项目配置、CLI 参数三个地方,那么你恢复出来的第 41 步和第 40 步极可能不是同一个后端。
工作流编排开发者最容易踩的坑不是逻辑写错,而是状态漂移:检查点文件里写着用自己的 key 跑了 40 步,恢复时 shell 里 export 的是另一个 key,鉴权通过但落到了不同的账户或不同的模型别名上,验证器判定结果不一致,于是整个恢复流程判为失败,又回到"失败清零"的原点。这篇就以"把 Harness 的检查点稳定接到 TaoToken 后端"为主线,给出一套可复制、可恢复、可审计的配置。
先明确三个事实,后面所有命令都基于它们:
- 模型后端统一走
https://taotoken.net/api,这是 Base URL,不加任何查询参数,工具配置里填的就是它; - 密钥统一用环境变量注入,代码里只出现占位符
YOUR_API_KEY,不把 key 写进检查点正文; - 检查点文件里要冗余记录当次执行所用的 Base URL 和模型名,恢复前先做一致性校验。
2. 先拿 Key、再定 Base URL:TaoToken 接入的最小准备
不管你的 Harness 最终调用的是 Claude Code、Codex 还是自己封的 OpenAI 兼容客户端,接入顺序都是一样的:先有 Key,再配 Base URL,最后才动 Harness 的调度配置。Key 在控制台创建,地址是 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=longhorizon_key ,创建时按用途拆开:长任务执行一个 key、验证环节一个 key、调试另一个 key。拆开的好处是,当 Harness 跑了几十小时之后你要审计"到底哪一步调用了什么",可以直接按 key 维度看用量归属,而不是所有步骤混在一张账单里。
拿到 Key 之后,先在 shell 里做最小验证,不要一上来就改 Harness 的调度器。用一个最普通的 OpenAI 兼容调用确认网络和鉴权都通:
export TAOTOKEN_BASE_URL="https://taotoken.net/api" export TAOTOKEN_API_KEY="YOUR_API_KEY" curl -sS "$TAOTOKEN_BASE_URL/models" \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" | head -c 400这一步的目的是把"网络层 / 鉴权层 / 模型层"三个可能出错的环节先分开。如果这里返回 401,问题在 Key;返回 404,通常是 Base URL 多写了/v1或者少写了路径,注意https://taotoken.net/api是工具里填的完整前缀,不要再拼/v1/v1;如果一直超时,先检查本机代理和 DNS,不要在 Harness 里调参浪费时间。
确认连通后,把这两个变量写进一个独立的 env 文件,便于 Harness 每个子进程都继承到同一份配置:
# ~/.config/taotoken/harness.env export TAOTOKEN_BASE_URL="https://taotoken.net/api" export TAOTOKEN_API_KEY="YOUR_API_KEY" export TAOTOKEN_DEFAULT_MODEL="claude-sonnet-4-5" export TAOTOKEN_VERIFY_MODEL="claude-haiku-4-5"执行长任务前用source加载,例如在 Harness 的启动脚本里第一行就写:
set -a source ~/.config/taotoken/harness.env set +a python -m harness run --task tasks/research.yamlset -a的作用是把 source 进来的变量自动 export 给子进程,这一点很关键:Harness 拆步骤时往往会 fork 新的执行进程,如果变量只在当前 shell 里存在,子进程拿不到,恢复时就会退化成"没有配置"的默认后端,前面 40 步的检查点等于白建。
3. Claude Code 三件套:settings.json、项目级配置与 CC Switch 的一致性
如果你的 Harness 把 Claude Code 当作执行体,配置就要按 Claude Code 自身的三层逻辑来组织,不能混用。第一层是全局~/.claude/settings.json,第二层是项目级.claude/settings.json,第三层是运行时的环境变量。三者优先级从低到高,Harness 恢复时如果用不同层级的配置,就会出现"同一份检查点、两条连接路径"的问题。
全局配置示例:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY", "ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4-5", "ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-haiku-4-5", "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1" } }项目级.claude/settings.json只保留与任务相关的模型选择,不要在这里重复写 key,避免 Harness 打包检查点时把密钥一并落盘:
{ "env": { "ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4-5" }, "permissions": { "allow": ["Bash(python:*)", "Read", "Write"] } }至于 CC Switch 这类多配置切换工具,它的"三件套"一般指三份可切换的配置源:Claude Code 的settings.json、Codex 的config.toml、以及一份共用的密钥/环境文件。用 CC Switch 做切换本身没问题,但接入 Harness 时要保证三件事:第一,三份配置里的 Base URL 都是https://taotoken.net/api,不能用某一份走旧的中转地址;第二,共用密钥文件只保留一份 key,避免切换后实际调用方发生变化;第三,切换动作不要发生在恢复过程中间,否则检查点里的provider字段会和运行时不符。简单说,CC Switch 负责的是"你想换"的时候换,Harness 负责"它想恢复"的时候稳定,两者职责不能交叉。
Claude Code 这一层的更多细节(包括环境变量优先级、模型别名映射、权限白名单写法)可以参考官方接入文档:https://taotoken.net/doc/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_content=longhorizon_cc_doc ,文档里的字段名和本文保持一致,照着填即可。
4. Codex 的 config.toml:不要把 ANTHROPIC_* 套过来
这是最容易出错的一节。Codex 走的是 TOML 配置体系,字段是model_provider/base_url/env_key/wire_api这一套,不是ANTHROPIC_*。把 Claude Code 的配置直接复制到 Codex,表现通常是配置解析通过但请求打到错误的端点,或者鉴权头格式不对,报 401 但 key 明明是对的。
正确写法示例:
# ~/.codex/config.toml model = "gpt-5-codex" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "responses" [profiles.longtask] model_provider = "taotoken" model = "gpt-5-codex" approval_policy = "on-request"关键点有三个:
base_url只写到https://taotoken.net/api,不要在后面拼/chat/completions,路径由 Codex 自己按wire_api拼接;env_key指向环境变量名,不写 key 本身,Harness 恢复时只要环境变量在,鉴权就能重建;- 高位长任务建议单独开 profile,把
approval_policy设成on-request,避免执行体在某一步卡在交互确认上,让整个循环超时、误判为步骤失败。
验证 Codex 配置是否被正确加载,可以先用一次最小调用:
codex exec --profile longtask "reply with the single word: ready"如果返回正常,说明 Codex 侧已接通。这里要强调一句:同一台机器上,Claude Code 和 Codex 可以共用同一个环境变量文件,但配置文件必须各写各的,Claude Code 用 JSON 的ANTHROPIC_*,Codex 用 TOML 的model_providers,禁止交叉粘贴。Harness 在恢复时按检查点里记录的harness_backend字段选择加载哪一份,所以检查点里也要把这一项写清楚。
5. 给 Harness 写一个可复现的检查点模块
Harness 本身可能有自己的检查点实现,但作为编排开发者,你至少要知道它落在哪、写了什么、恢复时按什么字段校验。下面是一个可直接运行的最小检查点模块,用 OpenAI 兼容客户端调 TaoToken,把每一步的状态、摘要、所用后端起止信息都落盘。它不是替代 Harness,而是给你一个可对照、可审计的落地样本。
# harness_checkpoint.py import hashlib import json import os import time from pathlib import Path from openai import OpenAI BASE_URL = os.environ.get("TAOTOKEN_BASE_URL", "https://taotoken.net/api") MODEL = os.environ.get("TAOTOKEN_DEFAULT_MODEL", "claude-sonnet-4-5") client = OpenAI(base_url=BASE_URL, api_key=os.environ["TAOTOKEN_API_KEY"]) CKPT_DIR = Path("./.harness/checkpoints") CKPT_DIR.mkdir(parents=True, exist_ok=True) def digest(step_id: int, payload: dict) -> str: raw = json.dumps({"step": step_id, "payload": payload}, sort_keys=True) return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:16] def save_checkpoint(step_id: int, state: dict, verified: bool) -> Path: record = { "step": step_id, "digest": digest(step_id, state), "verified": verified, "created_at": time.time(), "backend": { "base_url": BASE_URL, "model": MODEL, }, "state": state, } path = CKPT_DIR / f"step-{step_id:04d}.json" path.write_text(json.dumps(record, ensure_ascii=False, indent=2)) return path def latest_verified_checkpoint() -> Path | None: files = sorted(CKPT_DIR.glob("step-*.json")) for path in reversed(files): record = json.loads(path.read_text()) if record.get("verified"): return path return None def run_step(step_id: int, instruction: str, state: dict) -> dict: # 每一步用全新上下文执行,不携带前文消息历史 resp = client.chat.completions.create( model=MODEL, messages=[ {"role": "system", "content": "You are a bounded task executor."}, {"role": "user", "content": instruction}, ], timeout=120, ) output = resp.choices[0].message.content new_state = {**state, "last_output": output} save_checkpoint(step_id, new_state, verified=True) return new_state这段代码对应 Harness 循环里的三个关键动作:执行用全新消息列表(不复用历史)、每步结束立刻落检查点、检查点里冗余记录base_url与model。恢复时只要读最后一个verified == true的文件即可,不需要自己去猜进度。
目录结构建议保持稳定,方便审计脚本扫描:
.harness/ ├── checkpoints/ │ ├── step-0038.json │ ├── step-0039.json │ └── step-0040.json ├── audit.log └── resume.json6. 恢复实操:从最近一个已验证检查点续跑的三条命令
检查点有了,恢复命令就不该是"重跑一遍"。下面三条命令分别对应三种常见恢复场景,按实际入口替换命令名即可,逻辑是通用的。
第一条:列出所有检查点,确认最近一个已验证步骤在哪。
python - <<'PY' import json from pathlib import Path for path in sorted(Path(".harness/checkpoints").glob("step-*.json")): rec = json.loads(path.read_text()) flag = "OK " if rec.get("verified") else "BAD" print(f'{flag} {path.name} backend={rec["backend"]["model"]} at={rec["backend"]["base_url"]}') PY第二条:从指定检查点恢复执行,只重做失败的那一步及其后续。
set -a source ~/.config/taotoken/harness.env set +a python -m harness resume \ --from-checkpoint .harness/checkpoints/step-0040.json \ --task tasks/research.yaml \ --max-retries 3 \ --backend claude-code第三条:恢复前做一致性校验,避免"用新 key 跑旧检查点"。
python - <<'PY' import json import os from pathlib import Path rec = json.loads(Path(".harness/checkpoints/step-0040.json").read_text()) expected = os.environ.get("TAOTOKEN_BASE_URL", "https://taotoken.net/api") assert rec["backend"]["base_url"] == expected, "base_url 与当前环境不一致,拒绝恢复" print("checkpoint backend consistency: ok") PY第三条命令看起来多余,但它是防"状态漂移"的最后一道闸。很多 Harness 恢复失败的案例,根因不是逻辑而是环境:A 环境写的检查点,B 环境去恢复,base_url一样但 key 归属不同,验证器给出的判定和之前不一致,循环就反复失败。把校验前置,能省掉大量排查时间。恢复之后再跑一次审计,确认每一步的输入输出都可追溯:
python -m harness audit \ --dir .harness/checkpoints \ --since 24h \ --output .harness/audit.log审计日志里至少要能看到:步骤号、摘要、是否验证通过、所用模型、所用 Base URL 的 host 部分(不记录 key)。能在事故后回答"第 37 步为什么被判定失败",你的循环才算真正可运维。
7. 排障清单与边界说明
把常见故障按层拆开,比在 Harness 里加日志更快。
鉴权层。401 基本只有三种可能:key 没注入进子进程、key 拼写/换行有问题、请求头格式不对。Claude Code 用ANTHROPIC_AUTH_TOKEN,Codex 用env_key指向的环境变量,两者不要互相复制。检查方式是把 Harness 启动脚本里的env | grep -i taotoken打出来,确认每个 fork 出来的进程都能看到。
路由层。404 通常来自 base_url 拼错,典型是https://taotoken.net/api/v1或https://taotoken.net/api/chat/completions。Base URL 就填https://taotoken.net/api,路径交给工具自己处理。超时则优先检查本机网络和并发数,长任务里一步超时不等于任务失败,Harness 应该按重试策略处理,而不是直接把整条链判死。
状态层。恢复后行为异常,先怀疑两件事:检查点里记录的模型和当次环境里的模型不一致;或者上一步verified为 false 却被当成可恢复点。恢复命令里指定检查点时,务必使用最近一个verified == true的文件。
上下文层。全新上下文执行意味着每一步都看不到前文,这对减少污染有好处,但也要求检查点里的state字段足够完整——凡是下一步需要的信息,都必须显式落盘,不能指望模型"记得"。这是排查"恢复后胡干"的第一切入点。
最后说清楚边界。这层循环解决的是执行的持久性和可恢复性,不是能力本身。执行体不会操作的软件,套上检查点也不会突然会;它在长任务里的价值,是把"失败清零"变成"从最近一个已验证点继续"。也正因为它控制的是连接、状态和恢复,后端配置的一致性才如此关键——把 Base URL 固定成https://taotoken.net/api、把 key 收进环境变量、把检查点写全,这三件事做到位,几十小时的连续执行才有可复盘的基础。
8. 下一步:把执行、验证、恢复都统一到同一条链路上
如果你正准备把 Harness 的循环接到实际项目上,建议按下面的顺序走一遍,每一步都有对应的入口可以对照:
第一,想先确认模型侧行为是否符合预期,用模型对话页面直接试一次长指令,观察返回结构:https://taotoken.net/models/detail/chat?utm_source=taotoken_aicg_blog_end&utm_content=longhorizon_chat 。第二,长任务涉及大量步骤和并发,先了解 Coding Plan 的配额与限流规则,再决定检查点间距:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=longhorizon_plan 。第三,按用途分别创建 key,把执行 key、验证 key、调试 key 分开:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=longhorizon_keys 。第四,回到 Claude Code 接入文档,核对settings.json字段与环境变量优先级:https://taotoken.net/doc/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_content=longhorizon_doc 。
把官网入口留在这里,方便你按需回看:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=longhorizon_footer 。执行这条链路时记住三句话:Base URL 固定写https://taotoken.net/api;key 只放环境变量,占位符用YOUR_API_KEY;检查点必须冗余记录后端信息,恢复前先校验。做到这三点,Harness 的"检查点 / 恢复"才真正接得上,长任务也才有可能从"干几分钟"走到"连续扛几十小时"。