1. 从一次“越跑越笨”的 Agent 说起:Loop 与 Harness 到底在解决什么
如果你最近在折腾编码类 Agent,大概率遇到过这种场景:同一个项目,第一轮对话它能把重构任务拆得明明白白,第二轮换个文件就又开始犯低级错误,第三轮甚至把之前已经修好的接口又改回去了。你明明感觉它“应该学到点什么”,但实际表现是每一轮都在从零推理,经验像沙子一样从指缝漏走。
这不是模型不够强,而是绝大多数 Agent 产品还停留在“预训练 + 固定提示词 + 静态工具”的模式。模型上线那一刻能力就冻结了,交互产生的轨迹用完即弃。清华和 Frontis.AI 联合发布的那篇《Self-Improving Agents in the Era of Experience》把这个矛盾讲得很透:经验时代的核心逻辑是反过来的——Agent 的价值不再取决于预训练数据集有多大,而取决于部署运行过程中持续生成的交互经验能不能被提炼、沉淀、再反哺回系统本身。
这篇论文里有两个词反复出现,也是整个自进化体系的骨架:Loop和Harness。
Loop 是循环,但它不是简单的“思考-行动-观察”任务闭环。论文把 Agent 的演进分成三代:第一代是任务闭环,任务结束循环终止,记忆清空;第二代是跨任务复用,引入持久记忆和技能库,但框架全靠人工配置,上线后不会自己更新;第三代是运行时系统,把 Harness 本身变成可被 Agent 修改和进化的对象。三代演进的核心变化,就是循环从“任务内闭环”扩展成了“跨任务、跨部署的持续进化循环”。
Harness 则是这个循环的载体。一个部署后的智能体系统由四部分决定:基座模型、可变的状态化 Harness、用户侧(目标与反馈)、环境侧(工具与执行状态)。模型权重更新一次要数天和数百万美元,而 Harness 的状态在部署期间可以被快速检查、修改和治理。它直接决定了模型能看到什么、能做什么、能捕获什么证据、哪些交互能变成可复用的经验。
所以这篇不是纯论文解读,我想从工程落地视角把它拆开:Loop 怎么配、Harness 怎么校验、自进化和元进化的流程怎么用可复制的配置片段跑起来,以及怎么通过统一的 Key/API 通道完成一次端到端验证。适合谁看?有后端基础、正在做 Agent 应用开发、想让自己的 Agent 从“一次性工具”变成“越用越强”的工程师。下面所有配置和脚本都可以直接抄。
2. TaoToken 前置:统一 Key/API 通道怎么接进自进化 Loop
在讲 Loop 和 Harness 的具体配置之前,得先把“通道”这件事说清楚。自进化 Agent 的一个核心特征是:它会在运行过程中频繁调用模型——提炼经验要调、生成技能要调、校验轨迹要调、元层调度还要调。如果每个环节都散落在不同的 Key、不同的 Base URL、不同的计费口径上,Harness 的状态治理根本无从谈起,因为你连“这一轮进化消耗了多少、走了哪个模型”都追踪不了。
我试过把模型调用统一到一个入口,Harness 的日志和归因才真正可读。TaoToken 在这里扮演的就是这个统一通道的角色:一个 Key 覆盖多种模型,Base URL 固定,调用格式兼容主流 SDK。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api ,注意 API 地址后面不加任何 UTM 参数,直接写进配置里就行。
为什么自进化场景特别需要这个?因为论文里把进化分成两条路径:快速路径是在 Harness 层面更新技能和记忆,便宜、快速、可逆;慢速路径是把累积经验内化到模型参数,昂贵、缓慢、几乎不可逆。工程落地时,99% 的迭代都发生在快速路径上——也就是反复调用模型来提炼技能、压缩记忆、校验轨迹。这条路径的调用频次极高,如果通道不统一,成本和质量都没法归因。
具体到操作,你需要先拿到 Key。进入控制台创建 API Key,地址是 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。创建完之后,把 Base URL 和 Key 写进环境变量,后面所有脚本都从这里读:
export TAOTOKEN_BASE_URL="https://taotoken.net/api" export TAOTOKEN_API_KEY="sk-你的key"这里有个坑要提前说:Base URL 结尾不要带斜杠,也不要自己拼/v1,SDK 会按标准路径去拼。我见过有人写成https://taotoken.net/api/v1/,结果 404,排查半天以为是 Key 的问题。另外,如果你用的是 OpenAI 兼容的 SDK,base_url直接填上面那个地址即可。
模型选择上,自进化 Loop 里不同环节对模型的要求不一样。提炼技能、生成结构化 JSON 这种任务,用响应快、指令遵循好的模型就够;元层调度、复杂轨迹归因这种需要长上下文推理的,再上更强的模型。TaoToken 的好处是同一个 Key 可以切换 Model ID,Harness 里只需要改一个字段,不用换通道。模型对话的入口在 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite ,可以先在那里试一下不同模型对同一段轨迹的提炼效果,再决定 Loop 里用哪个。
如果你打算长期跑编码类 Agent 或者让 Agent 自己做元进化调度,调用量会比较大,可以看下 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite ,里面有完整的参数说明和错误码对照,排障的时候比猜要快得多。
把通道统一之后,Harness 才能在每个 Loop 结束时,把“这一轮用了哪个模型、消耗多少、产出了什么技能、记忆有没有更新”这些信息写进结构化日志。没有这一步,后面的自进化验证就是空中楼阁。
3. 可复制配置:Loop 循环片段与 Harness 校验脚本
这一节是全文的核心,直接给可复制的配置。我把它拆成三块:Loop 的循环配置、Harness 的状态文件、以及校验脚本。三块拼起来就是一个最小可运行的自进化闭环。
先说 Loop。论文里把进化路径分成 Skills、记忆、环境、参数固化、元进化五层,工程落地时不需要一次全上,先从 Skills 和记忆这两层做起,因为它们都在 Harness 层面,可逆、便宜、好调试。下面这个loop_config.json定义了一个 Loop 的完整流程:
{ "loop_id": "self-evolve-coding-agent", "base_url": "https://taotoken.net/api", "model_id": "claude-sonnet-4-5", "max_iterations": 8, "stages": [ { "name": "interact", "description": "与用户/环境交互,产生轨迹", "capture": ["user_input", "tool_calls", "tool_results", "final_output"] }, { "name": "extract", "description": "从轨迹中提炼有效经验", "prompt_template": "从以下轨迹中提炼可复用的操作步骤,输出 JSON:{trace}", "output_schema": { "skill_name": "string", "steps": "array", "preconditions": "array", "failure_signals": "array" } }, { "name": "validate", "description": "Harness 校验提炼结果", "checks": ["schema_valid", "no_secret_leak", "step_executable"] }, { "name": "commit", "description": "写入技能库或记忆存储", "target": "harness_state.json", "rollback_on_fail": true } ], "harness": { "state_file": "./harness_state.json", "skill_dir": "./skills", "memory_dir": "./memory", "log_file": "./loop_trace.jsonl" } }这个配置里几个关键点值得展开。stages的顺序就是 Loop 的执行顺序:交互产生轨迹、提炼经验、Harness 校验、提交到状态文件。rollback_on_fail是快速路径可逆性的体现——校验不过就回滚,不会污染技能库。output_schema强制提炼结果结构化,这是后面 Harness 能做自动校验的前提。
然后是 Harness 的状态文件harness_state.json,它记录当前 Agent 的“能力底座”:
{ "version": "0.1.0", "updated_at": "2026-01-15T10:30:00Z", "skills": [ { "name": "fix-typescript-import-order", "steps": ["读取 tsconfig", "按 paths 排序 import", "运行 tsc --noEmit"], "preconditions": ["项目使用 TypeScript", "存在 tsconfig.json"], "failure_signals": ["tsc 报 TS2307"], "hit_count": 0, "last_used": null } ], "memory": { "user_preferences": {}, "failure_lessons": [] }, "evolution_log": [] }注意hit_count和last_used这两个字段。论文里提到技能库要能“淘汰长期闲置冗余技能”,这两个字段就是淘汰策略的依据。Harness 每次使用技能后更新它们,元层调度时根据命中率决定保留还是清理。
接下来是 Harness 校验脚本harness_validate.py,这是整个闭环里最容易被忽略但最重要的一环:
import json import os import re from jsonschema import validate, ValidationError SKILL_SCHEMA = { "type": "object", "required": ["skill_name", "steps", "preconditions", "failure_signals"], "properties": { "skill_name": {"type": "string", "minLength": 3}, "steps": {"type": "array", "minItems": 1}, "preconditions": {"type": "array"}, "failure_signals": {"type": "array"} } } SECRET_PATTERN = re.compile(r"(sk-[A-Za-z0-9]{16,}|AKIA[0-9A-Z]{16})") def check_schema(skill): try: validate(instance=skill, schema=SKILL_SCHEMA) return True, "schema_ok" except ValidationError as e: return False, f"schema_fail: {e.message}" def check_secret_leak(skill): raw = json.dumps(skill, ensure_ascii=False) if SECRET_PATTERN.search(raw): return False, "secret_leak_detected" return True, "no_secret" def check_step_executable(skill): for step in skill["steps"]: if not isinstance(step, str) or len(step.strip()) < 4: return False, f"step_too_short: {step}" return True, "steps_ok" def validate_skill(skill): results = [] for fn in (check_schema, check_secret_leak, check_step_executable): ok, msg = fn(skill) results.append({"check": fn.__name__, "pass": ok, "msg": msg}) if not ok: break return all(r["pass"] for r in results), results if __name__ == "__main__": with open("./pending_skill.json", "r", encoding="utf-8") as f: skill = json.load(f) ok, detail = validate_skill(skill) print(json.dumps({"pass": ok, "detail": detail}, ensure_ascii=False, indent=2)) if not ok: os._exit(1)这个脚本做了三件事:schema 校验、密钥泄露检测、步骤可执行性检查。密钥泄露检测这条特别重要,论文里提到的“技能供应链攻击”和“记忆投毒”,很多都是从提炼环节把敏感信息写进技能库开始的。Harness 作为经验进化的核心载体,必须在提交前把这道关。
把这三块拼起来,一个最小的自进化 Loop 就跑起来了:交互产生轨迹 → 调模型提炼技能 → Harness 校验 → 通过则写入harness_state.json,不通过则回滚。整个过程都在快速路径上,可逆、可观测、可归因。
4. 验证请求:跑一次端到端自进化并看结果
配置写完了,得实际跑一次才知道对不对。这一节给一个完整的端到端验证流程,从发请求到看结果,每一步都有可复制的命令。
先准备一个待提炼的轨迹文件trace.json,模拟一次真实的编码交互:
{ "user_input": "帮我修复 src/utils/date.ts 里的时区转换错误", "tool_calls": [ {"tool": "read_file", "args": {"path": "src/utils/date.ts"}}, {"tool": "run_test", "args": {"cmd": "npm test -- date.spec.ts"}} ], "tool_results": [ {"tool": "read_file", "output": "export function toLocal(d: Date) { return d.toISOString(); }"}, {"tool": "run_test", "output": "FAIL: expected 2026-01-15T10:00:00+08:00, got 2026-01-15T02:00:00Z"} ], "final_output": "将 toISOString 替换为 toLocaleString 并传入 timeZone 参数" }然后写一个调用脚本run_loop.py,把轨迹发给模型提炼技能:
import json import os from openai import OpenAI client = OpenAI( base_url=os.environ["TAOTOKEN_BASE_URL"], api_key=os.environ["TAOTOKEN_API_KEY"] ) with open("./trace.json", "r", encoding="utf-8") as f: trace = json.load(f) prompt = f"""从以下轨迹中提炼可复用的操作步骤,严格输出 JSON,字段为 skill_name, steps, preconditions, failure_signals。 轨迹:{json.dumps(trace, ensure_ascii=False)}""" resp = client.chat.completions.create( model="claude-sonnet-4-5", messages=[{"role": "user", "content": prompt}], response_format={"type": "json_object"} ) skill = json.loads(resp.choices[0].message.content) with open("./pending_skill.json", "w", encoding="utf-8") as f: json.dump(skill, f, ensure_ascii=False, indent=2) print(json.dumps(skill, ensure_ascii=False, indent=2))跑之前确认环境变量已经导出,然后执行:
python run_loop.py正常的话你会看到类似这样的输出:
{ "skill_name": "fix-timezone-conversion", "steps": [ "读取目标文件确认当前时间转换实现", "运行相关测试定位失败断言", "将 toISOString 替换为带 timeZone 参数的 toLocaleString", "重新运行测试确认通过" ], "preconditions": ["项目使用 TypeScript", "存在对应测试文件"], "failure_signals": ["测试断言时区偏移不匹配"] }拿到pending_skill.json之后,跑 Harness 校验:
python harness_validate.py校验通过会输出"pass": true,然后就可以提交到状态文件。提交这一步建议单独写个小脚本,把 pending 技能合并进harness_state.json,同时更新evolution_log:
import json from datetime import datetime, timezone with open("./pending_skill.json", "r", encoding="utf-8") as f: skill = json.load(f) with open("./harness_state.json", "r", encoding="utf-8") as f: state = json.load(f) skill["hit_count"] = 0 skill["last_used"] = None state["skills"].append(skill) state["updated_at"] = datetime.now(timezone.utc).isoformat() state["evolution_log"].append({ "action": "add_skill", "skill_name": skill["skill_name"], "at": state["updated_at"] }) with open("./harness_state.json", "w", encoding="utf-8") as f: json.dump(state, f, ensure_ascii=False, indent=2) print("committed:", skill["skill_name"])到这里,一次完整的自进化循环就跑完了:轨迹 → 提炼 → 校验 → 提交。你可以再跑一次run_loop.py,换一个类似的时区问题轨迹,观察模型提炼出的技能是否和已有的fix-timezone-conversion重复。如果重复,说明 Harness 需要一个去重策略;如果互补,说明技能库在正向增长。这就是论文里说的“正向泛化增益”的工程化观测方式。
想验证模型在不同任务上的提炼质量,可以到模型对话页面手动试几段轨迹:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。把同一段轨迹发给不同模型,对比输出的steps粒度和failure_signals准确度,再决定 Loop 里固定用哪个 Model ID。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
自进化 Loop 跑不起来,90% 的问题集中在四类报错上。这一节按真实报错逐个拆,每个都给定位方法和修复动作。
401 Unauthorized。这个最常见,但原因不止一种。先确认TAOTOKEN_API_KEY有没有正确导出,echo $TAOTOKEN_API_KEY看是不是空。如果 Key 没问题,检查 Base URL 是不是写成了https://taotoken.net/api/带尾斜杠,或者自己拼了/v1。SDK 会按标准路径拼,多一段就 401 或 404。还有一种情况是 Key 创建后没复制完整,前后有空格,用export的时候被截断。修复方式:重新到控制台复制一次,地址是 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite ,粘贴时注意不要带换行。
local proxy failed。这个报错通常出现在你本地配了某些网络层,SDK 请求被拦截。先检查环境变量里有没有HTTP_PROXY、HTTPS_PROXY、ALL_PROXY,有的话临时 unset 掉再跑。另外确认base_url是https://taotoken.net/api,不要写成http。如果是在容器里跑,检查容器的 DNS 和出网策略,curl -I https://taotoken.net/api看能不能通。这个报错和 Key 无关,纯粹是请求没发出去。
reading 'choices' of undefined。这是 OpenAI SDK 的典型报错,意思是resp.choices是 undefined,通常发生在resp本身是错误响应的时候。根因一般是模型名写错了,或者response_format不被该模型支持。先确认model字段填的是有效的 Model ID,可以在模型对话页面确认:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。如果模型不支持json_object格式,去掉response_format,改成在 prompt 里强调“只输出 JSON”,然后在代码里做一次json.loads的容错。
OAuth 相关报错。如果你用的是 Claude Code 或者 Codex 这类带 OAuth 流程的工具,报错信息里出现OAuth token expired或invalid_grant,说明本地缓存的凭证过期了。这类工具通常把凭证存在~/.config或~/.codex/auth.json下。以 Codex 为例,auth.json里需要同时配好三件套:Base URL、Key、Model ID。Base URL 填https://taotoken.net/api,Key 填控制台创建的 Key,Model ID 填你要用的模型。三个字段缺一个都会走到 OAuth 回退逻辑然后失败。Claude Code 的配置类似,在 settings 里把ANTHROPIC_BASE_URL指向统一通道,ANTHROPIC_API_KEY填 Key,模型名按文档填。接入文档里有完整的字段对照:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。
排障的时候有个通用技巧:把 SDK 的日志级别调到 debug,看实际发出的请求 URL 和 header。很多问题看一眼真实请求就清楚了,比猜快得多。另外,Harness 的loop_trace.jsonl里记录了每一轮的模型调用和结果,出问题时先看这个日志,能快速定位是提炼环节挂了还是校验环节挂了。
6. 语义一致 CTA:把自进化 Loop 接到你的工程里
写到这里,Loop 和 Harness 的协作机制、可复制配置、端到端验证、常见报错都过了一遍。回到论文本身,它最有价值的地方不是提出了多少新概念,而是把“Agent 怎么越用越强”这件事拆成了可工程化的层次:快速路径改 Harness,慢速路径改参数,元层决定谁来改。工程落地时,先把快速路径跑通,也就是技能和记忆这两层,投入产出比最高。
如果你要接着往下做,几个方向可以选。想先把模型调用通道统一、把 Key 和 Base URL 配好,直接去控制台创建 Key:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。想对照完整参数和错误码把 Harness 校验脚本写得更健壮,看接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。想先手动试几段轨迹、对比不同模型的提炼质量再决定 Loop 里用哪个 Model ID,去模型对话页面:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。如果你打算长期跑编码类 Agent、让元层自己做调度和归因,调用量会上来,可以看 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。
最后留一个我踩过的坑:Harness 的状态文件一定要纳入版本管理,每次提交技能都打一个 commit。自进化最怕的不是不进化,而是进化错了方向还不自知。有了版本历史,回滚就是一条git revert的事,这比任何安全审计都实在。