最近我连续看到三样东西:一个开源大模型社区里冒出来的 Harness 框架,一个商用编程助手背后的 Mods 扩展玩法,还有一个以 Durable 持久化为核心卖点的实验性项目。乍一看,这三者互不相干——一个是执行框架,一个是插件生态,一个是状态管理。但把它们放在一起琢磨几天之后,我发现它们其实都在往同一个大方向上使劲:AI 干活的方式,正在从“每次对话都是新的”转向“把模型放进一个持续运行、可扩展、有记忆的执行环境里”。
这个环境,业内一般就叫 Harness。
我为什么这么肯定它们属于同一个方向?因为我自己在过去大半年里,前前后后搭过三四套类似的 Agent 框架,踩了不少坑,也摸索出了一套还算稳定的做法。这篇文章不打算给你罗列概念,而是想从一个实际动手者的角度,把“Harness 新方向”这件事拆开聊聊:它为什么重要、三个案例各自贡献了什么、以及如果你也想自己搭一个最小的 Harness,应该从哪里入手。
1. Harness 是什么,为什么突然大家都在提
1.1 从“模型调用”到“模型放进环境”
以前我们开发 AI 应用,最典型的做法就是调用模型接口:写一段系统提示词,把用户问题拼进去,发给模型,拿回一段文本。这个过程简单直接,几行代码就能跑通。但它有一个天然的局限——你拿到的是“一次性回答”,而不是“一件被干完的事”。
举个例子,你让 AI 帮忙重构一个项目。它需要先了解项目结构,再决定改哪些文件,逐个修改,然后跑测试验证结果。如果只是单次调用,它根本做不到这些,因为单次调用没有状态。模型不记得自己刚才看过的文件,也不知道上一次改动有没有把别的功能搞坏。你只能手动把结果再喂回去,对话一长,自己就先晕了。
这就是最原始的痛点:模型本身只是一个“能生成的脑子”,它需要一套外部机制来帮它“持续干活”。而这套外部机制,就是 Harness。
我理解的 Harness,类比一下就是给模型配了一套“驾驶舱”。模型是司机,但它不能直接碰方向盘之外的东西。方向盘、油门、仪表盘,这些都由 Harness 来控制。模型通过工具接口发出指令,Harness 负责执行、记录、反馈结果,并在出错时兜底。
1.2 测试领域的 Harness 概念,怎么迁移到 AI 上
其实 Harness 不是新词。在软件测试领域,Test Harness 指的是围绕被测代码搭建的一套执行框架——它负责准备环境、提供桩模块、执行用例、汇总结果。被测代码本身不知道测试框架的存在,但它能在框架里稳定地跑起来,并且每一次运行结果都是可预期、可记录的。
AI 领域说的 Harness,本质上是这个思路的迁移。模型就是那个“被测对象”,Harness 在外面套一层基础设施:工具定义、执行循环、状态存储、结果验证。模型被限制在 Harness 设定的边界里活动,而 Harness 保证整个活动过程是可控、可追踪、可恢复的。
这个迁移很有意思,因为它把问题的焦点从“模型有多聪明”转移到了“系统有多可靠”。模型依然重要,但真正决定一个 Agent 能不能上生产环境的,往往是外围这套 Harness 做得好不好。
1.3 为什么这个方向突然变得重要
我觉得有三股力量在推动。
第一,模型能力足够强了。现在的开源模型和商用模型,在工具调用、代码生成、多步推理上的表现,已经能支撑真实的工作流,不再只是玩具。能力上来了,大家自然开始想“能不能让它独立干点正经事”。
第二,长任务的需求变多了。无论是代码重构、数据分析,还是文档整理,真实任务很少有一步能完成的。大家开始意识到,单纯把问题丢给模型是不够的,需要让它在多步操作中保持方向感,这就必须有状态管理和流程控制。
第三,生产环境要求可重现。没人敢把一个行为完全不可预测、无法恢复的 Agent 放进生产系统。Harness 提供了一个边界,让 Agent 的行为可以被日志记录、被回放、被测试,这才是工程上能被接受的东西。
还有一个我自己的感受:模型调用成本在持续走低,大家的关注点已经从“模型能生成什么”转向“用模型能干完什么”。干完一件事,靠的不是一次生成,而是一整套工程配合。Harness 就是这个工程配合的集大成者。
2. 三个看似不同的项目,各自贡献了什么
2.1 开源社区那个“驾驶舱”方案,做的是可控执行框架
第一个案例,是开源大模型社区里一个以“Harness”命名的方案。它的核心思路,就是把模型接进一个可控的循环里:准备一个工作目录,给模型暴露一组固定工具,包括读写文件、执行命令、跑测试等等,然后让模型自己规划步骤,但每一步都必须通过框架来执行,而不是模型凭空生成一段完整的所谓“结果”。
这个方案最打动我的地方,是它对“模型自主性”和“系统可控性”做了非常明确的边界划分。模型可以提出任何它想做的动作,但动作能不能执行、以什么权限执行、执行结果怎么反馈,全部由框架说了算。这个设计哲学在工程上极其重要——你在保护系统,也在保护模型不犯低级错误。
举一个我实际遇到的场景。早期我自己写 Agent 的时候,让模型直接返回一段“我已经修改了文件”的文本,但它其实根本没有权限改文件,只是在幻觉。后来我把操作改成“模型只能输出要执行的动作,由框架去落地”,这个幻觉问题就消失了。因为这个边界把“想”和“做”分离了,模型负责想,框架负责做。
2.2 编程助手背后的 Mods 生态,做的是可扩展能力层
第二个案例,是某个商用编程助手工具的 Mods 扩展玩法。它的思路是给编程助手增加一个“可插拔的指令模块层”。用户可以提交各种 mods,每个 mods 本质上是一段预设的行为规则,让助手在特定场景下带上特定的工作方式,比如“每次生成代码后必须检查未定义变量”“提交前自动跑一遍 lint”之类的约束。
以前,一个编程助手的能力边界是官方划定的,用户只能等官方更新。Mods 模式把这个逻辑彻底反了过来:能力边界由社区共同扩展。你用不上某个 mods 就不加载,用得上就装一个。这个模式很像浏览器插件生态,或者说像乐高积木——基础框架只提供一个最小闭环,其余能力靠组合获得。
放到 Harness 的方向上看,Mods 解决的是“扩展性”问题。一个 Harness 如果只能做预设好的固定流程,那它只是脚本。但当你把工具、验证规则、行为策略都做成可插拔的模块,它就变成一个真正意义上的平台。这也是为什么我认为 Mods 不是一个小功能,而是 Harness 类架构里至关重要的一块拼图。
我实际试过在自己的 Harness 里模仿这种设计。我给框架做了一套“行为插件”,每个插件就是一个包含触发条件和执行逻辑的模块,启动时统一加载。效果比我预想的好——新增能力的时候不需要改动核心循环代码,只是在目录里多放一个配置文件。这种模式一旦跑通,扩展成本低到可以忽略。
2.3 那个 Durable 项目,做的是状态持久化
第三个案例,是一个以 Durable 持久化为核心卖点的实验项目。它的具体做法是:把对话历史、命令历史、文件变更记录、当前任务进度,全部序列化到磁盘上。这样即使进程崩溃、服务重启,Agent 也能从上次中断的地方继续干活,而不是从头开始。
这听起来像是“顺手做一下”的功能,但实际动手的人会知道,这恰恰是 Harness 最难也最容易被忽略的部分。模型的上下文窗口总是有限的,你不可能永远把全部历史都塞给模型。但真实的任务往往要跨多个时间段,今天改了一半的代码,明天还要接着改。没有持久化的 Harness,本质上和“每次对话都是全新的”没有任何区别。
我当时看到这个项目的第一反应是:它把一个长期被当作“附加项”的东西——状态管理——升级成了“核心设计目标”。它的做法也很扎实:不光是保存文本对话,而是把整个工作区的状态变更都记录下来。这意味着恢复的不只是“聊到哪儿了”,而是“干到哪儿了”。
这三样东西,表面上一个做执行边界、一个做扩展生态、一个做状态持久化,但本质上是同一个问题的三个侧面:怎么让 AI 不只是“会聊天”,而是“能干活”。这个问题的答案,就是 Harness 化——给模型一个可以持续运行、可扩展、有记忆的完整执行环境。
3. 从零搭一个极简 Harness,关键环节怎么设计
3.1 最小闭环需要哪几个核心组件
先不要想太复杂的架构。一个 Harness 如果要从零搭起来,我觉得最少需要四个组件。
第一是模型接口。它负责和模型通信,不管是调用云端接口还是本地推理,统一封装成一个函数就行。第二是状态存储。它负责保存所有历史交互和任务进度,可以是内存里的列表,也可以是磁盘上的文件。第三是工具执行器。它负责把模型的输出解析成具体动作,并在真实环境里执行这些动作——比如读写文件、运行命令。第四是循环控制。它负责决定 Agent 什么时候该继续干活,什么时候该停下来。
这四个组件合在一起,就构成一个最简 Agent 循环。模型在循环里反复经历“接收当前状态—生成下一步动作—执行动作—把结果追加回状态”的过程,直到任务完成或达到终止条件。
3.2 一个可以跑的极简骨架
下面这段代码,是我自己平时用来做实验的最简版本。它不是生产级方案,但结构完整,能让你一眼看明白 Harness 的内部逻辑。我用的是 Python,模型接口先用一个假函数占位,方便你在没有接口的情况下也能跑通整个流程。
import json import os import time # 第一步:模型接口。真实场景里替换成你的模型调用 def call_model(history): # 这里用最简单的方式模拟模型返回一个动作 last_message = history[-1]["content"] if "写文件" in last_message: return {"action": "write_file", "path": "demo.txt", "content": "hello"} elif "完成" in last_message: return {"action": "finish", "result": "任务已完成"} else: return {"action": "read_file", "path": "demo.txt"} # 第二步:工具执行器。每个工具都必须是真实可执行的 def execute_action(action): if action["action"] == "write_file": with open(action["path"], "w", encoding="utf-8") as f: f.write(action["content"]) return f"已写入文件 {action['path']}" elif action["action"] == "read_file": if os.path.exists(action["path"]): with open(action["path"], "r", encoding="utf-8") as f: return f.read() return "文件不存在" elif action["action"] == "finish": return action["result"] return f"未知动作: {action}" # 第三步:循环控制。决定什么时候继续,什么时候停止 def run_harness(initial_task, max_iterations=5): history = [{"role": "user", "content": initial_task}] for i in range(max_iterations): response = call_model(history) observation = execute_action(response) if response["action"] == "finish": print(f"Agent 在第 {i+1} 轮完成任务:{observation}") return observation history.append({"role": "assistant", "content": json.dumps(response, ensure_ascii=False)}) history.append({"role": "tool", "content": observation}) print(f"第 {i+1} 轮:执行了 {response['action']},结果:{observation}") print("达到最大轮数,任务未完成") return None if __name__ == "__main__": run_harness("请写一个文件,然后告诉我完成")这段代码跑起来,你会看到一个最基础的 Agent 循环长什么样:模型提请求,工具执行器落地,观察结果回流,循环继续。真实项目里,call_model 会换成真正的模型接口,execute_action 会换成安全受控的沙箱执行环境,但整体的骨架是不会变的。
3.3 加入持久化,让 Harness 在重启后还能接着干
上面的骨架有个明显问题:一旦进程退出,下次启动什么也不记得了。要解决这个问题,最简单的办法是把 history 和一份工作区变更日志写到磁盘上,启动时先加载,没加载到就从头开始。
我给出一个非常轻量的持久化补丁,把这条逻辑加进去:
def save_state(history, state_path="harness_state.json"): with open(state_path, "w", encoding="utf-8") as f: json.dump(history, f, ensure_ascii=False, indent=2) def load_state(state_path="harness_state.json"): if os.path.exists(state_path): with open(state_path, "r", encoding="utf-8") as f: return json.load(f) return [] def run_harness_resumable(initial_task, state_path="harness_state.json"): history = load_state(state_path) if not history: history = [{"role": "user", "content": initial_task}] save_state(history, state_path) max_iterations = 5 for i in range(max_iterations): response = call_model(history) observation = execute_action(response) if response["action"] == "finish": print(f"任务完成:{observation}") return observation history.append({"role": "assistant", "content": json.dumps(response, ensure_ascii=False)}) history.append({"role": "tool", "content": observation}) save_state(history, state_path) print("达到最大轮数,任务未完成") return None你可能会觉得,这不就是把列表存了个盘吗?简单是简单,但它的意义在于:一旦历史记录成为可恢复的持久化对象,Agent 就不再依赖一次进程的生命周期。你可以今天跑三轮,关掉电脑,明天继续跑第四轮。这个体验,和原来“一次对话定生死”是完全不同的。
3.4 这里面的关键设计决策
这些代码看起来简单,但有三个设计点值得你细品。
第一个是把模型输出当数据。模型返回的不是最终答案,而是一个动作描述,由工具执行器去解析并执行。这个设计让“思考”和“执行”彻底分离。模型是在给执行器提建议,而不是自己声称干完了什么。这一点,我认为是整个 Harness 架构最重要的原则。
第二个是终止条件必须明确。循环控制里我写了 max_iterations,这是保底方案。真实世界里,模型可能把同一个工具调用重复一万次,如果没有轮数限制,你的费用账单会非常壮观。所以任何 Harness 都必须同时设置软性终止条件——比如模型自身判断任务完成,和硬性终止条件——比如最多执行 N 轮。
第三个是状态要完整回流。工具执行完的每一个结果,都要追加进历史记录里,让模型下一步能看到。很多第一次写 Agent 的人会忽略这步,结果模型像盲人一样反复执行同一个错误动作。确保工具结果被及时记录并反馈给模型,是 Agent 能不能走向收敛的关键一环。
4. 常见问题与实战避坑记录
4.1 排查速查表
我在这类系统上踩过的坑,比顺利跑通的次数多得多。下面这个速查表,是我在实际开发中总结的经验,不一定全,但大概率能帮你省掉好几个小时。
| 现象 | 最可能的原因 | 怎么解决 |
|---|---|---|
| Agent 反复执行同一个动作 | 工具结果没有回流给模型,或者模型没看到结果 | 检查 history 里是否追加了 tool 返回内容 |
| 改完代码但功能反而坏了 | 缺少验证闭环,Agent 没有跑测试 | 在工具层增加“执行测试”工具,并在循环里强制调用 |
| 进程一重启就失忆 | 没有做状态持久化 | 至少把 history 和变更日志序列化到磁盘 |
| 对话上下文越聊越长,模型开始答非所问 | 历史记录无限制膨胀 | 对历史做截断或摘要,保留最近 N 轮加关键信息 |
| 模型干到一半偏离原始任务 | 缺少任务意图的持续锚定 | 在每轮提示词里保留原始任务描述,并要求模型先回顾再动作 |
| 工具调用报错但不是模型的错 | 动作解析太宽松,模型输出格式偶尔不规范 | 让模型输出结构化数据,解析失败时重试而不是直接报错 |
4.2 三个我印象最深的坑
第一个坑,是我早期特别迷信模型的“自觉”。我以为只要提示词里写了“修改完记得跑测试”,模型就会照做。结果它在实际干活的时候经常跳过测试直接结束。后来我改成硬编码:在循环控制里规定,凡是涉及代码修改的动作之后,如果没有跟一个测试工具的调用,就不允许模型进入完成状态。这一改,解决问题的能力立刻上升了一个级别。模型的自觉靠不住,要靠流程保证。
第二个坑,是上下文爆炸。有一段时间我的 Agent 跑长任务跑到最后,质量肉眼可见地变差。日志一查,历史列表里塞了几百条工具结果,每条都很长,模型早被噪音淹没了。后来我做了两件事:一是对工具结果做长度截断,长输出只保留摘要;二是定期把旧对话压缩成一段总结,只保留近期细节。效果立竿见影。
第三个坑,是错误动作没有兜底。模型偶尔会生成一个格式不对的动作或者文件路径写错,我的第一版解析代码直接抛出异常,整个 Agent 就挂了。后来我改成对解析失败做重试,连续重试两轮还失败,再把这个情况当作工具结果回传给模型,让它自己调整。这招在很多场合比直接报错中止好得多,因为模型见过错误信息之后往往能自己纠正。
4.3 如果你也想搞这个方向,我给你一条建议
我个人觉得,不要一上来就追求大而全。别想着第一天就搭出一个多工具的复杂系统,那只会让你在调试地狱里消耗热情。从一个非常具体的场景切入,比如“让 Agent 独立完成一次 Markdown 文件的批量格式修订”,然后围绕这个场景,实现最小循环、一个文件工具、一个验证工具,跑通之后再慢慢加功能。
在动手之前,先想清楚你要解决的核心问题是什么。如果你头疼的是 Agent 做着做着就跑偏,你应该先加强循环控制;如果你头疼的是会话经常断,那你应该先做持久化;如果你头疼的是能力不够用,那你要做的是插件扩展层。这三个方向虽然都归到 Harness 大方向下,但发力点完全不同。找准自己的切入点,比什么都重要。
最后再分享一个小技巧。调试 Agent 循环的时候,我建议你在每个关键节点打印一份“透明日志”:记录当前轮次、模型提了什么动作、工具返回了什么结果、为什么继续或终止。这些日志平时看起来啰嗦,一旦 Agent 出问题,它们就是最直接的定位线索。我自己现在所有 Harness 相关项目都默认带这套日志,它已经帮我省下了无数排查时间。