
上个月帮一个朋友 review 他写的 LLM Agent 项目代码里塞了三十多个 if-else 来切换工具调用、角色、上下文拼接。看着像一张摊开的意大利面。问题不是模型不行而是整个执行过程没有一个像样的Agent Harness。你只有 Agent没有让 Agent 运转起来的“骨架”。这篇文章我从一个后厨类比的视角把手里的 Agent Harness 拆成 12 个组件顺便解释每个组件为什么存在、怎么用、哪些可以一开始就砍掉。适合正在设计多步骤 AI Agent、或者想把一堆 prompt 组织成工程化系统的开发者看。1. 先把概念拎清Agent 是演员Harness 是舞台很多人在网上搜“harness 和 agent 区别”搜完还是懵。我直接用一句大白话总结Agent 是那个负责“思考”的演员Harness 是整个舞台的机械系统。演员台词再好没有灯光、提词器、后台调度、安全网也没法完成一出完整的话剧。Agent Harness 就是承载 Agent 运行的一套外围框架负责接收任务、编排流程、调用工具、管理记忆、记录日志、控制权限。从实现上讲Agent 是一个循环把用户请求拼进 prompt调大模型拿结果判断是否需要调用工具再继续调模型。而 Agent Harness 是这个循环之外的“操作环境”。对比项AgentAgent Harness核心职责推理、决策、生成回复编排、状态、工具、通信、治理控制粒度单个循环内的输入输出跨步骤、跨多 Agent 的流程典型问题怎么让模型“想”得更准怎么让系统“跑”得更稳实现产物prompt 模型 API 调用调度器、记忆库、事件总线、观测组件单独使用效果能完成简单单步任务能承载复杂、可恢复、可观测的 Agent 应用所以当你说“我做了个 Agent”你大概率只是写了 prompt 和模型调用当你说“我搞了一个 Agent Harness”你是在定义这个 Agent 如何被创建、执行、暂停、恢复、追踪和治理。后者才是工程前者是实验。一个反直觉的事实是调试复杂 Agent 时瓶颈往往不在大模型的“智商”而在 Harness 的“组织能力”。任务解析含糊、工具调用结果没有归档、断点续跑做不到、日志里看不见中间步骤——这些问题用再强的模型也补不回来。2. 用一个“晚宴后厨”的类比从 12 个问题倒推出 12 个组件如果说一个复杂的 Agent 系统是一场晚宴那么 Agent Harness 就是后厨。顾客不会看到厨师怎么备料、怎么协调、怎么应急但后厨的每个环节直接决定菜能不能稳定端上去。我不主张凭空列组件清单而是先列出“运行一个复杂的 Agent 时必须回答的 12 个问题”再把问题映射到后厨角色最后抽象成组件。用户到底让我干什么先干什么、后干什么谁来实际动手干用什么口径、格式、限制去干不会的事情怎么调用外部能力干到一半上一步的细节忘了怎么办下次还想用上次沉淀的信息怎么办多角色/多步骤之间怎么传话进行到哪一步了中断后能不能接着干结果靠不靠谱要不要校验出了问题怎么定位是哪一步错了是不是什么操作都允许 Agent 执行对应到后厨就是菜单、总厨、灶台、菜谱、备料台、手边冰箱、档案室、对讲机、台账本、试菜员、监控屏、保险柜。再抽象成工程组件正好是下面这张映射表。后厨类比要解决的工程问题Agent Harness 组件菜单/下单台用户意图转结构化任务Task Parser 任务解析器总厨决定做菜顺序和派工Orchestrator 编排调度器灶台真正执行的厨师Agent Runtime/Executor菜谱控制行为范式的描述Prompt Manager 提示词管理器备料台外部技能和函数调用Tool Invoker 工具调用器手边冰箱当前对话上下文Context Manager 上下文管理器档案室跨会话的长期记忆Long-term Memory 长期记忆库对讲机组件间通信Event Bus 事件总线台账本状态和断点保存State Store 状态存储试菜员输出校验与兜底Guardrails 护栏组件监控屏日志、追踪、指标Observability 可观测组件保险柜权限、密钥、操作边界Security/Auth 安全组件这 12 个问题不是拍脑袋它们覆盖了 Agent 运行链路的四个平面决策、执行、支撑、治理。每层 3 个组件形成了紧凑的 3×4 结构。后厨类比的妙处在于它天然把“流程协作”“资源管理”“异常兜底”这三个要素放在了一起这正好是 Agent Harness 的核心命题。3. 12 个组件逐个拆解职责、接口和关键点组件清单列出来了但每个组件到底做什么、接口长什么样、什么时候必须做这些决定你能不能把理论落地。我按四个平面展开讲。3.1 决策链路任务解析、编排调度、提示词管理这一层负责告诉系统“用户要什么、按什么顺序做、用什么话术做”是整个 Harness 的头部。Task Parser 任务解析器对应后厨的菜单。它不是简单的意图分类器而是要把“帮我查一下附近有没有卖情人节玫瑰的店顺便规划一条路线”解析成结构化的任务intentpurchase_search, targets[flower_shop, route], constraints[valentine, nearby]。常见实现有两种一种是直接让大模型输出 JSON一种是结合规则做槽位抽取。我建议先让模型自由解析再用 schema 校验把不合格的重试一次。接口可以直接抽象成def parse(user_input: str) - Task。Orchestrator 编排调度器对应总厨。它决定子任务按什么顺序执行能不能并行失败怎么重试。这里要区分两种风格中心化编排和 ReAct 式自循环。中心化编排适合步骤明确的任务比如“先搜索再总结再发邮件”ReAct 自循环适合动态探索比如“持续检索直到找到答案”。一个成熟的 Harness 通常两者都要支持用有限状态机来描述步骤流转而不是靠模型自由发挥。接口大概是def run(task: Task, context: Context) - Result。Prompt Manager 提示词管理器对应菜谱。没有它你会把 prompt 拼在代码里换一个模型就崩。这个组件负责统一管理系统提示词、few-shot 示例、工具描述模板并支持按组件或按任务类型渲染。关键点是变量隔离只有通过render(task, context)才能注入动态内容而不是到处用 f-string 直接拼接。否则 prompt 会变得不可测试、不可审计出问题的时候根本分不清是模型问题还是模板问题。这一层最容易犯的错是让 Orchestrator 直接改 prompt。编排器只应该决定流程走向不该关心措辞。prompt 是 Prompt Manager 的地盘。3.2 执行链路Agent 运行器、工具调用器、上下文管理器任务定好了接下来要有人动手。这一层是离大模型最近的地方。Agent Runtime/Executor对应灶台上的厨师也是很多人理解的“Agent”。它维护最内层的推理循环把当前消息发给模型、解析模型输出、检测是否有工具调用、如果有就触发工具、把工具结果放回对话、再继续。要注意它和 Orchestrator 的区别Orchestrator 管的是“多个步骤/多个 Agent 之间的宏观编排”Agent Runtime 管的是“单个 Agent 内部的模型-工具循环”。接口核心是def step(messages: list) - AgentUpdate每次只走一步把控制权交还给 Harness。这样你才能在外面做中断、暂停、人工审批。Tool Invoker 工具调用器对应备料台。真实项目里工具签名五花八门有些是 REST API有些是 Python 函数有些是数据库查询。Tool Invoker 要解决三件事工具发现有哪些工具可用、参数转换把模型给的 JSON 转成实际函数参数、结果归一化把返回结果统一包装成模型能理解的消息。最容易被忽略的是错误透出。工具抛异常时不能直接把堆栈丢给模型而是要把“发生了什么、有什么替代方案”变成结构化的工具反馈。比如搜索超时应该返回{status: timeout, suggestion: try_again}。Context Manager 上下文管理器对应手边冰箱。所有需要进入模型上下文的信息都在这里汇聚同时它也是 token 预算的守护者。需要考虑滚动窗口、自动摘要、丢弃不重要信息。很多人问“上下文管理器跟记忆组件有什么区别”区别很简单上下文管理器管的是“这一次推理要带什么”长期记忆管的是“以后还能不能找回”。接口可以设计成def build_system_messages(task, state) - list和def compact(messages) - messages。执行链路是出 bug 最多的地方。我见过最常见的 bug 是工具调用结果过长导致上下文爆炸模型开始重复已经说过的内容。解决方案不是调大 token 上限而是让 Context Manager 学习摘要压缩。3.3 支撑链路事件总线、状态存储、长期记忆这三兄弟不直接参与推理但少了它们Harness 只能跑单机单线程连中途续跑都做不到。Event Bus 事件总线对应后厨的对讲机。组件间需要解耦通信比如工具执行完发一个ToolFinished事件编排器监听后决定下一步观测组件同时监听来做日志记录。实现上可以用一个简单的发布订阅类不需要一上来就上消息队列。事件应当是不可变的包含时间戳、事件类型、关联的run_id。比如dataclass class Event: type: str # tool.finished, agent.step, task.pending run_id: str payload: dict timestamp: floatState Store 状态存储对应台账本。Agent 每执行一步都要把状态持久化当前在哪个节点、已经收集了哪些信息、哪些工具调用成功过、有没有待人工确认的东西。为什么需要单独的组件因为内存中的对象一旦进程重启就全没了。生产级 Harness 至少要把 state 写入 Redis 或 Postgres。接口就两个save(run_id, state)和load(run_id)。有了它你才能实现“任务中断后恢复”而不是从头再来。Long-term Memory 长期记忆库对应档案室。这里存的是结构化知识、历史会话摘要、用户偏好。常见的实现是向量库存语义检索的 chunk加关系表存事实。不要把它和 Context Manager 搞混长期记忆不是每轮都全文塞给模型而是先通过检索把相关片段取出来交给 Context Manager 做组装。长期记忆的录入也必须有策略不能把乱七八糟的内容都写入要有评分、去重和过期机制。这一层设计好了Agent 才是真正的“可规模化系统”。否则换个节点就跑不了改个参数就丢上下文。3.4 治理链路护栏、可观测性、安全权限执行完后厨还要有人把关。没有治理层的 Harness 只能跑 Demo不能跑业务。Guardrails 护栏组件对应试菜员。它在 Agent 的输入侧和输出侧做双向检查。输入侧防止用户注入恶意 prompt输出侧校验结果格式、敏感信息、是否符合规范。注意护栏不是简单字符串过滤它还可以做结构化验证比如要求 Agent 输出 JSON护栏先尝试解析 JSON解析失败就让模型修正一次。也能检查工具调用参数是否在白名单内防止 Agent 去调用删除类操作。这个组件放在 Agent Runtime 的外围作为每个 step 之后的拦截点。Observability 可观测组件对应监控屏。复杂 Agent 的调试难度是几何级上升的没有 trace 的话你根本不知道是模型回答错了还是工具返回错了还是编排顺序错了。这个组件至少要记录每次模型请求的 prompt 摘要、返回 token 数、耗时、工具调用的输入输出、各步骤的事件流。如果团队有条件把事件总线里的所有事件都接到这个组件就能还原一次完整运行的“时间线”。这一步做到位线上问题处理效率能提升一个量级。Security/Auth 安全组件对应保险柜。包括密钥管理、用户身份校验、数据隔离、操作审计。很多人觉得“我就是个内部工具不用管”但一旦 Agent 可以调工具它就有了实际影响力。必须做到所有外部工具调用的凭证不过明文工具执行前校验权限关键操作发邮件、删文件、改数据库进入审计日志。安全组件要直接嵌入 Tool Invoker 和 API 入口不能是事后补丁。治理链路不是可选项。我见过太多项目立项时觉得麻烦结果一上生产就出事要么工具被 prompt 注入控制要么出了问题完全无从查起。4. 从后厨回到代码一个 12 组件 Agent Harness 的最小骨架光有概念不成我给出一个可以直接参考的伪代码骨架。这不是某个框架的完整实现而是告诉你 12 个组件如何咬合在一起。先定义一个最小配置harness: components: task_parser: enabled: true max_retries: 1 orchestrator: strategy: fsm max_steps: 10 agent_runtime: model: chat-llm temperature: 0.2 tool_invoker: timeout_seconds: 15 retry: 1 context_manager: max_tokens: 4000 strategy: sliding_window long_term_memory: store: vector_store event_bus: enabled: true state_store: backend: redis guardrails: verification: json_schema observability: traces: true security: requires_auth: true然后是运行时的主类核心思路是让每个组件只做一件事由 Harness 主循环把它们串起来class AgentHarness: def __init__(self, components): self.parser components[task_parser] self.orchestrator components[orchestrator] self.executor components[agent_runtime] self.tools components[tool_invoker] self.context components[context_manager] self.state components[state_store] self.events components[event_bus] self.guardrails components[guardrails] def run(self, user_input, run_idNone): run_id run_id or create_run_id() task self.parser.parse(user_input) plan self.orchestrator.plan(task) self.state.save(run_id, {plan: plan, step: 0, context: {}}) self.events.publish(Event(harness.start, run_id, {task: task})) while not plan.is_finished(): step plan.next_step() messages self.context.build(step, self.state.load_context(run_id)) result self.executor.execute(step, messages, self.tools) validated self.guardrails.validate(result) if not validated.ok: result self.executor.repair(result, validated.feedback) self.state.update(run_id, step, result) self.events.publish(Event(harness.step, run_id, {step: step, result: result})) plan.advance() self.events.publish(Event(harness.end, run_id, {})) return self.state.load(run_id).final_answer一次请求的完整流转是这样的用户输入进入 HarnessTask Parser 先翻译成结构化任务Orchestrator 生成步骤计划State Store 马上落账。然后进入循环每一步先从长期记忆检索相关信息交到 Context Manager 组装消息Agent Runtime 调用模型模型决定要用哪个工具Tool Invoker 执行并把结果写回上下文Guardrails 检查结果状态更新事件广播直到计划完成。这个骨架不是炫技它体现了 Harness 的精髓每个组件都位于固定的边界内流程可暂停、可恢复、可追踪。在你自己的实现里可以按需替换组件实现比如把 Redis 换成内存事件总线换成最简单的函数回调只要保持接口稳定就行。5. 我实际落地时的三个教训组件不是越多越好上面的 12 个组件是“完全体”但真实项目里你不需要一上来就全部铺开。我踩过几个坑说出来供你参考。第一个教训别为了用组件而拆组件。最初给一个内部运维工具做 Harness我强行拆了 12 个组件结果一大半时间花在定义接口和搬数据上。后来冷静下来只保留 Task Parser、Orchestrator、Tool Invoker、State Store其他都砍掉开发速度快了三倍。如果任务只有三五个步骤执行链路单一那先砍掉长期记忆和事件总线用函数的返回值传递结果就够了。加组件应该基于真实的失败模式而不是基于架构理想。先让 Agent 跑起来再根据痛点补组件。第二个教训State Store 和 Observability 最容易偷懒但最不能偷懒。我第一次做完一个多步骤 Agent 后没有做持久化结果线上进程一重启所有正在执行的任务全丢了。后来补了 Redis 状态存储才发现恢复任务比自己想的重要。Observability 也是以前以为打印点日志就行直到一次线上事故无法还原发生了什么。现在我会在 Event Bus 发出所有事件时同步给观测组件至少把run_id、步骤、关键 Prompt、工具返回记录完整。第三个教训事件总线不要往消息队列的方向做。你要是把每个事件都丢进 Kafka 再做异步消费调试复杂度会急剧上升因为事件顺序和时序会变得不可控。Harness 里的事件总线更多是进程内发布订阅作用是解耦和审计。跨进程通信交给 State Store 和调度系统不要让事件总线承担“模式匹配再触发逻辑”的重任。否则你会陷入循环事件、消息延迟、难以复现的 bug 泥潭。还有个很实用的判断标准当一个组件需要超过两周时间去实现时先质疑它是不是伪需求。很多需求其实靠 prompt 调优就能解决根本不需要拆出一个新组件。真正值得投入的往往是那些能让任务“可恢复”“可观测”的基础设施而不是花哨的编排 DSL。做个原型时我建议的最小集合是Task Parser Agent Runtime Tool Invoker Context Manager。这四个组件能覆盖大多数 POC等 POC 通过再依次补上 State Store、Observability、Guardrails最后按业务需要补长期记忆和事件总线。这样既不浪费前期开发时间也能在关键节点接住复杂性。Agent Harness 的 12 个组件不是一套需要全部照抄的模板而是一张检查清单。把这张清单放在手边每次设计 Agent 时过一遍问自己我的 Runner 遇到异常能不能恢复我的调用路径能不能被追踪我的 Agent 会不会执行超出边界的工具这些问题想清楚你的 Agent 工程化水平会和只调 prompt 的版本拉开明显距离。