最近翻 Multi-Agent 相关项目时,“Harness”这个词出现的频率越来越高。B站、GitHub、技术社区里,DeepSeek Harness、Codex Harness、Harness Engineering 这些概念被反复提及,不少教程甚至用“六十集全量讲解”“七天从小白到大神”来吸引注意力。但很多开发者的第一反应其实是:这又是一个换皮新词?它和 Agent 到底有什么区别?我为什么要学它?
先给一个明确判断:如果你目前只写“一个 Python 脚本 + 一个大模型 API + 几段 Prompt”的小工具,Harness 确实不是刚需。但一旦你要做多 Agent 协作,要让 AI 调用外部命令,要上生产环境并接受别人 review 代码,Harness 就是决定项目能否稳定维护的分水岭。它解决的不是“模型能不能回答”,而是“AI 应用能不能像正规软件工程一样被控制、被审计、被回滚”。
这篇文章不会尝试复述六十集视频的全部内容,而是提炼一条最精简的 Harness 工程学习路径:先搞懂 Harness 为什么出现,再拆开 Agent、SandBox、Skill 这几个核心概念,最后用一套可以在本地直接跑通的最小工程代码,把 Multi-Agent 流水线串起来。读完你会得到三个东西:一个能直接运行的最小 Harness 工程骨架;一份判断 Harness 相关报错和异常的思路;一套在真实项目里使用 Harness 的工程建议。
1. Harness 工程到底解决什么问题
先从一个真实场景说起。假设你正在做一个“文档自动审校”功能:大模型读取一篇技术文档,找出其中疑似不合格的句子,然后给出修改建议。单 Agent 版本很容易写,几行代码就能跑通。但是产品经理很快会加需求:文档需要先拆分章节,不同章节分配给不同的审校 Agent;审校 Agent 还需要调用一个内部的拼写检查脚本;最后要有一个汇总 Agent 输出报告。
这个时候问题就来了。
状态放在哪里?多个 Agent 之间怎么传数据?如果某个 Agent 调用外部命令,它有没有权限删除文件?一次运行失败后,怎么知道是哪一步出了错?如果把大模型换掉,整个流程还能不能复现?
这些问题已经不再是“怎么设计 Prompt”能够解决的,而是一个软件工程问题。Harness 就是在这里介入的。
Harness 工程可以理解成一套“AI 应用运行控制框架”:它把一次 AI 任务的输入、模型调用、工具执行、结果校验、日志、权限、失败重试全部纳入统一的运行环境。没有它,Agent 是一堆松散的函数;有了它,Agent 是在一个可监控、可限制、可恢复的“驾驶舱”里工作。
所以 Harness 工程真正降低的不是模型调用成本,而是三类软件工程成本:
- 可控成本:每个 Agent 能做什么、不能做什么,不再依赖模型自觉,而是由 Harness 的权限边界决定。
- 可观测成本:每次运行的输入输出、每个 Skill 的执行情况、每次沙箱命令的结果都有日志。
- 可复用成本:模型调用、文本处理、命令执行等原子能力抽成 Skill,不同 Agent 可以共享。
如果你已经在做 Multi-Agent 应用,并且开始被“结果不稳定、问题定位难、代码不可维护”困扰,那么 Harness 就是下一步该补的课。
2. Harness、Agent、SandBox、Skill 的概念与区别
很多文章把这四个词混在一起讲,导致新手越看越乱。这里我用工程类比把它们拆开。
2.1 Agent:决策和执行者
Agent 是具备自主决策和执行能力的程序模块。它接收一个任务对象,根据输入决定“接下来调用哪个模型能力、哪个工具”,然后把结果写到输出对象里。Agent 可以简单到只做一次文本分类,也可以复杂到包含多轮推理循环。
关键认知:Agent 解决的是“做什么、怎么做”,但它没有义务保证“这次运行是否合法、是否能复现”。
2.2 Skill:可复用的原子能力
Skill 指的是可以被多个 Agent 复用的原子能力,比如“统计文本字数”“查找关键词”“调用拼音纠错接口”“读取指定数据库表”。Skill 本身不感知业务流程,它只对外提供一个稳定的输入输出接口。
为什么需要 Skill?如果没有这个抽象层,每个 Agent 都会自己写一套文本处理代码,很快就出现重复实现和参数不一致。把能力抽成 Skill 之后,Agent 只关心“调用哪个 Skill”,而不关心 Skill 内部怎么实现。
2.3 SandBox:隔离执行环境
SandBox 是执行外部命令或不可信代码时的隔离环境。它限制 Agent 能访问哪些资源、能执行哪些命令、能读写哪些目录,从而避免模型生成的命令直接操作到真实系统。
注意一点,SandBox 不等于“安全”。它更多是 Harness 工程里的一道护栏。生产环境中的沙箱需要依赖容器、虚拟机或独立进程来实现,而不是一个简单的判断开关。
2.4 Harness:连接模型与工程的控制壳
Harness 是包裹在 Agent、Skill、SandBox 之外的“工程壳”。它定义了任务如何创建、Agent 之间如何传递数据、Skill 如何注册、SandBox 如何启用、日志如何输出。
Harness 和 Agent 的区别是最容易被问到的。简单说:Agent 是工具箱里的工人,Harness 是工地管理制度。工人可以自己选工具、按顺序干活,但管理制度规定了他能进哪个区域、必须戴什么安全帽、出了问题怎么追责。
| 概念 | 核心职责 | 抽象层级 | 类比 |
|---|---|---|---|
| Agent | 决策和执行任务 | 业务逻辑层 | 工人 |
| Skill | 提供可复用的原子能力 | 能力层 | 专用工具 |
| SandBox | 限制执行边界 | 隔离层 | 安全围栏 |
| Harness | 控制流程、权限、日志、恢复 | 工程控制层 | 工地管理制度 |
3. 为什么 Multi-Agent 开发需要 Harness 而不是简单函数调用
传统软件工程中,一次业务请求的主控逻辑是开发人员写死的:先调 A 接口,再调 B 工具,最后写响应。只要没有故障,执行路径是确定的。
Multi-Agent 应用改变了这一点。主控逻辑的一部分被交给了模型,模型会根据输入动态决定调用哪个工具、哪条分支。这带来灵活性的同时,也带来了不确定性:同一段输入,两次运行可能走不同的分支;模型可能生成一个你从未预期的工具调用;某个 Agent 的输出格式可能发生变化。
Harness 工程就是在这种背景下出现的。它做的事情,是把“模型自主决策”重新放回工程约束的笼子里。
具体来说,Harness 提供了四层约束:
第一,结构约束。所有 Agent 之间传递的数据使用统一 Task 对象,而不是随意往全局变量里塞值。这样每个节点的输入输出都是可检查的。
第二,权限约束。Agent 调用外部命令、访问网络、读写文件之前,必须先经过 SandBox。Harness 可以在配置层面决定哪些命令允许执行,哪些必须拒绝。
第三,流程约束。Multi-Agent 的下一步动作不再完全由模型自由发挥,而是由 Harness 编排器根据当前步骤结果决定。模型在限定的分支里做选择,而不是无限发散。
第四,可观测约束。每一步运行都记录 trace_id,所有 Skill 调用和命令执行都有日志。出了问题时,可以按链路回溯。
没有这层约束,Multi-Agent 应用很容易出现“demo 能跑、上线就崩”的情况。因为 demo 只需要展示正常路径,而生产系统必须面对异常分支、权限失控、模型输出漂移这些问题。
这也是为什么社区近期讨论的 DeepSeek Harness、Codex Harness 等概念,本质上都不是在讨论某个神秘技术,而是在讨论“如何给 AI Agent 应用加上标准化的工程壳”。
4. 环境准备与工程结构
这一节开始进入实操。先说明,本文的示例不依赖任何特定大模型,只使用 Python 标准库,目的是用最小成本讲清楚 Harness 的核心结构。如果你之后要接云端大模型 API,再安装对应 SDK 即可。
4.1 环境要求
- Python 3.10 或更高版本。如果系统版本较旧,请至少保证 Python 3.8 以上,并把 dataclass 特性准备好。
- 一个独立的虚拟环境,避免污染全局 Python 环境。
- 不需要 GPU,不需要额外数据库。
- 如果后续要接入云端大模型,通常需要一个 API Key,但从环境变量读取,不要硬编码到代码里。
4.2 创建项目目录
建议命名为harness_demo,目录结构如下:
harness_demo/ ├── harness.py # Harness 核心组件 ├── skills.py # Skill 实现 ├── main.py # 主流程编排 └── config └── app.json # Harness 配置在终端里执行:
mkdir harness_demo cd harness_demo python -m venv .venv source .venv/bin/activate如果你使用的是 Windows PowerShell,激活命令改为:
.venv\Scripts\activate本示例只用标准库,所以不需要 pip install 任何额外包。目录里的config/app.json是示例配置,后面的代码会用到其中部分字段。
5. 核心流程拆解:从 Task 到 SandBox
在写完整代码之前,先理解 Harness 工程四条核心链路。这一步非常关键,因为后面所有代码都是在实现这四条链路。
5.1 Task:统一的数据载体
Task 对象是 Harness 工程中跨 Agent 传递数据的标准结构。它至少包含任务 ID、输入数据、输出数据和元信息四部分。
为什么不能用普通 dict 代替?dict 确实很方便,但到后期你会发现:没人知道某个 key 是谁写入的、value 是什么类型、某个 Agent 改没改过别人的数据。Task 把输入和输出明确分开,还允许通过 meta 记录运行过程信息,这是工程化最基本的一步。
5.2 SkillRegistry:技能注册中心
SkillRegistry 是一个注册表,Agent 不直接依赖某个函数,而是通过名字查找技能。这样带来的好处是:替换技能实现版本时,不需要修改 Agent 代码;多个 Agent 可以共享同一套能力;技能的调用情况可以被统一埋点记录。
5.3 BaseAgent:Agent 的接口契约
BaseAgent 定义了一个 run 方法,输入 Task,输出 Task。每个具体 Agent 只需要实现自己的 run 逻辑。这个接口约束保证了 Multi-Agent 流水线中的每个节点都可以被单独测试、单独替换。
5.4 Sandbox:外部命令执行护栏
Sandbox 是命令执行的统一出口。在实际 Harness 工程中,Agent 不应直接使用 subprocess 调用系统命令,而是统一走 Sandbox。Sandbox 内部可以判断开关状态、执行超时、记录日志。本示例给出一个教学级实现,生产环境请使用容器或独立进程做真正隔离。
6. 完整示例:一个带 Skill 的 Multi-Agent 审校流水线
现在把上面的设计落到代码里。示例场景是“文档审校流水线”:先从原文中拆出文本片段,再对片段做关键词质检,最后汇总报告。为了可读性,三个核心组件放在同一个文件里。
6.1 Harness 核心组件
文件路径:harness_demo/harness.py
# harness_demo/harness.py from abc import ABC, abstractmethod from dataclasses import dataclass, field from typing import Any, Callable, Dict, List @dataclass class Task: """任务对象:整个 Harness 内部传递数据的唯一载体。""" task_id: str input_data: Dict[str, Any] output: Dict[str, Any] = field(default_factory=dict) meta: Dict[str, Any] = field(default_factory=dict) class SkillRegistry: """技能注册表:把可复用的原子能力集中管理。""" def __init__(self): self._skills: Dict[str, Callable] = {} def register(self, name: str, func: Callable) -> None: self._skills[name] = func def execute(self, name: str, **kwargs) -> Any: if name not in self._skills: raise KeyError(f"Skill not found: {name}") return self._skills[name](**kwargs) def list_skills(self) -> List[str]: return list(self._skills.keys()) class BaseAgent(ABC): """Agent 基类:流水线中的每个节点都继承它。""" def __init__(self, name: str, role: str, registry: SkillRegistry): self.name = name self.role = role self.registry = registry @abstractmethod def run(self, task: Task) -> Task: """处理输入,把结果写回 task.output。""" pass class Sandbox: """最小沙箱:仅用于教学演示,真实项目应使用容器或独立进程隔离。""" def __init__(self, enabled: bool = True, timeout: int = 10): self.enabled = enabled self.timeout = timeout def execute(self, command: str) -> str: if not self.enabled: raise RuntimeError("Sandbox is disabled. Refuse to run external command.") import subprocess result = subprocess.run( command, shell=True, capture_output=True, text=True, timeout=self.timeout, ) return result.stdout.strip()这个文件里的代码有两处需要重点说明。
第一,SkillRegistry.execute通过名字查找函数。如果技能未注册,直接抛KeyError,这个异常会在完整代码中让我们快速定位“技能没注册”的问题。
第二,Sandbox.execute是教学实现,它只是用subprocess执行命令。所以在注释里我特别强调,真实项目必须做成进程级隔离或容器级隔离。这段代码更重要的是展示“所有命令执行必须经过统一出口”的设计思想。
6.2 Skill 定义
文件路径:harness_demo/skills.py
# harness_demo/skills.py def upper_text(text: str) -> str: """把文本转成大写。""" return text.upper() def count_words(text: str) -> int: """统计文本单词数。""" return len(text.split()) def find_keyword(text: str, keyword: str) -> bool: """检查文本是否包含指定关键词。""" return keyword in text这三个 Skill 都比较简单,但已经足够演示“注册 - 调用”的完整流程。真实的 Skill 可以是一个复杂的模型调用封装、数据库查询函数或者内部 HTTP 接口。
6.3 Multi-Agent 主流程
文件路径:harness_demo/main.py
# harness_demo/main.py from harness import Task, SkillRegistry, BaseAgent, Sandbox from skills import count_words, find_keyword, upper_text # 1. 注册 Skill registry = SkillRegistry() registry.register("count_words", count_words) registry.register("find_keyword", find_keyword) registry.register("upper_text", upper_text) class ExtractAgent(BaseAgent): """第一环节:从原始文档中抽取待审校的文本片段。""" def run(self, task: Task) -> Task: raw_text = task.input_data.get("document", "") fragments = [ fragment.strip() for fragment in raw_text.split(".") if fragment.strip() ] task.output["fragments"] = fragments task.meta["fragment_count"] = len(fragments) return task class CheckAgent(BaseAgent): """第二环节:对每个片段做关键词检查,模拟模型质检。""" def run(self, task: Task) -> Task: keyword = task.input_data.get("keyword", "TODO") problems = [] for fragment in task.output.get("fragments", []): if not self.registry.execute( "find_keyword", text=fragment, keyword=keyword ): problems.append(fragment) task.output["problems"] = problems return task class SummaryAgent(BaseAgent): """第三环节:汇总结果,输出最终报告。""" def run(self, task: Task) -> Task: problems = task.output.get("problems", []) task.output["summary"] = { "fragment_count": task.meta.get("fragment_count", 0), "problem_count": len(problems), "level": "pass" if len(problems) == 0 else "need_review", } return task def build_pipeline(): """构建 Multi-Agent 流水线。""" return [ ExtractAgent("extract-agent", "抽取器", registry), CheckAgent("check-agent", "质检器", registry), SummaryAgent("summary-agent", "汇总器", registry), ] def run_pipeline(agents, task: Task) -> Task: """按顺序执行 Agent,前一个 Agent 的输出作为后一个的输入。""" for agent in agents: print(f"[{agent.role}] {agent.name} 正在处理 {task.task_id}") task = agent.run(task) return task if __name__ == "__main__": demo_task = Task( task_id="task-001", input_data={ "document": "Harness工程是AI应用开发的关键。TODO:补充沙箱配置。Skill是可复用能力。", "keyword": "TODO", }, ) agents = build_pipeline() final_task = run_pipeline(agents, demo_task) print("结果:", final_task.output["summary"]) # 演示 Sandbox 命令执行 sandbox = Sandbox(enabled=True, timeout=5) print("沙箱执行结果:", sandbox.execute("echo hello-harness"))这段代码的核心逻辑可以分成四步看。
第一步,注册 Skill。registry是所有 Agent 共享的技能中心,CheckAgent并不直接调用find_keyword函数,而是通过self.registry.execute("find_keyword", ...)来调用。
第二步,按顺序执行 Agent。run_pipeline函数按列表顺序把同一个Task传给三个 Agent。ExtractAgent负责拆解文本,CheckAgent从 Task.output 中读取 fragments,再把有问题的片段写回。SummaryAgent最终汇总。
第三步,体会数据传递方向。注意每个 Agent 都是修改同一个 Task 对象,前一个 Agent 写入的 output 字段,后一个 Agent 可以读取。这就是 Harness 工程里最基础的“结构化流水线”。
第四步,Sandbox 演示。主流程最后实例化一个 Sandbox 并执行echo hello-harness。在实际项目中,这里应该是一个经过权限校验的命令执行入口。
7. 运行结果与验证方式
在harness_demo目录下执行:
python main.py预期输出:
[抽取器] extract-agent 正在处理 task-001 [质检器] check-agent 正在处理 task-001 [汇总器] summary-agent 正在处理 task-001 结果: {'fragment_count': 3, 'problem_count': 1, 'level': 'need_review'} 沙箱执行结果: hello-harness如何判断运行成功?
关键看三处。第一,fragment_count应该等于 3,说明ExtractAgent成功把文档按句号拆成了三部分。第二,problem_count等于 1,因为只有第二句包含TODO关键词,CheckAgent的逻辑正确。第三,沙箱执行结果输出hello-harness,说明命令走通了沙箱出口。
如果失败,第一步看 Python 回溯堆栈。最常见的错误包括:模块找不到、当前目录不在 Python 路径里、虚拟环境未激活。按下面顺序排查:
# 确认当前目录 pwd # 确认 Python 版本 python --version # 直接运行脚本 python -m main把这段最小流程跑通之后,你就可以把文本处理逻辑替换成真正的大模型 API 调用,把CheckAgent的规则判断换成“让模型判断片段是否合规”,从而得到一个更接近生产形态的 Harness 工程。
8. 常见问题与排查思路
这一节整理 Harness 工程学习中最常见的五个问题。每一个问题都来自实际项目中的典型场景,而不是凭空想象。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 启动时报 ModuleNotFoundError | 未激活虚拟环境,或依赖未安装 | 检查python -m pip list是否包含对应包 | 激活虚拟环境,按依赖清单安装 |
| 运行提示 Skill not found | 技能名称拼写不一致,或注册顺序晚于调用 | 在调用前打印registry.list_skills() | 统一技能命名,启动时先注册全部 Skill |
| 沙箱提示 disabled no sandbox | 配置中沙箱开关为 false,或代码绕过了沙箱直接执行命令 | 检查配置文件里sandbox.enabled的值,搜索代码里的 subprocess 调用 | 所有外部命令统一走 Sandbox,默认关闭不信任命令 |
| Multi-Agent 运行很久不结束 | 某个 Agent 内部出现循环,或模型调用超时 | 查看每个 Agent 的开始和结束日志,确认卡在哪一步 | 为每个 Agent 增加最大步数和执行超时,超时后走 fallback |
| 结果不稳定,同一次输入两次输出不一致 | 模型输出格式不稳定,或 Agent 依赖了未排序的数据结构 | 打印模型原始输出与解析结果 | 增加输出 schema 校验,解析失败时进入重试或人工处理 |
| 日志太多,问题不好定位 | 没有统一 trace_id,无法串联同一任务的日志 | 检查日志中是否包含 task_id | 在 Task 创建时生成唯一 ID,所有日志统一带上该 ID |
这里特别说一下disabled no sandbox这类报错。它通常不是某一个框架的标准报错,而是沙箱开关被关闭时的提示。出现这个信息,真正的排查方向不是“如何绕过沙箱”,而是“为什么你的运行环境没有启用沙箱”。是配置里没开,还是当前环境不被允许执行外部命令,还是安全策略默认拒绝。绕过沙箱是最危险的做法。
9. 最佳实践与工程建议
跑通最小示例只是第一步。真正要在项目里使用 Harness 工程,下面几条建议值得直接进团队规范。
9.1 使用 Task 对象传递数据,不要堆全局变量
Multi-Agent 项目一旦超过两三个 Agent,全局变量就会变成维护灾难。你很难追踪某个字段是什么时候被谁写入的,也容易在并发场景下出数据竞争。统一使用 Task 对象,并在每次 Agent 执行前后打印核心字段,是成本最低的排查手段。
9.2 所有外部命令必须经过 Sandbox,且默认拒绝
如果你的 Agent 会执行 Python 脚本、Shell 命令或调用内部工具,不要直接写 subprocess。先定义一个 Sandbox 接口,然后在接口内部做命令白名单、超时、日志记录。默认情况下,未显式放行的命令应该被拒绝,而不是默认允许。
9.3 Skill 尽量“小、纯、可测试”
Skill 设计得越小越好。一个 Skill 只做一件明确的事,输入输出都是基础类型或简单的数据结构。这样你可以为每个 Skill 单独写单元测试。如果一个 Skill 内部既调数据库又调大模型还改文件,一旦出错,定位成本会非常高。
9.4 给模型调用设置超时和重试
把大模型调用接进 Harness 后,一定要考虑超时。模型服务偶尔变慢,某个 Agent 阻塞会导致整个流水线卡死。比较稳妥的做法是:每个模型调用设置独立的超时时间,超时后先重试一次,仍然失败则把异常信息写进 Task.meta,并走降级分支。
9.5 生产环境控制权限与审计
如果 Harness 工程部署在服务器上,并且 Agent 能接触生产数据,请务必遵守最小权限原则。具体来说:
- 沙箱进程使用独立操作系统用户,不共用应用主账号。
- 数据库账号只授予任务所需的最小读写权限。
- 所有 Agent 执行的关键操作写审计日志。
- 涉及删除、修改配置等敏感操作,先经过测试环境验证,再制定回滚方案。
不要相信模型永远会按预设路线走。Harness 的价值恰恰在于:即使模型走了一条意外路径,你的权限边界、执行沙箱和审计日志仍然能拦住风险。
9.6 评估集先行
不少 Multi-Agent 项目“跑起来容易,改起来崩溃”的主要原因是没有评估集。建议在写第一个 Harness 流水线时,同时准备 20 到 50 条典型输入,每条输入都标注期望输出。每次修改 Agent 逻辑或更换模型后,都跑一遍评估集,对比成功率。这一步会让你的工程迭代速度明显提升。
10. 总结与后续学习方向
Harness 工程不是一个炫技词汇,它是 AI 应用从小工具走向可维护系统过程中必然出现的工程层。把视角拉高一点:传统的软件工程控制的是代码逻辑,而 Harness 工程要控制的不只是代码,还有模型的决策过程和工具执行过程。Multi-Agent 的下一步不是堆更多的 Agent,而是把现有的 Agent 放进一套可观测、有边界、可回滚的 Harness 里。
如果你正打算学习这一方向,我的建议是从本文这个最小 demo 开始,先把 Task、SkillRegistry、BaseAgent、Sandbox 四个概念串起来,跑通一次完整的流水线,再逐步把它替换成你真实项目里的模型调用和工具链。B站上那些几十集的教程可以作为备查资料,但真正重要的是在本地亲手跑通一个最小闭环,哪怕它只有三四十行代码。能稳定复现的小工程,比一百集看过即忘的视频更能说明问题。
下一步可以继续深入的方向有三个:第一,把示例里的 Sandbox 换成真正的容器隔离方案,并补充命令白名单策略;第二,为 Task 增加版本号和链路追踪,完善运行日志;第三,接入一个大模型 API,把 CheckAgent 的规则判断改成模型语义判断,让流水线真正具备“智能”。每走一步,你都会更清楚地感受到 Harness 工程与普通脚本开发之间的差距。