返回博客

Harness工程四件套:Agent安全自动修复编码循环实战

人工智能4426
Harness工程四件套:Agent安全自动修复编码循环实战

这一期聊 harness 工程:用四个确定性组件把 LLM 包裹起来,让 Agent 在自动修复编码循环里安全地生成代码。我的实测结论是,模型负责"想",harness 负责"做、记、查",四件套缺了任何一件,自动修复都只能停留在 demo 阶段。

最近一轮技术演示里,演示者用 ADK 2.0 与 Antigravity SDK 组合跑通了一个自动修复编码循环:代码构建失败,Agent 自己定位错误、生成补丁、重跑测试,直到绿灯。整个过程不需要逐行人工审查。我按同一思路在 4sapi(https://4sapi.com)统一网关环境下复现并拆解了这套结构,下面是完整过程。

一、开篇:我为什么需要 harness

先回答一个直接的问题:Agent 生成代码的能力已经很强了,为什么还要在外面套一层工程结构?

原因在于模型本身的属性。LLM 是概率性的生成器,同样的 prompt 两次生成的代码可能完全不同;一次编译通过,不代表下一次修复还可靠。但自动修复编码循环里真正需要被信任的环节,恰恰是确定性的:补丁应用在哪个目录、允许访问哪些网络、失败后重试几次、这次修复算不算通过。这些环节如果交给模型临场发挥,结果就无法复现、无法审计、无法承担责任。

harness 工程的定义因此非常朴素:用确定性组件包裹 LLM。模型继续负责生成,但生成之外的一切——调度、隔离、记忆、判定——由确定性的代码接管。我拆解后的核心是四个组件:编排层、执行沙箱、状态持久化和验证工具,下面逐一展开。

二、开篇痛点:能写代码的 Agent,没人敢放权

自动修复编码循环的真正瓶颈不在模型会不会写代码,而在敢不敢让它自己改代码。我归纳了四个痛点:

这四个痛点指向同一件事:缺少模型之外的确定性保证。harness 四件套恰好分别回答这四个问题——编排层管流程、沙箱管风险、持久化管记忆、验证工具管结论。

三、原理速览:harness 工程是什么

用一张图概括 harness 的整体结构:

text
LLM(概率性的生成器)


harness(确定性组件)
      ├── 编排层        决定下一步做什么
      ├── 执行沙箱      在隔离环境里跑代码
      ├── 状态持久化    保存补丁、验证结果与迭代进度
      └── 验证工具      用确定性规则判定 pass/fail


输出:经过验证的代码变更

一句话概括:模型负责"想",harness 负责"做、记、查"。四个组件全部是确定性的,不依赖模型的临场发挥。这样设计的好处是:即使模型某轮生成了错误的补丁,失败也会被验证工具拦截、被沙箱限制在隔离环境内、被持久化记录在案,最终由编排层决定是否进入下一轮迭代。

四、第一件:编排层——循环的大脑

编排层回答的问题是"下一步做什么"。它是整个自动修复循环的调度中枢,演示里的 ADK 2.0 就承担了这部分职责。在一个典型的修复循环里,编排层负责:

编排层还决定失败信息的回喂方式。原始测试日志往往很长,直接全量塞进上下文既费 token 又稀释注意力;更稳妥的做法是截断、去重、只保留错误摘要与失败断言,让模型每一轮都看到"干净的失败信号"。

五、第二件:执行沙箱——风险半径的边界

补丁写出来之后必须真的跑一遍,自动修复才有意义。但"跑一遍"发生在哪里,决定了整个系统的风险半径。执行沙箱就是这道边界,我从三个维度控制它:

沙箱的意义在于把试错的代价兜住。自动修复本质上是一个反复试错的过程,前几轮生成的补丁大概率是错的;如果这些错误补丁直接跑在真实环境里,一次误删或误发布就可能造成事故。在沙箱里试错,成本只是一次隔离环境里的失败。

需要说明的是,沙箱不是万能的安全方案。如果沙箱里挂载了生产密钥、拥有无限制的网络出口和可写的数据库,它仍然可能造成严重影响。隔离、最小权限与验证必须组合使用。

六、第三件:状态持久化——循环的记忆与账本

自动修复是典型的长会话任务:模型调用、补丁应用、测试执行交错进行,一轮失败的输出是下一轮的输入。如果这些中间状态没有落盘,任何一次断连或进程重启都会让整个循环归零。

状态持久化组件保存三样东西:

持久化还有一个容易被忽略的价值:可审计性。修复结束后,谁在什么时间生成了什么补丁、验证结果如何,都能从持久化记录里完整还原。这比"模型记得自己做过什么"可靠得多——记忆会漂移,账本不会。

七、第四件:验证工具——确定性的裁判

验证工具回答"这次修复到底成没成"。它是整个循环里唯一有资格下结论的组件,判定依据是确定性的规则:编译是否通过、测试是否全绿、类型检查与 lint 是否有错误。

我把四个组件放在一起对比过各自的分工:

组件职责没有它会怎样
编排层调度循环、控制次数与超时循环没有边界,可能无限重试
执行沙箱隔离环境、限制文件/网络/资源错误补丁直接污染真实环境
状态持久化保存补丁、日志与进度断连即归零,无法审计追溯
验证工具确定性判定 pass/fail修复是否成功只能靠模型自评

验证工具的关键特征是"不依赖模型判断"。失败就是失败,退出码和断言结果是客观事实。只有当验证工具给出绿灯,编排层才会收工并把补丁作为最终结果输出;否则失败信息被回喂,进入下一轮迭代。这正是"不需要逐行人工审查"能成立的前提——人工审查替代不了,但确定性验证可以把需要人看的内容压缩到极少数边界情形。

八、自动修复编码循环:四件套怎么协作

把四件套串起来,就是完整的自动修复循环。我用文本图把流程固定下来:

text
触发:构建失败 / 测试失败


① 编排层:构造修复任务,携带错误信息与上下文


② 模型生成补丁(经统一网关调用)


③ 执行沙箱:应用补丁并试跑测试


④ 验证工具:编译 / 测试 / lint,得出 pass/fail

      ├── 通过 ──→ ⑤ 状态持久化落盘,输出修复结果


⑥ 失败信息截断整理,回喂模型 → 回到 ②(受循环次数上限约束)

循环的每一圈都有明确的所有者:编排层决定转不转下一圈,沙箱承担试错代价,持久化保证断点可恢复,验证工具给出唯一的通过依据。四件套是流水线关系,任何一件缺席,其他三件都会失真——没有沙箱,验证工具不敢放心跑;没有持久化,编排层无从恢复进度;没有验证工具,编排层不知道该不该收工。

九、接入 4sapi:把模型调用接到统一网关

循环里频率最高、也最需要稳定的环节是模型调用。我选择把这一环统一接到 4sapi(https://4sapi.com)网关,请求流如下:

text
我的修复循环(harness 编排层)


4sapi 网关(https://4sapi.com)
      │  统一格式 / 鉴权 / 限流 / 计费

模型官方接口

网关把鉴权、限流、计费和格式转换集中在一个入口,循环里只需维护一个 base_url。演示中的 ADK 2.0 与 Antigravity SDK 组合,走的是同一类接入姿势:Agent 框架负责编排与工具调用,模型服务统一由网关分发。

接入代码,Python 示例(OpenAI 兼容客户端):

python
import os
import subprocess
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["4SAPI_API_KEY"],
    base_url="https://4sapi.com/v1",  # 统一网关地址
)

MAX_ROUNDS = 5  # 编排层:循环次数上限

def apply_patch(patch_text, workdir):
    # 执行沙箱内应用补丁(示意:写入统一补丁文件后应用)
    patch_file = os.path.join(workdir, "fix.patch")
    with open(patch_file, "w", encoding="utf-8") as f:
        f.write(patch_text)
    subprocess.run(["git", "apply", "fix.patch"], cwd=workdir, check=False)

def run_verification(workdir):
    # 验证工具:确定性 pass/fail,不依赖模型自评
    result = subprocess.run(
        ["pytest", "-q"], cwd=workdir,
        capture_output=True, text=True, timeout=120,
    )
    return result.returncode == 0, result.stdout + result.stderr

def repair_loop(task, workdir):
    messages = [{"role": "user", "content": task}]
    for round_no in range(1, MAX_ROUNDS + 1):
        # 编排层:调用模型生成补丁
        resp = client.chat.completions.create(
            model="gemini-2.5-pro",  # 模型名按网关列表填写
            messages=messages,
            temperature=0.2,
        )
        patch = resp.choices[0].message.content
        apply_patch(patch, workdir)          # 沙箱内应用补丁
        ok, log = run_verification(workdir)  # 验证工具试跑测试
        if ok:
            return round_no, patch
        # 状态持久化:记录本轮补丁与失败日志
        with open(os.path.join(workdir, f"round_{round_no}.log"), "w") as f:
            f.write(log)
        # 失败信息截断回喂,进入下一轮
        messages.append({"role": "assistant", "content": patch})
        messages.append({"role": "user", "content": f"验证失败,日志:\n{log[:4000]}"})
    return None, None

rounds, final_patch = repair_loop("修复 build 失败,见附件错误日志", "./workspace")

接入时有两点值得注意:一是模型名要与网关侧开通的模型一一对应,调用前先核对;二是每轮循环的 token 消耗都在累计,用网关的用量统计接口盯住单任务成本,比事后看总账单直观得多。

十、成本与风险提示

把这一期的坑集中列一下:

十一、harness 工程治理清单

上线一个自动修复循环前,我至少按下面这份清单过一遍:

总结

harness 工程的价值不在模型本身,而在模型外面的确定性结构:编排层让循环有边界,执行沙箱让试错有代价上限,状态持久化让长会话可恢复、可审计,验证工具让"修复成功"有了唯一客观的定义。四件套组合起来,自动修复编码循环才真正摆脱了对逐行人工审查的依赖,从演示走向可落地。这一轮我在 4sapi(https://4sapi.com)的网关日志里,能同时看到循环的调用次数、token 消耗与模型分账,四件套加统一网关,是我目前最顺手的 Agent 工程化组合。欢迎在评论区发表想法,一起聊聊 harness 的接入与落地经验。

标签:HarnessAgent自动修复安全4sapi

推荐阅读

探索更多前沿洞察与行业干货。