
Agent Harness 这个词最近在 AI Agent 工程实践里出现频率很高但很多人第一反应是它到底是一个新的 Agent 框架还是某种硬件或者只是把多个 Agent 包在一起的容器我自己的理解是Agent Harness 更像是一套“承载 Agent 运行的工程套具”。它管的是模型怎么跑、工具怎么注册、上下文怎么管理、子任务怎么派发、结果怎么回传、失败怎么处理。单 Agent 可以没有 Harness但多 Agent 协作要稳定落地基本绕不开这一层。这篇内容适合两类读者一类是已经在用 Claude Code、Codex 这类带 Harness 的编程 Agent想搞清楚它内部的 Skill、Tool、子 Agent 是怎么组织的另一类是自己在搭多 Agent 系统想少踩坑的人。下面我不按概念讲解顺序写而是按“理解概念 → 设计核心组件 → 跑通最小闭环 → 开发 Skill → 上生产”的顺序展开。1. 先把概念掰清楚Agent Harness 不是另一个 Agent 框架很多人一开始会把 Agent Harness 理解成“多 Agent 管理平台”。这个方向大差不差但不够准确。更准确的说法是Agent 负责“思考、决策、调用”Harness 负责“让思考能发生、让调用能执行、让结果能回填”。1.1 Agent 解决单次任务循环Harness 解决整个运行环境单个 Agent 的核心循环其实不复杂模型根据用户输入生成意图选择工具调用工具拿到结果再决定下一步。这个循环可以写在几十行代码里。但一旦进入真实项目你很快会碰到几个问题系统提示词放在哪里模型升级后怎么维护工具怎么注册、怎么校验参数、怎么统一返回格式长任务跑到一半上下文超限怎么办子 Agent 返回结果不稳定要不要重试一次批量任务跑挂了日志里能不能看到是哪一步出的问题这些都不属于“模型能力”本身而是工程问题。Harness 就是把这些工程问题收拢起来的那一层。英文里 harness 的原意是马具、缰绳引申出来就是“控制并承载”。放在 Agent 场景里它既是运行底座也是约束边界。没有 harness 的 Agent 是实验脚本有 harness 的 Agent 才是可供业务使用的系统。1.2 多 Agent 协作为什么绕不开 Harness真实业务里多 Agent 不是越多越好。哪怕只放两个 Agent你也要回答“谁先跑、谁调用谁、中间结果放哪里、出现分歧怎么办”。这些问题天然落在 Harness 层。现在多 Agent 设计里最容易被接受的一套思路就是主从模式。主 Agent 负责任务分解、节奏控制和结果汇总子 Agent 负责某一类具体工作。很多有经验的开发者会把它看成“将 subagent 作为另一种 tool 进行调用”。这个说法我特别认同。因为从实现角度看子 Agent 和普通工具的差别没有想象中那么大。普通工具是确定性的给定输入按代码逻辑返回输出。子 Agent 是不确定性的给定目标由模型决定中间步骤最后返回输出。但在 Harness 层面主 Agent 只需要知道“有一个可调用对象输入是子任务描述输出是结构化结果”至于它内部用了多少次模型推理主 Agent 不关心。这种抽象让多 Agent 系统变得可控。1.3 架构选型前先看一个对比表模式适合场景最大优点最大问题单 Agent任务链短、边界清晰简单、成本低上下文容易膨胀无法隔离复杂子任务单主多从企业任务拆解、生产流程职责清晰子任务可复用主 Agent 成为瓶颈流水线流程固定、各阶段强顺序稳定性高每步独立验证动态调整能力弱群组对话头脑风暴、方案探索发散性强结果收敛难token 消耗大如果你做的是企业级系统我一般建议从单主多从起步。先把主 Agent 当调度器把每个子 Agent 当工具等流程稳定后再考虑更复杂的流水线或群组模式。2. 把多个 Agent 装进 Harness核心是解决“谁调用谁、怎么回传结果”很多自己做过多 Agent 实验的人都有类似体验明明每个 Agent 单独跑都很正常一旦把它们连起来就开始互相干扰。前面 Agent 丢字段后面 Agent 就乱填子 Agent 跑偏了主 Agent 还傻等上下文越积越长最后整个系统变慢甚至卡死。这些问题不是模型不行而是 Harness 没有把边界和接口定义清楚。2.1 一个能落地的 Harness 至少要包含六层我比较习惯把 Harness 拆成下面这几个组件设计系统时逐一核对编排层负责任务分解、路由决定当前下一步应该由哪个 Agent 或哪个 Skill 执行。工具注册表维护所有可调用工具的 schema、入口、返回格式包括普通 API 工具和 subagent 形式的工具。Skill 仓库存放可复用的技能包每个技能包含提示词、脚本、模板和校验规则。上下文管理层管理会话历史、文件引用、临时目录和上下文压缩策略。执行层负责并发控制、超时、重试、任务队列和输出目录。可观测层记录日志、trace、每次调用的输入输出摘要方便排查。这套分层不一定要全部自研。很多现有平台已经提供了一部分。企业落地时更稳妥的做法是先用一个开源 Harness 或已有平台搭出最小闭环再逐步替换不满意的模块而不是第一天就全部重写。造轮子的成本通常比你预估的高。2.2 主从模式的本质把 subagent 当成另一种 Tool前面提到主从模式这里我具体拆一下。主 Agent 的提示词里不会写“你可以调用一个子 Agent”而是写“你有一个工具叫 draft_reviewer输入是一段代码输出是审查结果”。从模型角度看它只看到工具列表里多了一个候选工具。真正把 subagent 封装成 tool需要在 Harness 里做三件事定义输入输出 schema让主 Agent 能按格式传参。在 tool 的 run 方法里创建子 Agent 的独立会话注入专用 system prompt。限制子 Agent 的规模比如最大轮次、超时时间、是否允许再调用其他工具。这样做的好处很明显子任务的结果被结构化主 Agent 不用自己去读一堆中间推理子 Agent 的上下文是独立的不会被主任务的历史干扰失败的子 Agent 可以单独重试不影响主流程。2.3 设计接口时先考虑失败而不是成功我踩过比较多的坑是定义接口时只写了成功返回没写失败返回。结果真实运行时模型偶尔给一个空字符串主 Agent 拿不到结果又不敢追问整个任务就断在那里。所以设计 subagent 这类工具时返回结构一定要包含 status、data、error、cost 这几个字段。status 表示成功还是失败data 是结构化结果error 是失败摘要cost 是 token 消耗或耗时。主 Agent 拿到失败状态后可以决定重试、换一个 Agent 或者直接结束。这个设计比在提示词里反复求模型“你如果失败了一定要告诉我”可靠得多。3. 实战用最小代码把一个子 Agent 封装成 Tool到了动手环节我不建议一开始就上重型框架。先用最小的代码把“主 Agent 调用子 Agent”这条路跑通再逐步加功能。这里我用伪代码展示核心结构实际接入时换成你用的框架和模型接口就行。3.1 最小闭环主 Agent 循环 子 Agent 工具先定义子 Agent 工具class SubAgentTool: # 工具元数据主 Agent 会依据这些信息决定是否调用 name subagent_draft description 把一段子任务描述发送给子 Agent返回结构化结果 def schema(self): return { type: object, properties: { task: {type: string, description: 子任务目标越具体越好}, input_path: {type: string, description: 输入文件路径可选}, output_format: {type: string, enum: [json, markdown, text]} }, required: [task] } def run(self, task, input_pathNone, output_formatjson): # 真正执行时这里会创建一个子 Agent 会话 # 注入子 Agent 专用 system prompt # 限制最大轮次和超时时间 # 最后返回结构化结果 return { status: ok, data: {result: 这里是子 Agent 的输出, path: /tmp/output/xxx}, error: None, cost: {tokens: 1234, duration_s: 8.2} }然后是主 Agent 的循环逻辑def main_agent_loop(system_prompt, user_request, tools): messages [{role: system, content: system_prompt}] messages.append({role: user, content: user_request}) for step in range(max_steps): response llm.chat(messages, toolstools) tool_calls response.tool_calls # 没有工具调用说明任务结束 if not tool_calls: return response.content # 执行工具并把结果回填到消息里 for call in tool_calls: tool lookup_tool(call.function_name) result tool.run(**call.arguments) messages.append(call.to_message()) messages.append(tool_result_as_message(result)) return {status: timeout, error: 超过最大步骤数}这个循环看起来简单但已经包含了多 Agent 协作最核心的机制模型决策、工具派发、结果回填、条件继续。先跑通这一步再去想并发、队列、断点续跑这些高级能力。3.2 为什么先定义 Tool 接口再写业务逻辑很多人会直接用“在提示词里说你可以调用另一个 Agent”的方式来做多 Agent。这种方式不是说完全不能用但它有几个问题参数没有 schema子 Agent 返回格式看运气失败信息进不了结构化日志。对于一个测试脚本来说这些还能忍对于生产任务来说就是灾难。所以我建议的顺序是先定义 Tool 接口再写 Agent 的业务逻辑。接口定义了输入输出的契约让主 Agent 和子 Agent 都能在这个契约内工作。哪怕接口一开始很粗糙只要字段稳定后续调整提示词、模型、工具实现都不会影响整体结构。3.3 跑通后立刻验证三件事单任务能不能跑通给一个非常简单的人类任务确认主 Agent 能识别并调用 subagent 工具。返回格式是否稳定连续跑五次看返回的 JSON 字段是否齐全有没有乱格式。失败路径是否合理比如故意让子 Agent 处理一个空文件看主 Agent 是死循环还是能给出失败结论。这三件事都满足再进入批量任务。不要一上来就开高并发否则你很难判断问题是出在模型、工具、队列还是资源上。4. Skill 开发实战把经验固化成可复用技能包Skill 是最近非常热的概念尤其在 Claude Code、Codex 这类编程 Agent 的场景里。简单来说Skill 是把“完成某一类任务的方法论”打包成 Agent 能识别、能加载、能执行的技能单元。它和普通提示词的区别在于提示词只是一段文字Skill 是一组带结构的文件可能包含文档、脚本、模板和示例。4.1 Skill 与 Prompt、MCP、Agent 的区别概念本质解决的问题典型形态Prompt一段文本影响模型当前对话的行为system prompt、few-shot 示例Skill可复用技能包把一类任务的步骤、脚本、模板固化下来目录 SKILL.md 脚本 模板MCP工具调用协议标准化外部工具接入方式服务端 客户端 工具接口Agent运行时编排者决定下一步调用谁、怎么判断结束主循环 工具 记忆放一起看更清楚MCP 解决的是“工具怎么被调用”Skill 解决的是“一类任务应该怎么做”Agent 解决的是“当前任务应该派给谁”。Skill 和 MCP 不是竞争关系它们经常叠加使用。一个 Skill 内部可以调用多个 MCP 工具。4.2 设计一个 Skill 的基本目录结构拿“Code Review”这个场景举例。一个最小可用的 Skill 可以长这样skill-code-review/ SKILL.md prompts/ pre_review.md scripts/ analyze.py templates/ review_report.md examples/ sample_input.pySKILL.md 是这个 Skill 的说明文件一般包含name技能名称最好能和其他技能区分开。description什么时候应该使用这个技能什么时候不该用。when_to_use触发场景比如“当用户要求代码审查时”。steps执行步骤按顺序写给模型看。input / output输入格式和期望输出格式。examples一到两个示例帮助模型理解。写 SKILL.md 时最容易犯的错是把描述写得太宽。比如“审查代码质量”就太模糊模型不知道该关注什么。更有效的写法是“检查是否存在 SQL 注入风险、错误处理是否完整、函数职责是否单一并用给定的模板输出 Markdown 报告”。4.3 写 Skill 时先写“不要做什么”我后来总结的经验是Skill 里的限制条件比能力说明更重要。模型拿到一个宽泛的技能描述很容易在边界场景里自由发挥。你在 SKILL.md 里明确写下“不要修改原始文件”“不要输出未经验证的统计数字”“如果输入文件超过 1MB先提醒用户而不是直接解析”模型的行为会稳定很多。这个经验也适用于 subagent 类的 Skill。子 Agent 的执行环境和主 Agent 不同你必须把如何处理失败、如何遵守输出格式、最多执行多少步交代清楚。Skill 本质上是在约束模型而不是在给模型更多发挥空间。4.4 开发完 Skill 之后怎么验收我一般用三类样例来验证最小样例一个非常小的输入确认 Skill 能被正确加载输出格式正确。边界样例空文件、超长文本、缺失字段的输入看模型会不会卡住或乱输出。同类多样样例换几个相似但内容不同的问题确认 Skill 真的有通用性而不是只记住了某一条示例。Skill 也要纳入版本管理。模型升级后同样一个 Skill 可能表现变化很大所以每次模型版本变动都应该重新跑一遍 Skill 回归测试。5. 一个完整案例从需求拆解到代码审查的多 Agent 流水线下面用一个我近期做过的实验来拆解。目标是搭建一条“需求分析 → 代码实现 → 代码审查 → 测试报告”的多 Agent 流水线。场景不算特别复杂但足够说明 Harness 怎么落地。5.1 先定义角色和边界而不是直接写提示词我先把任务拆成四个角色Agent 角色职责输入输出需求解析 Agent将自然语言需求拆成任务清单需求文档结构化 JSON 任务列表编码 Agent按任务清单生成代码任务 JSON 仓库上下文代码 diff 或新文件审查 Agent检查代码质量和安全问题代码 diff审查报告测试 Agent根据需求和代码生成测试用例需求 JSON 代码可执行的测试文件和摘要每个 Agent 都对应一个独立 Skill 或 Tool。主 Agent 负责把它们串起来。最初设计时我也考虑过直接用一段长提示词让一个 Agent 做完所有事结果任务是跑通了但中间只要有一处出问题后面所有输出都受影响而且日志里很难定位到底哪里出问题。5.2 子 Agent 返回格式不稳定是最先暴露的问题第一次实验编码 Agent 返回的代码块里偶尔会混入解释性文本。下游审查 Agent 收到这些噪声后要么误解代码内容要么输出格式混乱。排查后发现问题不在模型而在子 Agent 工具的输出契约不够严格。我做的修复是在编码 Agent 的 system prompt 里明确要求“代码部分放在 code 字段解释部分放在 summary 字段”同时在 Harness 层加了函数级的 schema 校验如果返回 JSON 不符合预期格式直接标记为失败并触发一次重试。这样做的效果非常明显下游 Agent 收到的输入干净了很多。5.3 上下文膨胀是第二个坑跑了几条任务后任务历史越来越长。主 Agent 每转一轮都要把之前的工具结果重新放进上下文。到后面输入 token 消耗变得很高而且主 Agent 可能会被太多历史信息干扰。我的处理办法比较简单主 Agent 的上下文不保存完整子 Agent 推理过程只保存工具返回的摘要和结果路径。如果后续需要深入查看再让主 Agent 调用一个“文件读取工具”去加载详情。这个设计和前面说的“subagent 当作 tool”是同一个思路Harness 负责保存中间产物Agent 只保留必要信息。5.4 稳定之后才加并发当单条流水线能稳定跑通我才开始测并发。测试时的观察重点是三样单任务耗时、失败率、token 峰值占用。我建议先把并发数从 1 调到 2再调到 4每档至少跑一二十条任务。如果 2 并发没事、4 并发开始大量超时就要检查是不是模型接口限流、本地任务队列排队还是子 Agent 之间共享了某个有状态资源。并发任务还要注意输出目录的命名。每条任务必须带唯一任务 ID所有中间产物按任务 ID 存放。否则一旦任务重试很容易覆盖之前的结果排查时完全无法还原现场。6. 生产环境稳定性并发、日志、失败重试和常见报错最后一个部分是真正把 Harness 当生产系统来维护时最重要的内容。很多项目前期 demo 跑得很顺一放大规模就崩崩完还不知道原因。下面这些点都是我实际踩过之后整理出来的。6.1 先单任务再小并发不要一上来就拉满我见过不少人拿到一个不错的多 Agent 框架立刻把并发调到 10、20。结果要么模型接口限流要么输出目录乱掉要么子 Agent 之间互相等待造成死锁。更稳的顺序是单任务跑通记录平均耗时和 token 消耗。并发 2观察资源占用和输出是否互相干扰。并发翻倍观察失败率和耗时变化。达到预期吞吐后再停在这个值不要追求“上限”。多 Agent 系统的瓶颈往往不是模型能力而是队列、上下文长度、超时设置和外部服务可用性。并发数只是表象。6.2 日志和任务 ID 是调试的基础生产环境里最可怕的场景不是任务失败而是任务失败后你找不到是哪条任务、哪一步、用了什么参数。所以我建议从第一天开始就给每条任务生成唯一 ID并在日志里记录任务 ID、输入摘要、启动时间每步调用了哪个 Agent 或 Skill工具的输入参数和返回状态重试次数和最终结果这些记录不一定都要存到专门平台刚开始用结构化日志就够了。但格式必须固定至少让 grep 能够定位。等规模上来再考虑接入更完整的 trace 工具。6.3 常见问题排查顺序如果发现任务输出为空、结果异常或系统卡住我一般按下面顺序排查先看现象是报错、卡住、还是无输出。不要急着改参数。再看输入文件路径是否存在、输入格式是否满足 Skill 或工具 schema、字段有没有缺。再看日志任务 ID 对应到哪一步调用失败失败状态是超时、格式校验不通过还是模型返回空。再看资源显存、内存、磁盘、接口配额是否打满。最后才调参数超时、最大轮次、重试次数、并发数。最容易迷惑人的是“看起来像模型问题”的情况。比如输出为空实际是输入文件编码不对子 Agent 读到了乱码比如任务卡住实际是主 Agent 在等一个外部工具返回而那个工具没有设置超时。这类问题只看代码很难定位必须依赖日志和任务 ID。6.4 什么时候不要用多 Agent把多个 Agent 串起来听起来很强大但不是所有任务都适合。我自己的判断标准是如果单 Agent 加一个工具就能解决不要为了架构好看硬拆。如果任务对输出一致性要求极高比如金融计算、批量数据转换优先用确定性代码模型只负责理解需求。如果资源有限多 Agent 的 token 开销一定比单 Agent 高要提前算账。如果团队没有日志和排查能力先别上复杂编排出了问题会很难收场。多 Agent 的价值在于隔离上下文、复用技能、并行处理但它也有成本。真正生产级的做法是把“需要模型灵活性”的部分交给 Agent把“需要确定性和一致性”的部分交给普通代码。两者结合比纯 Agent 方案稳定得多。写到这里我最后想强调的其实很简单Agent Harness 和 Skill 都不是什么神秘技术它们是把模型能力工程化的手段。先跑通单任务再管理好子 Agent 的输入输出然后逐步加并发最后把日志和失败重试补齐。这个过程不炫技但可靠。