1. 先把话挑明:Harness 不是新框架,是 Agent 的“底盘”
1.1 从“能聊天”到“能干活”,差的从来不是模型
我见过太多团队把 Agent 做成聊天框的皮,底下什么都没有。演示的时候,模型能写一首诗、编一段代码、把网上教程复刻得有模有样。一旦放到真实业务里,让它去查库存、改工单、回邮件、跑批处理,三天两头就挂:要么工具参数传错,要么上下文爆了,要么同一个动作循环五遍不知道停,要么干了一半把重要数据改了还没人知道。
问题不在模型,在于缺了一个“真正干活”的外壳。这个外壳,社区里现在叫 Harness,更讲究一点的叫 Harness Engineering。它解决的核心问题不是“让模型更聪明”,而是“让模型和真实世界之间有一层可靠的连接”:任务分解、工具调用、状态维护、上下文管理、权限控制、自我评估,全部由这套外壳来承担。
1.2 Harness 和 Agent 的区别:发动机与整车的差距
很多人把 Agent 理解成一个能调工具的模型,这是把发动机当成了整车。发动机负责做功,但真正让你开到目的地的,是底盘、变速箱、转向、刹车、仪表盘、油路电路那一整套东西。Harness 就是这套东西。模型常常被替换,今天 GPT,明天其他开源模型,但底盘的逻辑不该跟着模型换。
打个比方:Agent 是“一个人”,Harness 是“这个人的工作台和作业流程”。人会出错,工作台负责约束错误的影响范围;人会忘事,工作台负责把状态记下来;人会被打断,工作台负责从断点接着跑。所以你问“Harness 到底是什么”,答案不是某个库、某个框架的名字,而是一组职责完备的子系统。把它拆开来看,一个生产级 Harness 至少要管七件事,我下面逐个讲清楚。
1.3 为什么偏偏是 7 个子系统
我先声明:这不是 ISO 标准,也不是某家大厂的白皮书定义。这是我结合自己的落地经验、以及拆解开源社区几个 Harness 项目之后总结出的“最小分工集”。七个子系统分别是:任务规划、工具调用、记忆管理、上下文工程、执行编排、评估反馈、安全沙箱。多一个嫌重,少一个就跑不动产线。你拿 LangChain、AutoGPT、DSPy,或者社区里那些叫 deepseek harness、dsh harness 的项目去对照,它们骨子里都是这几块,只是命名和封装方式不一样。
2. 七个子系统逐个拆:机制、要点和常见设计错误
2.1 任务规划子系统(Planner):把“大目标”切成“下一动作”
规划子系统负责把用户的自然语言目标拆解成可执行的动作序列,并在执行过程中动态调整。它的输入是目标,输出不是“一句好听的话”,而是一份结构化的计划,比如步骤列表、每一步的验收标准、依赖关系。
这里最反直觉的一点:不要一开始就规划得太细。真实任务里,工具返回的结果会影响下一步,你提前规划十步,往往执行到第三步就走偏了。更稳的做法是“懒规划”——只明确当前这一步和下一步,每轮根据观察结果重新评估计划。我在实际工程里会让规划器输出一个 JSON 结构,包含current_step、next_steps、risks三个字段,然后交给下游执行。代价很低,但纠偏能力提升非常明显。
常见的错误是让规划器去复述任务。比如用户说“帮我订会议室”,规划模块输出一大段“首先我需要了解需求,然后查询会议室,然后……”——这些废话没有信息量。好的规划应该是可执行的指令集,比如“调用list_rooms获取空档,若无空档则通知订会议室的人”。
2.2 工具调用子系统(Tool Executor):让模型的手真正碰到世界
这是 Harness 里最容易出问题的一块。它做的事包括:维护工具注册表、把模型返回的“函数调用意图”翻译成真实的 API 请求、校验参数、执行、把结果整理后塞回上下文。
这里有几个关键设计。第一,每个工具必须有严格 JSON Schema 参数声明,模型输出先过校验再执行,不合法就直接拒绝并把校验错误返回给模型,让它自己修改。第二,工具描述要写“副作用”:比如“发送邮件会真实发出,不可撤回,需要用户确认”,“删除记录无法恢复”。模型并不真正理解副作用,你必须在描述里写清楚,并且让需要确认的动作走审批闸门。第三,工具注册表要控制数量。一次调用塞 50 个工具给模型,它必然会选错;按任务动态加载相关工具,效果远好于全量暴露。
我踩过的坑是“参数强转”。早期图省事,在代码里把字符串参数int()强转,结果模型传了个"NaN",直接异常。现在一律先做 Schema 校验,校验失败的错误消息会带回给模型做下一次修正,这个闭环是工具子系统稳定性的命门。
2.3 记忆子系统(Memory):会话记忆、工作记忆、长期记忆要分家
记忆子系统不是简单把聊天记录存下来。它至少包含三层:
- 工作记忆:当前任务的执行状态,比如“步骤2已完成,步骤3等待审批”,这部分要精确、结构化,运行时随时读取;
- 会话记忆:和用户的对话历史,用于多轮交互;
- 长期记忆:跨任务沉淀下来的事实和规则,比如用户偏好、常用系统操作方式,一般落到向量库里做相似度检索。
记忆系统的核心问题不是“存”,而是“忘”。上下文是有限的,你不主动做记忆压缩,记忆就会变成噪音。我的做法是给三块记忆分别设置预算:工作记忆每次执行都会整体重写,会话记忆超过窗口就做滚动摘要,长期记忆只有模型显式调用“记住某个事实”时才写入。把记忆当成一种“需要显式调用的工具”,而不是后台自动堆积的日志,这是从 Demo 到生产的关键一步。
2.4 上下文工程子系统(Context Manager):token 预算就是真金白银
这是 7 个子系统里最容易被忽略、但对稳定性影响最大的一个。它负责决定每一轮请求“把哪些内容放进去”,包括系统提示词、工具定义、历史对话、检索文档、工具执行结果。放错一样,模型就不干活;全放进去,上下文窗口就爆炸。
核心手段有三个:截断、摘要、检索注入。截断最简单,保留最近的 N 轮,丢掉最旧的;摘要适合长对话场景,每过几轮就用模型压缩一次历史;检索注入则是在需要专业知识时,从知识库里取最相关的片段放进上下文。工程上必须做 token 计数,发送前估算、返回后统计,每一轮的 token 消耗都要记账,否则成本失控的时候你根本不知道花在哪。
另外提醒一句:上下文越长,推理越慢、越贵、越容易“迷失在中间”。这不是玄学,是模型注意力机制的普遍现象。所以上下文工程的目标不是“塞满窗口”,而是“用最少的 token 表达最关键的信息”。
2.5 执行编排子系统(Orchestrator):状态机、重试和循环的闭环
编排子系统是 Harness 的心脏。它是一个运行时循环:构建上下文 → 调用模型 → 解析输出 → 执行工具 → 更新状态 → 判断是否结束 → 回到第一步。这个循环看起来简单,生产环境里全是细节。
第一是状态机设计。一个任务至少要经历PLANNING、EXECUTING、WAITING_APPROVAL、DONE、ERROR这几种状态。等待人工确认的状态必须显式存在,尤其是涉及写操作或对外发送消息时。第二是终止条件。所有循环都必须有最大步数限制和“动作指纹”检测——把每一步的(工具名、参数摘要)算个哈希,发现相同动作重复出现,就强制中断并要求模型换策略。第三是重试。API 超时、限流、临时故障要区分重试策略,指数退避是底线。第四是可观测性。每一步的 trace_id、模型调用、工具入参出参、token 消耗,全部落日志,这是之后排查和评估的数据基础。
没有编排子系统的 Agent 就是一段死循环代码,加了它才叫“系统”。
2.6 评估反馈子系统(Evaluator):让 Agent 知道自己干得好不好
Agent 没有“自我感觉”,你必须给它一个客观的评估器。评估分为两层:
- 过程评估:每一步都做合法性检查。比如规划是否偏离目标、工具调用是否成功、结果是否满足验收条件;
- 结果评估:任务结束后,用规则或模型裁判判断最终结果是否达标。规则能判断的用规则,规则判断不了的再上“模型当裁判”的 LLM-as-a-judge。
评估子系统还有一个重要作用:沉淀数据。既然 Harness 把整个执行过程都记录了,那每一次成功和失败都成为数据集。失败案例加上“正确做法”标注,就是最好的微调语料或者少样本示例。我见过的高级玩法是把评估器接进 CI/CD:每次改提示词、改工具定义,先跑一遍历史用例回归,分数不达标就不允许上线。这才是“Agent 工程质量”的体现。
2.7 安全沙箱子系统(Guardrails):权限、审计和成本红线
这一块在演示阶段从来没人提,上了生产就是命门。安全子系统要管四件事:
- 权限:工具必须分级。只读工具可自动执行;写操作(发消息、改数据)默认进人工审批;高危操作(删数据、转钱)必须双重确认;
- 沙箱:代码执行类工具一律丢到隔离环境跑,限制文件系统和网络访问;
- 密钥管理:API Key、数据库密码绝不允许出现在发给模型的提示词里,工具需要的凭据由 Harness 在执行时通过环境变量注入;
- 成本控制:每个任务设置 token 预算和工具调用次数上限,超了直接熔断,而不是让它无限烧钱。
我在生产环境里遇到过模型“灵机一动”自己拼接出了不该访问的接口地址。没有权限拦截,这不是事故,是灾难。安全子系统不是约束,是给 Agent 上的一份“授权范围内的自由”。
3. 从 0 到 1 手搓一个最小 Harness:核心流程与代码骨架
3.1 先搭运行时主循环
把七个子系统串起来的核心就是那一条循环。我用 Python 写了一个最小骨架,不依赖任何重量级框架,你自己就能扩:
@dataclass class HarnessConfig: max_steps: int = 10 require_approval_for: set = frozenset({"send_email", "delete_record"}) token_budget: int = 8000 class MinimalHarness: def __init__(self, llm, tools, context_manager, memory, evaluator, guards): self.llm = llm self.tools = {t.name: t for t in tools} # 工具注册表 self.context = context_manager # 上下文工程 self.memory = memory # 记忆子系统 self.evaluator = evaluator # 评估反馈 self.guards = guards # 安全沙箱 self.state = {"step": 0, "plan": [], "history": []} def run(self, user_task): self.state["goal"] = user_task while self.state["step"] < self.guards.max_steps: prompt = self.context.build(self.state) # 构建受控上下文 response = self.llm.chat(prompt, tools=list(self.tools.values())) if response.tool_calls: for call in response.tool_calls: self._execute(call) # 工具调用 + 审批闸门 self.state["step"] += 1 continue if response.final_answer: if self.evaluator.check(self.state, response.final_answer): return response.final_answer self.state["history"].append("评估失败,需要修正") continue raise HarnessError("超过最大步数,任务未完成")这段代码把关键职责都集中到了context.build、_execute、evaluator.check三个位置,这也暗示了 Harness 的设计原则:模型只负责“想”,后续每一步都被 Harness 的子系统把关。
3.2 工具注册与参数校验
工具注册表的核心是给每个工具写清楚“什么时候用、怎么用、有什么副作用”。我强烈建议用字典描述,而不是让模型自由看源码:
tools = [ Tool( name="list_rooms", description="查询会议室空档,只读操作,可自动执行。入参 date 格式 YYYY-MM-DD。", schema={ "type": "object", "properties": {"date": {"type": "string"}}, "required": ["date"], }, handler=room_service.list_available, ), Tool( name="send_invite", description="向参会人发送会议邀请邮件,会真实发出,需用户确认。", schema={ "type": "object", "properties": {"email": {"type": "string"}, "room_id": {"type": "integer"}}, "required": ["email", "room_id"], }, handler=mail_service.send_invite, need_approval=True, ), ]执行时先做 JSON Schema 校验,再查need_approval,需要审批的暂停任务,等待人在界面上点确认。校验失败就把具体的错误信息(“date 缺少字段 YYYY-MM-DD”)回写进上下文,让模型下一轮自己修正。这里有个细节:返回给模型的错误信息必须指路明确,不能只抛“参数错误”,否则模型会继续瞎猜。
3.3 上下文管理:发送前的最后一次把关
我习惯在context.build里做三件事:清点 token、滚动摘要、拼接工具定义。伪代码如下:
def build(self, state): parts = [] parts.append(SYSTEM_PROMPT) # 固定系统指令 parts.append(self.format_tools()) # 仅注入当前任务相关工具 parts.append(self.memory.get_working_state()) # 工作记忆:当前计划与进度 history = self._truncate(state["history"]) # 超窗历史做滚动摘要 parts.append(history) prompt = "\n\n".join(parts) if count_tokens(prompt) > self.budget: prompt = self._trim_to_budget(prompt) # 超预算强制裁剪 return prompt这里值得注意的两点:工具定义要按需动态注入,而不是每次都全量塞;工作记忆必须是结构化的 JSON,不能是自然语言长段落,否则模型会“迷失”在执行状态里。
3.4 把评估器和人工确认闸门接进去
评估器在最小实现里可以只是一个纯函数:检查最终答案是否回答了用户问题、检查执行过程中是否存在工具失败未处理、检查是否触发了审批清单里的动作。人工确认则做成事件——任务挂在WAITING_APPROVAL状态,等外部系统回包再继续。这两件事都在 Harness 里做,而不是让模型自己判断,这是原则。
3.5 社区开源项目在教什么
如果你不想从零搭,可以去翻翻社区里那些叫deepseek harness、dsh harness的项目。它们多数是“Agent 的工程模板”,会示范如何组织 skill 插件、如何让多个智能体协作编排、如何在启动时加载插件。我建议你把它们当“参考实现”而不是“拿来即用的框架”。因为 Harness 的每一块都和你的业务耦合:审批流程在你这叫 OA 审批,在他那可能叫工单系统;记忆系统在他那存的是代码库上下文,在你这存的是客户画像。骨架可以抄,血肉必须自己长。
4. 真实落地中的问题排查与避坑实录
4.1 最常见的 5 个跑挂原因
我把自己和身边团队在生产里遇到的典型故障整理了一张速查表:
| 现象 | 根因 | 处理办法 |
|---|---|---|
| 模型反复调用同一个错误工具 | 工具描述太模糊或工具数量过多 | 精简工具列表,按任务动态注入;描述中写清使用边界 |
| 工具参数总是幻觉生成 | Schema 不够严格,模型自由发挥 | 收紧 JSON Schema,校验失败的错误回写上下文让模型自纠 |
| 任务陷入死循环不终止 | 缺少状态指纹检测 | 对(工具名+参数)做哈希,相同动作连续出现即强制中断 |
| 上下文越跑越长,成本和延迟双高 | 缺少 token 预算和滚动摘要 | 发送前计数、超窗摘要,历史分页截断 |
| 启动时报 failed to load plugins | 插件版本与 Harness 内核不匹配 | 检查插件清单依赖版本,按最小集逐个启用,定位冲突插件 |
最后一条我单独说几句。插件机制虽然是好设计,但“加载一堆插件”和“能干活”是两回事。插件越多,模型的选择空间越大,出错概率也越高。我见过有项目启动时报failed to load plugins: 2 entries did not activate,排查了半天,结果是两个插件互相依赖版本冲突。后来我的原则是:默认只启用干活必需的核心插件,其他全部按任务懒加载。
4.2 工具调用失败后,关键不是报错,是“下一步怎么走”
很多 Agent 挂掉是因为工具调用失败之后没有恢复策略。我现在的做法是给工具执行结果分三档:
- 成功:结果正常写回上下文;
- 可重试失败:比如接口超时、限流,按指数退避重试;
- 不可恢复失败:比如参数非法、业务规则不允许,直接把错误原因写回上下文,让模型修改动作或者向用户求助。
中间还有一种容易被忽视的情况:工具“成功”了但结果异常。比如查询返回空列表,模型以为没查到,其实可能是查询条件错了。这种要在评估环节加一条规则——空结果不等于无结果,需要模型说明判断依据。
4.3 成本观测是 Harness 里最值得做的一块
生产级 Harness 必须回答三个问题:每个任务花了多少 token、每一步调用花了多少钱、失败的任务在前 5 步就暴露了还是绕了很多圈。我建议给每一步都记录(step, tool_name, input_tokens, output_tokens, status, duration_ms)。有了这张表,你才能发现那些“看起来成功、实际烧钱”的任务模式。比如某个任务平均 6 步完成,但偶尔某个情况会绕 30 步,那多半是规划或工具选型有问题,可以做专项优化。
另外一个容易被忽略的成本点是“模型误调用只读工具刷数据”。比如模型为了确认一个信息反复查询同一个接口,这属于编排层面的浪费,靠上下文管理里的工具结果缓存可以缓解:同参数同工具的查询结果在一个任务内复用。
4.4 关于 Harness 与 Agent 区别的两个高频误解
网上经常有人问“harness 和 agent 有什么区别”,我见过两种典型误解。
第一种是把 Harness 当成某个具体软件,到处问“harness 下载”。实际上主流的 agent 框架普遍内置了部分 harness 能力,但完整度参差不齐。你真正要找的不是某个安装包,而是一套“能跑生产任务的外壳工程”,它应该包含我们说的那七个子系统。
第二种是认为 Harness 是一次性搭好的“静态框架”。恰恰相反,Harness 是跟着业务一起演化的。工具增加了、审批流程变了、模型供应商换了,底盘的接线方式都要跟着调。它更像一个持续迭代的“第二产品”,而不是交付完就固定的中间件。
5. 几个我现在的固定做法
选型上我坚持“骨架轻、插件化、可观测”。核心 Harness 只保留规划、编排、上下文、安全这几块最基本的能力,工具和技能全部走插件注册,这样换模型、换业务都不需要动骨架。每次发布 Agent 技能或修改提示词,我会强制跑一遍历史用例回归,通过率低于基线就直接拦下,这个习惯帮我挡住了好几次“改完反而变笨”的上线事故。
另外一个做法是把“教训内存”利用起来。每次任务失败,我会把失败摘要、失败原因、修正动作写成一条记忆写入长期存储,后续同类型任务触发时,Harness 自动检索到相关教训并注入上下文。效果很直接:同一类坑,第一次和第二次都踩过的概率明显下降。这本质上就是让 Harness 成为团队经验的沉淀池。
最后说句实在话:模型能力会不断升级,但 Harness 这套分工逻辑短期内不会变。早一点把规划、工具、记忆、上下文、编排、评估、安全这七件事想清楚,你手上任何一个“能聊天的 Demo”,都能变成真正在产线上干活的 Agent。