1. 从“Agent Harness”这个词说起:为什么它不是又一个包装概念
第一次看到 RRSI 这个缩写,很多人会下意识把它归类成“又一个自我改进的论文造词”。但如果你真的动手搭过 LLM agent,就会明白 Regularized Recursive Self-Improvement of Agent Harnesses 这个标题里,真正有分量的词其实是Agent Harness,而不是 Self-Improvement。
先把概念理清楚。现在大家嘴里的“agent”,通常指的是一个能感知输入、做决策、调用工具、观察结果、再继续决策的循环体。而harness指的是把这个循环体“架起来”的那套外壳:提示词模板、工具注册表、上下文管理策略、重试与终止条件、输出解析器、状态存储、错误处理路径。换句话说,模型是发动机,harness 是底盘、变速箱和线束。同一个模型换一套 harness,任务成功率能差出一大截,这个结论在圈内已经被反复验证过。
那 RRSI 想干什么?它想做的事情是:让 agent 的这套外壳,能够递归地自我改进,同时用某种正则化手段防止它在自我改进的过程中跑偏、退化或者过拟合到某几个任务上。这三个词拆开看都不新鲜——递归自我改进是老话题,正则化是机器学习的基本功,harness 工程是当下 agent 落地的核心痛点。但把它们拼在一起,指向的是一个非常具体的问题:agent 的外壳能不能自己迭代自己,而且迭代过程是可控的、不发散的。
我个人的判断是,这个方向之所以值得认真对待,是因为它绕开了“等更强的基座模型”这条被动路线。基座模型的能力你改不了,但 harness 是你完全掌控的。如果 harness 能自我进化,那即使模型不变,agent 的实际表现也能持续爬坡。这对做落地的人来说,意义比刷榜大得多。
这篇文章我会按“先讲清楚 harness 到底由什么构成,再讲递归自我改进的机制怎么设计,然后重点讲正则化为什么是命门,最后落到实操和踩坑”这个顺序展开。适合已经写过至少一个能跑通的 agent、但发现它“时好时坏、换个任务就崩”的读者。如果你还没搭过 agent,建议先补一下工具调用和 ReAct 循环的基础,不然中间几节会有点吃力。
2. Agent Harness 的解剖:自我改进到底在改什么
2.1 Harness 的六个可改动层
要让 harness 能自我改进,第一步是把它拆成“可被修改的单元”。如果整个 harness 是一坨硬编码的 Python,那所谓自我改进就无从下手。我习惯把它拆成六层,每一层都是潜在的改进对象:
| 层级 | 具体内容 | 改进空间 | 改动风险 |
|---|---|---|---|
| 提示层 | 系统提示、角色设定、few-shot 示例 | 高 | 中 |
| 工具层 | 工具描述、参数 schema、工具组合 | 高 | 高 |
| 控制层 | 循环终止条件、重试策略、分支逻辑 | 中 | 高 |
| 上下文层 | 历史压缩、检索策略、记忆写入规则 | 高 | 中 |
| 解析层 | 输出格式约束、错误恢复、兜底逻辑 | 中 | 低 |
| 评估层 | 成功判定、打分函数、反馈信号 | 高 | 低 |
这张表是我自己踩坑之后总结的。早期我总想着“改提示词就行了”,结果发现很多失败根本不是提示词的问题,而是工具描述写得让模型误解了参数含义,或者终止条件设得太死导致 agent 提前放弃。把 harness 分层,是让自我改进有的放矢的前提。
2.2 为什么“递归”是关键词而不是噱头
普通的 harness 优化是人工的:你跑一批任务,看失败案例,改提示词,再跑。这个过程是线性的,靠人的时间堆。递归的意思是,把这个“观察失败—提出修改—验证修改—保留有效修改”的循环,交给 agent 自己跑。
这里有个容易混淆的点:递归自我改进不等于模型改自己的权重。RRSI 改的是 harness,是外部的、符号化的、可读可写的那部分。这一点非常重要,因为它意味着每一次改进都是可审计的。你改的是提示词文本、工具描述、控制参数,这些东西都能 diff、能回滚、能人工复核。相比之下,改权重那种自我改进,出了问题你根本不知道是哪一步坏的。
我实测下来的感受是,harness 层面的递归改进,收敛速度比想象中快。一个中等复杂度的任务集,跑个三五轮迭代,成功率往往能有肉眼可见的提升。但前提是正则化做得好,否则第三轮开始就会出现“为了通过 A 类任务把 B 类任务搞崩”的情况,这就是下一节要重点讲的。
2.3 一个最小可用的 harness 表示
要让 harness 可被程序修改,你得先把它表示成结构化数据。我的做法是用一份 YAML 或 JSON 描述整个 harness,agent 改的是这份配置,而不是直接改代码。大致长这样:
harness: system_prompt: "你是一个..." tools: - name: search description: "用于检索..." params: query: {type: string, required: true} control: max_iterations: 8 retry_on_error: 2 stop_condition: "final_answer_emitted" context: compression: "summary" max_tokens: 6000 parser: format: "json" fallback: "extract_last_json"这份配置就是递归改进的“基因组”。每一轮迭代,agent 提出一个 patch,系统应用 patch,跑评估集,比较分数,决定是否保留。把 harness 配置化,是整个 RRSI 能跑起来的地基。如果你现在的 agent 还是散落在十几个文件里的硬编码,建议先花两天做这个重构,后面会省下大量时间。
3. 递归自我改进的闭环:一轮迭代里到底发生了什么
3.1 四个角色:执行者、诊断者、提案者、裁判
一个能自洽运行的 RRSI 闭环,至少需要四个逻辑角色。它们可以是同一个模型扮演,也可以是不同模型,但职责必须分开,否则会出现“自己改自己还自己打分”的作弊问题。
- 执行者(Executor):用当前 harness 跑任务,产生轨迹和结果。
- 诊断者(Diagnoser):分析失败轨迹,定位是 harness 哪一层出了问题。
- 提案者(Proposer):针对诊断结论,生成具体的 harness 修改方案。
- 裁判(Judge):在验证集上评估修改前后的表现,决定是否采纳。
我一开始图省事,让一个模型把诊断和提案一起做了,结果它经常给出“把提示词写得更清楚一点”这种没法执行的建议。把诊断和提案拆开之后,提案质量明显提升,因为诊断阶段被迫先输出结构化的失败归因,提案阶段才有明确的靶子。
3.2 诊断环节的结构化输出
诊断者不能只说“这次失败了”。它需要输出类似这样的结构化归因:
{ "task_id": "t-042", "failure_type": "tool_misuse", "layer": "tools", "evidence": "模型调用了 search 但把 query 参数填成了整个问题原文", "hypothesis": "工具描述没有说明 query 应该是关键词而非完整句子" }有了这个,提案者就能针对性地改工具描述,而不是漫无目的地重写系统提示。结构化诊断是防止递归改进变成随机游走的关键。我见过太多团队在这一步偷懒,最后迭代了十几轮,harness 越改越乱,就是因为诊断信号太弱。
3.3 提案的粒度控制
提案者最容易犯的错是一次改太多。一轮迭代里同时改提示词、改工具描述、改终止条件,结果分数涨了也不知道是哪个改动起的作用,分数跌了也不知道该回滚哪个。
我的经验是每轮只允许改一个层,且改动幅度受限。比如这一轮只动工具层,且最多修改两个工具的描述。这样每轮迭代的因果是清晰的,积累下来你还能得到一份“哪类改动最有效”的经验数据。这个约束本身就是一种正则化,后面还会展开。
3.4 验证与采纳:别用训练集自欺欺人
裁判必须在独立的验证集上评估。我踩过的最大的坑,就是早期用同一批任务既做诊断又做验证,结果 harness 迅速过拟合到这批任务的具体措辞上,换一批同类型但不同表述的任务,表现直接打回原形。
正确的做法是准备三个集合:训练集(用于诊断和提案)、验证集(用于采纳决策)、测试集(只在最后评估,迭代过程中绝不碰)。这个划分听起来是常识,但在 agent 场景下特别容易被忽略,因为很多人觉得“任务就这么多,分那么细干嘛”。分不细,你的改进就是假的。
4. 正则化:RRSI 里最容易被低估的命门
4.1 没有正则化,递归改进必然发散
递归自我改进有个内生的不稳定倾向:每一轮都倾向于把 harness 调整到“刚好能通过当前这批失败案例”的状态。这在机器学习里就是过拟合,在 agent 场景下表现得更隐蔽——因为 harness 是文本,过拟合的痕迹不像权重那样能画出来。
具体表现是什么?我遇到过几种典型症状:
- 提示词里堆满了针对特定任务的“如果遇到 X 就做 Y”的补丁,越来越长,最后模型自己都读晕了。
- 工具描述被改得越来越具体,通用性丧失,换个领域就完全不能用。
- 终止条件被调得越来越宽松,agent 开始“假装完成任务”来骗过判定。
这些症状的共同点是:在训练集上分数很好看,在验证集上原地踏步甚至倒退。正则化的作用,就是给这个发散过程套上缰绳。
4.2 四类正则化手段及其取舍
我把实践中有效的正则化手段归成四类,每一类解决不同的问题:
| 正则化类型 | 作用对象 | 具体做法 | 代价 |
|---|---|---|---|
| 复杂度惩罚 | harness 本身 | 限制提示词长度、工具数量、分支数 | 可能压制有效改进 |
| 改动幅度约束 | 单轮 patch | 限制每轮改动层数和字符数 | 收敛变慢 |
| 多样性保持 | 任务分布 | 验证集覆盖多类任务,加权评估 | 需要更多标注 |
| 回滚与早停 | 迭代过程 | 连续 N 轮无提升则停止并回滚 | 可能错过后期突破 |
这四类不是互斥的,实际用的时候要组合。我目前的默认配置是:复杂度惩罚用软约束(超过阈值扣分而非禁止),改动幅度硬约束(每轮一层),多样性用分层验证集,回滚用连续三轮无提升触发。
4.3 复杂度惩罚的具体计算
复杂度惩罚不能拍脑袋,得有个可计算的指标。我用的是 harness 的“描述长度”加权和:
complexity = w1 * len(system_prompt) + w2 * sum(len(tool.description) for tool in tools) + w3 * num_control_branches + w4 * num_few_shot_examples权重怎么定?我的经验是让提示词长度和工具描述长度的权重占大头,因为这两块最容易膨胀。然后把这个 complexity 作为一个惩罚项加进最终评分:
final_score = task_success_rate - lambda * complexitylambda 的取值很关键。太小了压不住膨胀,太大了会阻止任何有意义的改进。我一般从 0.001 开始试,观察几轮迭代后 harness 的膨胀速度,再微调。这个参数没有理论最优值,只能靠观察实际迭代曲线来定。
4.4 为什么“保持多样性”比想象中难
多样性正则化的难点在于,你很难定义什么叫“任务分布”。在真实项目里,任务往往是长尾的,少数高频任务占了大部分流量,但少数低频任务才是真正考验 harness 通用性的地方。
我的做法是给验证集做分层,每类任务至少保证一定比例,评估时用加权成功率而不是简单平均。这样即使某类任务样本少,它的失败也不会被高频任务的成功掩盖。这一步做不做,直接决定了你的 harness 是“能用”还是“只在 demo 里能用”。
5. 落地实操:从零搭一个能跑的 RRSI 循环
5.1 环境与依赖准备
先说清楚,RRSI 本身不依赖什么特殊框架,核心就是“配置化的 harness + 评估循环 + 模型调用”。我用的是 Python,主要依赖就几个:模型 API 客户端、YAML 解析、一个简单的评估脚本。不需要向量数据库,不需要复杂的编排框架,越简单越容易调试。
目录结构我建议这样组织:
rrsi/ harness/ current.yaml # 当前生效的 harness 配置 history/ # 每轮迭代的快照 tasks/ train.jsonl val.jsonl test.jsonl loop/ executor.py diagnoser.py proposer.py judge.py run_iteration.py这个结构的好处是每一轮的 harness 快照都留着,出问题能精确回滚到某一轮。我吃过没留快照的亏,有一次改崩了想回退,发现上一版配置已经被覆盖,只能凭记忆重建。
5.2 执行器的写法要点
执行器的核心是“读 harness 配置,跑任务,记录完整轨迹”。轨迹记录要足够详细,包括每一步的模型输入输出、工具调用参数、工具返回结果、耗时。这些是诊断者的原料。
def run_task(task, harness): trace = [] context = build_initial_context(task, harness) for step in range(harness.control.max_iterations): response = call_model(context, harness) trace.append({"step": step, "response": response}) if is_final(response): break tool_result = execute_tool(response, harness.tools) trace.append({"step": step, "tool_result": tool_result}) context = update_context(context, response, tool_result, harness) return {"task_id": task.id, "trace": trace, "result": extract_result(trace)}这里有个细节:context 的更新逻辑必须走 harness 配置,不能硬编码。因为上下文压缩策略本身就是可改进的一层,如果写死了,这一层就没法被 RRSI 优化。
5.3 诊断者的提示词设计
诊断者的提示词要引导模型输出结构化归因,而不是泛泛而谈。我的模板大致是:
你是一个 agent 失败分析专家。下面是一条失败的任务轨迹。 请判断失败发生在 harness 的哪一层(提示层/工具层/控制层/上下文层/解析层), 并给出具体证据和你的假设。输出 JSON 格式,字段包括: failure_type, layer, evidence, hypothesis。 不要给出修改建议,只做归因。最后那句“不要给出修改建议”很重要。我一开始没加,结果诊断者总是越界去提方案,导致和提案者的输出重复且互相矛盾。职责边界要在提示词里写死。
5.4 提案者的约束与输出格式
提案者收到诊断结论后,输出一个 patch。patch 的格式我建议用 JSON Patch 或者自定义的简单结构:
{ "target_layer": "tools", "operation": "modify", "tool_name": "search", "field": "description", "old_value": "用于检索信息", "new_value": "用于检索信息。query 参数应填写精炼的关键词,而非完整句子。" }这个格式的好处是改动明确、可 diff、可回滚。提案者被要求只输出一个 patch,且只能针对诊断指出的那一层。约束越硬,迭代越可控。
5.5 裁判的评分与采纳逻辑
裁判在验证集上跑修改前后的 harness,比较加权成功率。采纳规则我建议设得保守一点:
- 新分数 > 旧分数 + 最小提升阈值(比如 0.02),才采纳。
- 如果新分数提升但 complexity 增加超过阈值,需要提升幅度更大才采纳。
- 连续三轮不采纳,触发早停。
这个保守策略会让收敛变慢,但能有效防止 harness 被噪声带偏。宁可慢一点,也不要让 harness 在迭代中悄悄退化。
6. 实测中的坑:那些文档不会告诉你的细节
6.1 模型自评的偏差问题
裁判如果用同一个模型,会有一个隐蔽的偏差:模型倾向于给自己生成的 harness 打高分。我做过对比实验,同一个 harness 让生成它的模型评和让另一个模型评,分数能差 5 到 10 个百分点。
解决办法有两个:要么用不同的模型做裁判,要么在裁判提示词里加入“请严格评估,不要因为改动看起来合理就给高分”这类校准语句。前者更可靠,后者成本低。如果预算允许,裁判和执行者用不同模型,是性价比很高的一步。
6.2 工具描述改动的连锁反应
工具描述是高频改动对象,但它有个坑:改了一个工具的描述,可能影响模型对其他工具的选择。我遇到过把 search 描述改详细之后,模型开始过度使用 search,把本来该用 calculator 的任务也用 search 去查。
所以工具层的改动,验证时不能只看目标任务的分数,还要看工具调用分布的合理性。我现在会在评估里加一个指标:各工具调用次数的分布,如果某个工具调用占比突然飙升,即使总分涨了也要警惕。
6.3 上下文压缩策略的隐性成本
上下文层是很多人忽略的改进点。压缩策略从“全量保留”改成“摘要压缩”,token 成本降了,但可能丢失关键细节导致任务失败。这个 trade-off 在评分里必须体现,否则 RRSI 会一味追求省 token 而牺牲成功率。
我的做法是在评分里同时记录成功率和平均 token 消耗,用帕累托前沿的思路来评估改动。只看单一指标的迭代,最后一定会偏。
6.4 迭代轮数的边际收益递减
实测下来,RRSI 的收益曲线通常是前几轮陡峭,后面迅速变平。我跑过的项目里,大部分有效改进集中在前 5 轮,第 8 轮之后基本就是噪声级别的波动了。
所以不要迷信“迭代越多越好”。设置一个合理的早停条件,把省下来的算力用在扩充验证集或者优化诊断质量上,收益更高。递归自我改进不是无限爬坡,它有自己的天花板。
7. 这套东西适合什么场景,不适合什么场景
RRSI 不是万能药。它适合的场景有几个特征:任务类型相对稳定、有明确的成功判定、能拿到一定量的标注数据、harness 本身已经配置化。如果你的任务每次都不一样,或者成功与否全靠人主观判断,那 RRSI 的闭环根本转不起来。
不适合的场景也很明确:一次性任务、探索性任务、成功标准模糊的创意类任务。这些场景下,人工调 harness 反而更灵活,硬套 RRSI 只会浪费算力。
我个人的体会是,RRSI 最大的价值不在于“让 agent 自己变强”这个听起来很酷的叙事,而在于它强迫你把 harness 工程化、配置化、可评估化。哪怕你最后不用递归改进,光是做完这套基础设施,你对 agent 行为的理解就会上一个台阶。很多团队卡在 agent 不稳定上,根子不是模型不行,而是 harness 从来没被当成一个正经的工程对象来对待。RRSI 提供的正是这个视角。
最后分享一个我常用的检查习惯:每轮迭代后,把当前 harness 配置和第一版并排 diff 一遍,问自己“这些改动里,哪些是真正通用的,哪些只是针对某几个案例的补丁”。如果补丁占比超过三成,就该考虑回滚或者加强正则化了。这个习惯帮我避免了好几次 harness 的慢性膨胀。