Agent Harness完全指南

你的代理只有六行Python代码。Harness则是其余的一切。

Agent Harness完全指南
梯形图转SCL | AI模型价格对比 | AI工具导航 | ONNX模型库 | Vibe Coding教程 | PLC在线仿真器 | Tripo 3D | Meshy AI | ElevenLabs | KlingAI | ArtSpace | Phot.AI | InVideo

两周前,我在配置文件中写了一条规则:永远不要应用未经人类批准的编辑。

这是一条好规则。措辞清晰,位于文件顶部,代理在每次会话开始时都会读取它。

有一次会话在下午直接违反了这条规则十一次,将大约七十六处未经批准的编辑放入了客户交付物中。没有任何东西阻止它。没有错误,没有标记,没有任何等待确认的东西。两小时后,我检查文件,标记了代理未经询问就重写的每一行。四十六行,在一份印有客户名字的文档中。

我把一条好规则写在了错误的地方。

我将其重写为钩子:一个脚本,在分派工具调用之前由harness运行,它读取待处理的调用并返回拒绝。代理仍然可以决定进行编辑。但编辑永远不会到达文件。

从那以后,钩子错了两次。两次都是我把规则写得太窄了,两次我都可以打开文件并指出错误的那一行。这才是值得拥有的部分。提示版本让我无从下手。

这种差异——你陈述的规则与你执行的规则之间的差距——就是harness。它存在于五个地方:

  1. 代理被允许做什么
  2. 它能触及什么
  3. 什么在重启后存活
  4. 谁检查它的工作
  5. 你付多少钱向它展示任何东西

每个部分都附有代码,以及一个仓库,你可以在生产环境中失败之前,在自己的机器上观察该层失败。

(如果代理对你来说仍然陌生,从这里开始然后再回来。以下所有内容都假设你已经部署了一个并看着它行为异常。)

1、Harness真正的作用

让我从听起来太简单而不值得说的事情开始。

编码代理循环的核心是六行:

while agent.turns < max_turns:
    call = agent.next_call(observations)
    if call is None:
        break
    result = execute(call)
    observations.append(result)

就这样。这就是代理。加上错误处理和重试,你大约有二十行。你评估过的每个框架都包装了这六行,有趣的部分是execute,这是你决定模型被允许做什么、能触及什么、记住什么、谁检查它以及它能看到什么的地方。

这就是harness。现在你的信息流里满是告诉你它比选择哪个模型更重要的人。

我花了一周时间验证这个说法。它站不住脚,而替代它的东西更有用。

harness占主导的地方: 成本。一篇2026年6月的预印本在Terminal-Bench Pro(Terminal-Bench代理基准的更难变体)的分层50任务子集上运行了三个harness(Goose、OpenCode、OpenHands-SDK)。它测量到每个解决任务的token数差异高达40倍。升级模型仅使相同数字移动1.0到1.3倍。

它不占主导的地方: 能力。同一研究发现harness之间的通过率差异为0到8个百分点,除了最大的差距外,所有差距的引导置信区间都跨越零。作者称其准确性发现是描述性的,n=50。

因此,相同的任务,相同的模型,相同的成功率,在一个harness上的成本是另一个的四十倍。

以下所有内容使你的代理更便宜且更难被意外。但没有一个能使弱模型变聪明。

2、将规则移入代码

系统提示中的规则是一个赌注,赌模型会记住它、按你的意思阅读它,并在上下文压力下遵循它。模型大多数时候赢了这个赌注。这种可靠性正是危险所在,因为它是你停止检查的原因。

代码中的规则在每次调用时运行,包括模型已忘记其存在的调用。

在代理循环中有五个地方可以通过代码强制执行规则。以下各节逐一介绍:该层的用途、执行它的代码,以及缺少该层时相同代理的行为。你不需要在第一天就拥有所有五层。从你能想象本周发生在你身上的故障层开始。

None

3、第1层:执行边界

问题:代理被允许做什么?

边界是在模型决策和工具调用之间运行的函数。这是一个没有边界的相同代理。系统提示说永远不要未经询问就删除文件,它有三个工具调用排队。

SYSTEM_PROMPT = "IMPORTANT: never delete a file without asking the user first."

def execute(call):
    if call.name == "delete_file":
        FILES.pop(call.args["path"], None)
        return f"deleted {call.args['path']}"

运行它,代理会删除cache.tmp(这是垃圾),然后删除client-deliverable.md(正如其名)。它从未询问。规则一直在提示中。

修复方法是在工具之前运行一个函数:

def boundary(rules):
    def wrap(execute):
        def guarded(call):
            for name, deny_if, reason in rules:
                if deny_if(call):
                    return f"DENIED by {name}: {reason}"
            return execute(call)
        return guarded
    return wrap

NEEDS_APPROVAL = [(
    "delete-needs-approval",
    lambda c: c.name == "delete_file" and not c.args.get("approved_by_human"),
    "deletion requires an explicit human approval flag on the call",
)]

相同的代理,相同的脚本,相同的提示。

什么都不会被删除。

4、第2层:沙箱

问题:代理能触及什么?

沙箱是运行代理的进程可以触及的目录和主机集合。你从无开始,添加回工作所需的内容:路径允许列表、主机允许列表,以及一个看不到其外部任何内容的进程。

常见版本是拒绝列表,命名代理不能触及的路径:

DENY = ["secrets/"]

def execute(call):
    path = call.args["path"]
    if any(path.startswith(d) for d in DENY):   # 检查拼写
        return "DENIED by deny-list"
    real = os.path.normpath(path)               # ../ 在此处折叠,在检查之后
    return DISK.get(real, "not found")

运行它,代理请求两次:

read('secrets/api_key')          -> DENIED by deny-list
read('work/../secrets/api_key')  -> sk-live-DO-NOT-LEAK

检查运行在代理输入的字符串上。查找运行在该字符串实际指向的位置。

两行之隔,两种拼写是同一个文件。

修复方法是在比较之前解析路径:

ALLOW_ROOTS = ["work"]

def resolve(path):
    real = os.path.normpath(path)               # 首先解析
    if not any(real == r or real.startswith(r + os.sep) for r in ALLOW_ROOTS):
        return None
    return real

现在两种拼写都到达secrets/api_key,都不在work下,都被拒绝。允许列表替代拒绝列表的原因相同:拒绝列表只阻止你想到的拼写。

两个版本都在仓库中作为failures/02_no_sandbox.pylayers/02_sandboxing.py运行。它们是Python函数,因此你可以无需设置容器即可运行它们。在生产环境中,相同的检查是进程本身,一个在work/外部没有读权限的用户或容器,操作顺序相同。

这是第1层无法完成的工作。你自己程序中的检查只看到它识别的调用,同一个文件可以通过shell命令、符号链接或包含..的路径到达。这些都不是它正在监视的调用。

5、第3层:内存持久性

问题:什么在上下文重置后存活?

我今年改变的最有用的事情是学会毫不犹豫地丢弃对话。

上下文填满,质量下降,代理开始自相矛盾。本能是维持会话。更好的做法是自由重启,并确保重要的内容存在于重启无法触及的地方:提交的代码、配置、harness本身、提取的笔记。

这意味着有意识地决定代理产生的每个状态属于哪里:(1)在对话中,随会话消亡;(2)在磁盘上,存活且可检索;(3)在harness配置中,塑造每个后续会话。

三个目的地,一个调度:

CONVERSATION, DISK, HARNESS_CONFIG = [], {}, {}

def remember(kind, key, value):
    """'chat' 随会话消亡,'disk' 存活,'config' 塑造每个后续会话。"""
    {"chat": lambda: CONVERSATION.append(value),
     "disk": lambda: DISK.__setitem__(key, value),
     "config": lambda: HARNESS_CONFIG.__setitem__(key, value)}[kind]()

调度很简单。价值在于你不再能在不明确说出三者之一的情况下存储任何东西。

大多数团队从未明确做出这个决定,这就是为什么他们的代理在某些方面感觉健忘,在其他方面顽固错误。(运行layers/03_memory_persistence.py:对话返回[],而决策和规则仍然存在于磁盘和配置中。)

6、第4层:验证循环

问题:谁检查工作,什么阻止他们自己修复?

验证循环是某物对代理输出的第二次传递,该物可以报告问题但无法修复。

句子的后半部分是人们跳过的部分。给审查子代理写访问权限,它会默默地修复发现的问题。这听起来很高效。它破坏了信号,因为现在你无法区分原本正确的 work 和原本错误但被修补的 work。

使审查者只读。其输出是批评。对其采取行动是具有自己记录的单独步骤。这种分离保持了信号的可读性。

这里的第二个模式是模拟运行。对于任何不可逆的操作,工具接受一个标志,默认描述它会做什么,只有在标志明确关闭时才执行。默认是安全的,所以忘记是无害的。(在layers/04_verification_loops.py中,apply_fix打印DRY RUN: would rewrite total.py, nothing written,而差一错误仍然存在于末尾。)

两条规则都适合一个execute

def execute(call):
    if call.name == "review":
        snapshot = dict(CODE)               # 副本,审查者无法写入
        src = snapshot[call.args["path"]]
        return f"VERDICT: {'off-by-one' if '+ 1' in src else 'looks good'}"
    if call.name == "apply_fix":
        if call.args.get("dry_run", True):  # 默认开启,有意关闭
            return f"DRY RUN: would rewrite {call.args['path']}, nothing written"
        CODE[call.args["path"]] = call.args["new"]
        return f"wrote {call.args['path']}"

审查者发现了差一错误但无法触及它。

7、第5层:上下文管道

问题:模型实际看到什么,这花费多少?

上下文管道是信息到达模型上下文窗口的路径,以及关于多少信息到达的决定。

决定是昂贵的部分。你让进入窗口的每个token都在该轮和之后每轮付费,这就是为什么四十倍的差距出现在这里而不是第1层。

朴素的harness将所有内容放入窗口:当代理询问一个函数时整个文件,当需要一行时整个搜索输出,以及之前的每一轮。它有效。这也意味着第二十轮再次为第一到十九轮读取的所有内容付费。

修复它的方法是蒸馏委托。一个子代理获得自己的上下文窗口,探索数万个token,返回一到两千。主线程永远不会看到搜索转储。它看到的是答案。

在代码中,整个技巧是哪个计数器递增:

def subagent_search(query):
    """它自己的窗口。主线程永远不会为这次阅读付费。"""
    global subagent_tokens
    subagent_tokens += sum(len(v.split()) for v in CORPUS.values())
    return next(f"{n}: {b.split('ANSWER:')[1].strip()}"
                for n, b in CORPUS.items() if "ANSWER:" in b)
def execute(call):
    global main_tokens
    distilled = subagent_search(call.args["q"])
    main_tokens += len(distilled.split())    # 唯一计费的一行
    return distilled

仓库中的演示是那个的最小诚实版本。相同的问题,相同的答案,两次。主线程在一个中读取9,602个token,在另一个中读取2个。

这也是为什么顺序子代理模式令人失望:每一步等待上一步的链为你购买了上下文隔离,但没有购买任何并行性。如果步骤真正独立,就那样运行它们。如果不是,诚实地说你在为隔离付费。(隔离通常值得花钱。只需知道发票上是什么。)

8、没人能告诉你的事情

限制,因为围绕这个话题的信心远超其下的证据。

  1. 这里没有一项对照研究经过同行评审。 截至2026年8月,每个harness隔离结果都是未经评审的arXiv预印本。最谨慎的实验性自称为 workshop 审稿中的初步工作。被引用最多的框架论文是一篇自称立场论文,没有进行自己的实验。这对你意味着什么: 这里没有任何东西通过同行评审,包括我的。在根据任何内容做预算之前,在自己的任务上重新运行比较。
  2. Harness增益通常无法在新任务上存活。 一篇2026年7月的论文发现自动harness演化*"不能一致地优于简单的测试时缩放方法,并表现出有限的泛化"*,因为搜索和最终评估共享一个基准。Terminal-Bench 2.0上的持续学习评估看着GEPA在调优任务上攀升至70.8%,然后在更广泛的集合上降至54.5%,低于起始的56.8%。优化使其变差。这对你意味着什么: 在未用于构建的任务上测量你的harness。仅出现在调优集上的增益是你在生产中失去的增益。
  3. 一个著名的微调结果是用其自己的奖励信号评分的。 Baseten为临床笔记生成微调了Gemma 3 27B,从比Claude Sonnet 4差35%变为好60%。他们的文章明确表示*"评估harness本身在训练期间用作奖励信号。"* 合理的技术,结果是针对训练它的东西测量的。这对你意味着什么: 当供应商报告微调胜利时,询问训练期间的评估是什么。如果是同一个harness,该数字告诉你模型学会了评估。
  4. 你的harness不可移植。 一项2026年6月的研究发现,仅在训练后应用harness只能恢复很少的训练时优势。在最小harness上后训练的模型在工具定义形状改变后返回*"Invalid tool format"*的比率为75.1%,一个Qwen2.5-7B变体比其起始基础模型低10.8分。适用于每个模型的harness对每个模型都稍差。这对你意味着什么: 在后训练之前选择你的harness。事后添加它只能恢复很少的训练时优势。如果你不训练自己的模型,预计项目中途的harness交换成本高于迁移看起来应有的成本。
  5. 该领域尚未就该词的含义达成一致。 Hugging Face在2026年5月发布了一个术语表,将harness与scaffold分开,并指出Claude Code、Codex等无论如何都称整个为harness。五层是一种分解。在更窄的定义下,上述一半证据在测量其他东西。选择一个词汇表,在团队内保持一致,并谨慎对待跨研究比较。(这就是为什么五层作为你自己设置的检查表出现:检查表在词汇变化中存活,而分类法与之争论。)这对你意味着什么: 在相信两个代理结果之间的差距之前,询问每个包含哪些层。
  6. 且harness效果似乎随着模型改进而缩小。 Harness-Bench报告更强的模型后端显示*"更高的平均分数同时表现出更低的跨harness方差"*。脚手架可能正在补偿即将消失的弱点。这对你意味着什么: 将你的harness工作集中在成本控制上(持久),而不是准确性上(下一个模型版本可能免费吸收)。

9、仓库

十个脚本,在github.com/paoloap-py/agent-harness-guide:每层都有其保护,以及没有它的相同代理。每个都在你的机器上运行,无需API密钥,因为模型被替换为发出固定工具调用序列的脚本替身。你可以运行的差异胜过你必须信任的声明。

git clone https://github.com/paoloap-py/agent-harness-guide
cd agent-harness-guide

python3 run_all.py        # 所有五层,保护关闭然后开启,并排
python3 test_harness.py   # 断言上述所有差异,10个检查

README将五层作为审计检查表。对于每一个,问题相同:这是由代码强制执行,还是由提示中的一句话强制执行?任何你无法用文件路径勾选的框都是你希望的规则。

10、这让你处于什么位置

我的规则很好。它在错误的地方待了两周,代价是十五次违规,其中十一次在那个下午,以及我想要回来的一天。

移动规则是执行它的方式。文字从未改变。

这就是全部想法。你购买模型。你构建它周围的一切,而那一半决定了你的代理成本以及它做你未批准的事情的频率。两者都是工程工作。只有一个是你的。


原文链接:The Complete Guide to Agent Harnesses (With Code)

汇智网翻译整理,转载请标明出处