想象一家经营得井井有条的餐厅。经理站在前厅,观察着客流、物资和人员状况,决定菜单如何设计、服务员如何调配、打烊时间如何确定。后厨的流水线则完全不同——厨师们按照标准作业流程,一个接一个地处理订单:读单、配菜、烹饪、摆盘、出餐。经理不会经常跑进后厨颠勺,厨师也不会擅自更改菜单。正是这样的分工,让餐厅既能灵活应对突发状况,又能稳定高效地批量出餐。 AI Agent架构中的Harness层与Loop层,恰好对应这对角色。Harness是“经理”,负责整体任务编排、做出流程决策、管理全局状态;Loop是“后厨流水线”,负责重复运行“大模型推理→调用工具→观察结果”的执行循环。本节将深入拆解两者的职责边界,帮你掌握分层原则、接口契约与协作机制,在真实系统中避免职责重叠造成的维护灾难。
学会划分Harness与Loop的边界,你就能设计出结构清晰、易于测试和维护的多Agent系统,这是AI应用架构师的核心能力。
可视化演示
展示AI Agent架构中Harness工程脚手架层与Loop推理循环层的职责边界划分、控制流归属、以及多Agent共享Harness独立Loop的分层实践,通过交互切换理解架构分层原则。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Harness层与Loop层:工程脚手架与推理循环分离 Harness层:输入清洗/安全过滤/输出格式化,不参与业务推理 Loop层:编排控制流/状态管理/工具调用,掌控推理决策 边界划分:数据经Harness清洗后进入Loop,推理结果经Harness格式化输出 Input → Harness(clean) → Loop(reason) → Harness(format) → Output 分层隔离工程职责与推理逻辑,降低耦合提升可维护性
层级定位
经理站在餐厅门口,目光扫过整个大厅——今天客流如何、库存还够不够、服务员排班是否合理。他思考的是“整体”:这家餐厅要往哪个方向走。而走进后厨,厨师们正沿着流水线作业,每个订单来了就按固定流程处理:读订单、配菜、下锅、摆盘、喊服务员上菜。他们在意的是“当前这一步”:这道菜做好了没?
在AI Agent架构中,这种分工被清晰地划分为两个层次。Harness层就像那位经理,它是整个任务执行的编排者——定义任务目标、选择执行策略、管理生命周期、协调多组件协作。Loop层则像后厨流水线,它以循环方式驱动Agent执行:让大模型生成下一步决策,调用工具获取结果,观察环境反馈,然后再次进入推理。这个循环会一直持续,直到任务完成或Harness下令停止。
划分两者的边界,核心在“职责”而不在“代码量”。我们可以用系统总复杂度的分配来理解:
经理管方向、后厨管执行,这句话翻译成架构语言就是:Harness定义“做什么”,Loop负责“怎么做”。当你在代码评审中看到Harness里出现了“这一轮调用哪个工具”的硬编码,或者Loop里出现了“任务是否应该继续”的判断,就该意识到——越界了。
- 总复杂度$C_{total}$ 表示整个Agent系统的总体设计复杂度,包括逻辑分支、状态流转和异常处理。
- Harness复杂度$C_{harness}$ 是编排层所承载的复杂度——流程决策、全局调度、生命周期管理。
- Loop复杂度$C_{loop}$ 是执行层所承载的复杂度——单次迭代的推理、工具调用、结果观察。
- 零重叠约束若Harness掺入Loop细节,公式会多出一个交叉项 $C_{overlap}$,意味着系统开始腐化。
控制流编排
餐厅经理每天要做很多决策:今天是否开启午市套餐?客流量大时是让顾客排队还是开放预订?后厨的出菜顺序先后如何?这些决策有一个共同特征——它们发生在“动作之前”,属于流程级别的控制。而后厨并不会思考“今天该推什么套餐”,它只关心手上这道菜怎么做。
控制流的归属,是Harness与Loop分工的第一条分界线。Harness负责所有流程级决策:任务启动与终止条件、分支路由(走工具调用还是走人工确认)、并发策略(多个Loop并行还是串行)、重试与退出逻辑。Loop负责迭代级决策:当前这一步调用哪个工具、大模型生成的参数是什么、观察到的结果如何暂存。
为了看清这种分工的数学本质,我们可以把一次任务执行建模为状态转移与动作选择的分离:
$f$ 写在Loop里,$g$ 写在Harness里,这就是控制流的归属原则。一个良好的系统,其流程决策应该是可预测、可审计的——就像一家有标准作业手册的餐厅,任何一位厨师长上任都能保持出品稳定,因为“什么菜先上、什么情况插单”由经理的制度决定,而不是由某个厨师的临场发挥决定。
- 状态变量$s_t$ 表示第 $t$ 次迭代时系统的运行状态,例如当前答案草稿、已调用过的工具列表。
- 动作变量$a_t$ 表示Harness在第 $t$ 次迭代时做出的流程决策,例如“调用搜索工具”或“结束循环”。
- 状态转移函数$f$ 是Loop内部的执行逻辑——根据当前状态和动作,执行一步并产生新状态。
- 决策函数$g$ 是Harness的决策逻辑,输入当前状态与全局历史 $\mathcal{H}$,输出动作。
状态管理
经理的办公桌上有一本全局账本,上面记录着餐厅的库存、销售额、人员考勤;而每位厨师面前只有一张小小的订单小票,上面写着这道菜的要求。经理和后厨都在“记录”,但记录的范围和生命周期截然不同。账本要保存三个月以便复盘,小票在出餐后就被扔掉。
Harness持有的状态包括:用户会话、多轮对话历史、任务级配置、可用工具列表、用户偏好。这些状态的共同点是——跨越多次Loop迭代而存在,需要全局可见。Loop持有的状态包括:当前迭代的大模型输出、工具调用参数、上一步的观察结果、本轮的错误信息。这些状态在迭代结束时就完成了使命,不需要全局保留。
状态管理分工有一条黄金法则:只有需要跨迭代共享的数据才上提给Harness,其余一律留在Loop内部。用集合的视角来看:
这和餐厅的账本逻辑完全一致:经理不需要知道某一盘菜里葱花是怎么切的,但必须知道整个厨房的库存水平。当你在代码里看到Loop把某一轮的模型输出写进了全局状态,或者在Harness里查不到任何关于任务进度的记录,那一定是状态管理出了偏差。
- 全局状态集合$S_{global}$ 表示任务运行期间系统涉及的全部状态总和,既包括跨迭代的,也包括迭代内的。
- 局部状态并集$\bigcup_{i=1}^{n} S_{loop_i}$ 表示所有Loop实例(编号 $i$ 从1到 $n$)各自持有的局部状态的并集。
- 差集运算$S_{harness}$ 是两者的差集——从全局状态中剔除所有局部状态后,余下的跨迭代数据才属于Harness。
错误处理
某天,后厨火候没掌握好,把一份招牌菜做糊了。厨师不会沉默地自己重新做——万一食材不够呢?更不会直接给顾客换菜。他会立刻报告经理,由经理根据情况决策:食材充足就重做;食材不够就换一道同等价位的菜;如果顾客已经在投诉了,可能直接免单并送一份甜点。错误处理从来不是执行者一个人的独角戏。
AI Agent场景中,错误处理是Harness与Loop协作最密集的环节。Loop负责“检测与上报”:发现工具调用失败、模型输出格式异常、超时等情况,收集剩余上下文并向上抛出。Harness负责“决策与恢复”:评估错误的严重程度,选择重试、换工具、降级方案或终止任务。
重试策略是错误处理的核心。假设单次执行成功的概率为 $p$,系统最多重试 $k$ 次,那么最终成功的概率为:
Harness需要决定 $k$ 的取值:$k$ 太小,错误恢复的概率不够;$k$ 太大,可能浪费算力甚至放大损害(比如重复扣款)。关键原则是:Loop把错误原样上报,重试和降级的决策留在Harness。那些“在Loop里悄悄重试三次”的做法,就像厨师自己把做糊的菜重新炒一遍——经理根本不知道发生了什么。
- 单次成功率$p$ 表示单次执行成功的概率,例如调用某个外部API的成功率是95%。
- 连续失败率$(1-p)^k$ 表示连续 $k$ 次都失败的概率,这是“重试了也没用”的坏情况。
- 至少一次成功$1 - (1-p)^k$ 是重试 $k$ 次后至少成功一次的概率,也是Harness评估策略收益的依据。
接口契约
菜单是餐厅里最神圣的文件。后厨按菜单做菜,服务员按菜单下单,顾客按菜单点餐。菜单上写着“宫保鸡丁”,后厨不会做成“番茄炒蛋”,服务员也不会拿它去哄客人。菜单就是一份契约,它让各方不需要了解彼此的内部细节就能顺畅协作。
Harness与Loop之间同样需要一份“菜单”——接口契约。契约定义了三个核心维度。第一是协议定义:Loop向Harness注册自己的输入输出Schema,Harness通过Schema向Loop下发任务。第二是数据边界:哪些数据由Harness传入Loop,哪些由Loop返回Harness,哪些数据Loop严禁修改(如只读配置)。第三是事件通知:Loop执行中需要向Harness发送事件,如“步骤完成”“遇到错误”“需要人工确认”,这些事件的类型、载荷和时序要提前约定。
契约的稳定程度直接决定系统的可维护性,可以用兼容率来衡量:
维护好接口契约后,Harness更换大模型供应商、Loop增加新工具,双方都能像餐厅换食材供应商一样无感。一个反例是:Harness直接读取Loop内部的数据结构字段,或者Loop在没通知Harness的情况下改动了返回格式——这就像服务员绕过菜单直接向后厨喊“今天做点你拿手的吧”,短期方便,长期灾难。
- 兼容变更数$N_{compatible}$ 表示在接口演进过程中不破坏现有调用方的变更数量,例如新增一个可选字段。
- 总变更数$N_{total}$ 表示一段时间内接口发生的全部变更数量,包括破坏性变更和非破坏性变更。
- 兼容率$C_{ratio}$ 越接近1,说明契约越稳定;越接近0,说明每次迭代都在“拆桥”。
可测试性
餐厅出新品时,后厨会在操作台上单独试做几份,让内部试吃专员评价——这叫单元测试。在正式上线新菜单前,餐厅会组织一次全员模拟演练:服务员、厨师、经理甚至收银员全部到场,按真实营业流程走一遍——这叫集成测试。这样就不用等到真实营业时才发现“这道菜要15分钟,可菜单承诺10分钟上菜”。
Harness与Loop的测试策略同样可以分离。测试Harness时,将Loop替换为Mock对象——就像试菜时让一位经验固定的替班厨师上场,保证后厨行为可控。测试Loop时,不依赖Harness的具体实现,只给它标准的测试输入,就像给厨师一份拿手的菜单让他发挥。除了这两类测试,可观测性(日志、追踪、指标)贯穿始终,相当于给餐厅装监控摄像头,出了问题能回放到底哪一步出了岔子。
用成本来评估测试策略是否健康:
一个架构是否健康,可以看 $T_{mock}$ 的占比:如果Mock成本高得离谱,往往说明契约耦合太深,测试时不得不伪造大量状态。反之,当你能轻松地用Mock替换掉Loop来测试Harness的决策逻辑,就像用替班厨师试菜一样,说明两者的边界非常干净。
- 单元测试成本$T_{unit}$ 是分别测试Loop和Harness的成本,通常最低,因为依赖都被隔离了。
- 集成测试成本$T_{integration}$ 是两者联测的成本,需要准备环境、依赖和真实工具。
- Mock成本$T_{mock}$ 是编写和维护模拟对象的成本。契约越清晰,Mock越简单,成本越低。
性能维护
最糟糕的餐厅是什么样?经理心血来潮冲进后厨亲自颠勺,指令和下厨指令混杂在一起;厨师看见前厅排队太长,擅自改了菜单、换了烹饪流程。结果就是菜做乱了、账记混了、顾客投诉了。职责重叠是复杂系统腐化的第一步。
当Harness开始直接操作Loop的迭代细节(比如硬编码某一次工具调用的参数),或者Loop开始自己决定要不要重试、要不要退出循环,架构的边界就开始模糊。这种职责重叠会带来三类问题:性能瓶颈——全局状态被高频读写,每次迭代都做无谓的序列化和同步;调试困难——一个bug无法定位属于哪一层,既像Harness决策错又像Loop执行错;演进风险——改一处牵一发动全身,任何重构都要同步修改两层。
用控制流复杂度 $M$ 来量化系统的混乱程度:
为了让系统长期可维护,建议用架构评审来守住边界。每一次重构都问一句:这个决策属于Harness还是Loop?这个状态该放全局还是局部?这道菜该由经理定夺还是后厨自理?当团队里每个人都能不假思索地回答这些问题,你的Agent系统就拥有了像那家理想餐厅一样的分工美学——经理安心管大局,后厨专注炒好每一道菜。
- 边数$E$ 表示控制流图中边的数量,代表状态转移和函数调用关系;职责重叠会疯狂增加调用边。
- 节点数$N$ 表示控制流图中节点的数量,代表基本代码块。
- 连通分量数$P$ 表示图中连通分量的数量,规范的程序通常 $P=1$。
- 复杂度含义$M$ 越高,测试覆盖难度越大,维护成本越高。业界经验认为 $M \leq 10$ 是可接受区间。
AI 应用场景
多Agent保险理赔系统
阶段:任务编排与多Agent协作
解决的问题:传统理赔系统用一堆if-else串联规则,流程改动需要重写全部代码。用Harness + Loop分层后,Harness负责理赔单的全局状态和流程节点(录入→核验→定损→打款),各子Agent作为Loop循环执行自己环节的推理与工具调用。遇到单据不清晰时,Loop上报哈Harness,Harness统一决定是请求用户补充还是转人工,避免子Agent各自为政。
具体案例:某保险科技公司的理赔Agent,Harness维护理赔单状态机,OCR子Agent循环执行“识别单据→提取字段→校验置信度”,低于阈值时报Harness,Harness自动发起补充材料请求。上线后理赔处理时效提升60%,且新增险种时只需在Harness中加一条流程分支。
智能客服对话机器人
阶段:对话管理与工具调用编排
解决的问题:客服机器人最怕对话轮次一多就混乱,要么重复提问,要么检索失败后强行编造答案。用Harness管理对话轮次、意图路由和全局用户画像,Loop执行“检索知识库→生成回复→更新上下文”的循环。当检索结果为空时,Loop上报Harness,Harness决定改用大模型直接生成还是转人工坐席。
具体案例:某电商平台的售前客服机器人,Harness判断当前意图是“查物流”还是“问退换货”,将对应任务下发给专门的Loop执行。Loop每轮检索结果都会写回Harness维护的会话历史,保证多轮追问不丢失上下文,用户满意度提升20%。
自动化测试执行Agent
阶段:测试编排与失败恢复
解决的问题:传统自动化测试脚本一旦某一步失败就全部中断,排查问题时日志混乱。用Harness管理测试用例的执行顺序、超时阈值和重试策略,Loop循环执行“打开页面→模拟操作→收集断言结果”。元素定位失败这类局部错误由Loop记录上下文并上报,Harness决定跳过该用例还是整体重跑。
具体案例:某金融公司的UI回归测试Agent,Harness按矩阵调度用例,Loop每轮跑完将页面快照和网络请求回传。当某用例连续3次失败,Harness自动将故障用例隔离并生成缺陷报告,测试维护成本降低40%。
Python 演示
"""演示 Harness 与 Loop 的职责边界:Harness 负责流程决策,Loop 负责单步执行。""" import random class Loop: """执行循环:负责单步执行,不关心全局策略。""" def __init__(self): self.step_count = 0 def run_step(self, task): """执行一次迭代:模拟模型推理+工具调用。失败时向上抛出。""" self.step_count += 1 if random.random() < 0.2: raise RuntimeError("工具调用失败") return {"step": self.step_count, "result": "完成" + task + "的第" + str(self.step_count) + "步"} class Harness: """高层编排:决定循环何时停止,处理 Loop 上报的错误。""" def __init__(self, loop): self.loop = loop self.max_retries = 2 self.max_steps = 5 def execute(self, task): """执行完整任务:控制循环边界,错误重试与降级。""" results = [] while self.loop.step_count < self.max_steps: retry = 0 while retry <= self.max_retries: try: results.append(self.loop.run_step(task)) break except RuntimeError: retry += 1 else: print("重试耗尽,切换降级策略") break return results if __name__ == "__main__": runner = Loop() boss = Harness(runner) output = boss.execute("处理理赔申请") for item in output: print(item)
self.step_count = 0Loop只维护自己的迭代次数,不保存任务全局信息,就像厨师只看订单小票。while self.loop.step_count < self.max_steps:循环何时停止由Harness判断,Loop自身没有退出机制。raise RuntimeError("工具调用失败")Loop把错误原样抛出,不自己消化,对应“检测与上报”。while retry <= self.max_retries:Harness决定重试次数上限,Loop无权修改重试策略。print("重试耗尽,切换降级策略")重试无果后Harness执行降级决策,这是编排层的专属权力。
常见误区
误区为了让Agent“更自主灵活”,我把流程决策、重试逻辑和终止判断全都写进Loop里,这样Harness就只管启动任务,多省事啊。
正解这就像让厨师自己决定换菜单。短期看灵活,长期看流程完全不可预测——同一个任务这次走A路径下次走B路径,出了问题无法复现。流程决策应留在Harness,Loop只负责迭代执行,才能保证任务可控制、可审计。
为何常见:因为大模型时代“自由发挥”被过度神化,误以为把决策权下放给模型越多越好,忽略了工程上可维护性的重要。
误区Harness应该控制一切,包括Loop内部每一步的推理细节、工具参数,这样才能保证最终质量不出偏差。
正解经理不能每一道菜都自己颠勺。Harness管得越细,Loop执行上下文就会被大量无关状态污染,同时Harness本身复杂度激增,沦为“上帝类”。正确做法是只在契约层约束Loop的输入输出,让Loop在契约范围内自主执行。
为何常见:这是从传统软件开发的“控制一切”思维迁移过来的,缺乏分层抽象的信任感,总觉得不控制就会失控。
误区既然要保证数据一致性,干脆把所有状态都放进Harness管理的全局存储里,Loop执行时想读就读、想写就写,多方便。
正解这会造成类似多线程共享可变状态的并发地狱——多个Loop并发修改同一份状态,轻则逻辑错乱,重则死锁崩溃。正确做法是“能不全局就不全局”,局部状态留在Loop内部,只有跨迭代的数据才上提给Harness。
为何常见:追求“统一入口”的直觉很强,但忽略了高频全局读写带来的性能代价和组织性代价。
误区Loop遇到错误自己重试几次就好了,不用每次都打断Harness,这样效率高、响应快。
正解Loop在Harness不知情的情况下重试可能放大损害——比如调用了扣款工具,第一次超时但其实成功了,Loop重试导致重复扣款。Harness需要知道错误上下文才能做出全局最优决策(重试、换工具还是终止),Loop的“好心”反而会酿成大错。
为何常见:把错误处理等同于局部重试,忽略了单次执行失败对整体任务目标可能产生的连锁影响。
误区为了灵活,Harness与Loop之间不定义严格接口,直接传JSON对象,字段随便加,反正都是自己团队写的代码。
正解松散的接口短期灵活,时间一长任何一方悄悄改了字段,都会破坏另一方的隐含假设,而且这种破坏在运行时才暴露。就像菜单模糊不清,后厨和服务员迟早要吵架。契约要显式定义Schema、数据边界和事件类型,变更走评审。
为何常见:小团队或小型系统阶段很难预见契约的价值,等到系统膨胀后再补契约,改造成本已是天价。
面试题
Q1. Harness层在Agent架构中的核心职责是什么?
- A.执行单步大模型推理
- B.编排整体流程并管理全局状态← 正确答案
- C.调用外部工具获取结果
- D.保存每一轮的对话历史
答案:B。Harness对应餐厅经理,负责流程决策与全局状态管理;单步推理、工具调用是Loop或具体工具的事,对话历史存储属于基础设施,不是Harness的核心职责。
Q2. 关于Loop层的描述,正确的是?
- A.Loop负责全任务的生命周期管理
- B.Loop定义终止条件和重试策略
- C.Loop负责循环执行推理与工具调用← 正确答案
- D.Loop持有用户级全局配置
答案:C。Loop是后厨流水线,负责重复执行“推理→工具→观察”的循环。生命周期、终止条件、重试策略都属于Harness的编排职责,Loop不应越权持有用户级配置。
Q3. 控制流编排的分工原则是什么?
- A.流程归Harness迭代归Loop← 正确答案
- B.流程和迭代都归Loop
- C.流程和迭代都归Harness
- D.全部交给大模型自由决策
答案:A。流程级决策(何时开始、走哪个分支、何时终止)由Harness负责,迭代级决策(当前调哪个工具、参数是什么)由Loop负责。全部下放会让流程不可控,全部上提会让Harness膨胀,全交给大模型则失去工程保障。
Q4. 一个理赔任务的状态中,哪些应由Harness持有?
- A.当前OCR返回的文本内容
- B.本次工具调用的超时时间
- C.全任务的处理阶段和会话历史← 正确答案
- D.上一步大模型的原始输出
答案:C。OCR文本、超时时间、单次模型输出都是迭代级局部状态,用完即弃;任务处理阶段与会话历史需要跨迭代存在,归Harness全局管理,对应经理账本上的经营数据。
Q5. 某次工具调用失败,Loop应该怎么做?
- A.无视错误继续下一步
- B.自行无限重试直到成功
- C.收集错误并上报给Harness← 正确答案
- D.直接终止整个任务
答案:C。Loop检测并上报错误上下文,Harness决定重试、降级还是终止。Loop自行无限重试可能放大损害(如重复扣款),直接终止又可能误杀整个任务,无视错误更危险。
Q6. 重试成功率公式中,若单次成功率p=0.8,重试3次后至少成功一次的概率约为?
- A.0.512
- B.0.992← 正确答案
- C.0.800
- D.0.896
答案:B。代入公式 $P=1-(1-0.8)^3=1-0.008=0.992$。0.512是三次全部成功的概率,0.896是简单把三次成功率相加的错觉,0.800忽略了重试的叠加收益。
Q7. 接口契约中“数据边界”的含义是?
- A.限定哪些字段可以传给大模型
- B.规定两层之间数据如何流转← 正确答案
- C.限制用户输入的最大长度
- D.设置API调用的并发数上限
答案:B。数据边界明确哪些数据由Harness传给Loop、哪些由Loop回传、哪些是只读全局配置禁止修改,是菜单式契约的重要组成部分,与模型字段、用户输入长度、并发控制无关。
Q8. 哪些做法能提高系统的可测试性?
- A.用Mock替换Loop以测Harness← 正确答案
- B.将测试环境的数据写死
- C.为Loop提供标准化的测试输入← 正确答案
- D.跳过集成测试直接上线
答案:AC。Mock让Harness测试不依赖Loop真实实现,标准化输入让Loop测试可重复、可断言。将测试数据写死会掩盖真实边界条件,跳过集成测试则失去了验证两层协作的机会,均不可取。
Q9. 以下哪些属于职责重叠的反模式?
- A.Harness硬编码单次工具参数← 正确答案
- B.Loop自行决定退出循环← 正确答案
- C.Harness管理任务的启动终止
- D.Loop统一上报错误上下文
答案:AB。Harness硬编码迭代细节、Loop自己决定退出循环,都跨越了职责边界。启动终止由Harness管理、错误上报由Loop执行,这是正确的分工,不属于反模式。
Q10. 符合状态管理分工原则的做法是?
- A.会话历史由Harness全局维护← 正确答案
- B.单次模型输出仅保留在Loop内部← 正确答案
- C.全局配置允许任意Loop直接修改
- D.跨迭代的任务进度由Harness记录← 正确答案
答案:ABD。跨迭代数据(会话历史、任务进度)归Harness,迭代内数据(单次输出)留Loop。全局配置是只读契约的一部分,绝不允许Loop随手改写——这就像后厨不能擅自修改菜单。