ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Java 17 + Spring Boot 3 构建多Agent Harness:主从模式与Skill开发实践

2026/8/30 9:05:01 拓冰建站 浏览量
Java 17 + Spring Boot 3 构建多Agent Harness:主从模式与Skill开发实践 在 2026 年的企业级 AI 项目中多 Agent 协同已经从“每个 Agent 独立处理一个任务”的玩具阶段进入“多个 Agent 共享上下文、按流程协作、互相调用工具”的工程化阶段。这个阶段的难点不再是单个 Agent 的模型选择而是如何设计一套可控、可观测、可回滚的 Agent 运行框架。行业内把这套系统工程实践称为 Harness Engineering而承载多 Agent 运行周期的工程底座就是 Agent Harness。很多团队在落地时真正卡住的并不是大模型能力不足而是没有理解 Agent、Harness、Skill、Tool 四者之间的关系导致项目结构混乱、子 Agent 调度失控、上下文被反复截断。这篇文章会从 Harness Engineering 的底层原理讲起先分清 Agent 和 Harness 的边界再解释主从模式下为什么“子 Agent 本质上是另一种 Tool”然后搭建一个基于 Java 17 Spring Boot 3 的最小多 Agent Harness 项目完成一个主从模式的多 Agent 代码评审工作流最后落到一个非常实际的问题如何开发符合 Spring Boot 开发规范的 Claude Skill并在 Cursor 等 AI IDE 中校验和安装。整篇文章覆盖原理、代码、运行验证、排错、最佳实践适合正在做多 Agent 工程化、准备把 Agent Harness 引入企业项目的架构师和资深后端工程师。1. 先弄清楚 Agent、Harness、Skill、Tool 在工程里分别承担什么角色1.1 Agent 是决策单元Harness 是运行载体从工程角度说Agent 是一个具备目标、记忆和行为能力的自主决策单元。它接收任务描述结合上下文和工具执行结果决定下一步调用哪个工具或输出最终答案。单独看一个 Agent它本质上是“大模型 记忆 工具调用能力”的组合。而 Agent Harness 是承载 Agent 运行周期的工程框架。它把模型调用、工具注册、上下文管理、任务规划、结果校验、重试与降级、日志追踪这些环节封装成可复用的运行时。Harness 本身不产生智能它提供的是让智能可以稳定输出的“运行环境”。用一句话区分Agent 是大脑Harness 是身体。在实际项目中Harness 解决的问题非常具体控制循环Agent 每次思考后如何再次调用模型而不是一次生成完就结束。工具注册与校验Agent 能调用哪些 Tool参数格式如何校验。上下文管理多轮对话与子 Agent 结果如何写入上下文避免无限膨胀。安全边界Agent 的代码执行、文件操作、网络请求需要被限制。可观测性每一步决策、工具调用、错误信息都要能被追踪。如果团队只是用裸 API 写一个while循环调用模型那不能叫 Agent Harness只能叫“一个会调接口的脚本”。Harness 的价值在于把这些散落在业务代码里的逻辑统一收口。1.2 Harness 和 Agent 的区别为什么不能只写 Agent 循环很多开发者会把“Agent”和“Harness”混为一谈导致项目的核心逻辑散落在 Agent 内部。这里需要做一个明确区分Agent 只负责“决策”它不应该关心工具是如何注册的、上下文是如何存储的、异常是如何重试的。这些工程问题应该由 Harness 统一处理。对比关系如下表维度AgentAgent Harness核心职责根据目标和上下文做决策提供决策所需的运行环境主要产物决策结果、行动指令控制循环、工具注册表、上下文管理器关心的问题下一步该调用什么工具工具调用是否合规、上下文是否超限、异常如何处理是否依赖具体模型是Agent 需要绑定模型否Harness 通过模型抽象层接入不同模型故障影响单次决策错误整个 Agent 运行周期崩溃所以企业级项目开始时就要明确Agent Harness 是独立于具体 Agent 逻辑的公共层。后续每新增一个 Agent都只是在 Harness 里注册自己的决策配置而不是重写一套循环和上下文管理逻辑。1.3 主从模式下subagent 为什么可以当作另一种 Tool最新的多 Agent 设计里主从模式是非常常见的架构即一个主 Agent 负责任务分解、调度和结果汇总多个子 Agent 分别执行具体子任务。但工程实现上有一个关键设计主 Agent 并不直接“调用”子 Agent 的代码而是通过 Tool 接口去“触发”子 Agent。也就是说子 Agent 被封装成 Tool注册到 Harness 的 ToolRegistry 中。主 Agent 在决策时看到的是一个名为subagent_code_review的 Tool它只关心输入是什么、输出是什么不需要关心子 Agent 内部用的是哪个模型、怎么编排提示词。这样做有三个明显好处统一决策空间主 Agent 的全部行为能力都收敛为 Tool 列表决策模型更简单。隔离子 Agent 复杂度子 Agent 的内部改动不影响主 Agent。便于观察和重试子 Agent 的执行可以被当作一个普通 Tool 调用记录日志失败时重试或降级。这种设计的本质是“把 Agent 当作执行单元把 Tool 当作交互协议”。理解了这一点多 Agent 协同的工程实现就会清晰很多。2. Agent Harness 的底层原理与核心组件2.1 控制循环感知、决策、行动、观察Agent Harness 最核心的机制是控制循环。一个标准的循环由四个阶段组成感知从上下文中收集当前任务状态、用户输入、历史记录。决策调用模型让模型决定下一步行动。行动执行模型选择的 Tool或者返回最终结果。观察读取 Tool 执行结果把它追加到上下文中然后进入下一轮。这个过程一直重复直到模型输出“任务完成”信号或者达到最大迭代次数。下面是控制循环的伪代码function runAgent(harnessContext, task): while not harnessContext.isFinished(): perception harnessContext.getPerception() decision llm.decide(perception, harnessContext.getHistory()) if decision.action call_tool: toolResult toolRegistry.execute(decision.toolName, decision.arguments) harnessContext.addObservation(decision.toolName, toolResult) elif decision.action finish: return decision.finalAnswer else: raise InvalidDecisionException(decision) return harnessContext.getPartialResult()这个循环看起来简单但工程化时会产生大量细节。比如模型输出格式不稳定、Tool 参数校验失败、上下文达到 token 上限、子 Agent 执行超时等都需要 Harness 在循环中统一处理。2.2 核心组件拆解一个可落地的 Agent Harness 通常包含以下组件组件职责典型实现ContextManager维护任务上下文控制 token 预算窗口截断、摘要压缩、关键信息持久化ToolRegistry注册所有可调用工具提供参数校验基于注解的方法注册、JSON Schema 校验Planner负责任务分解与路径规划简单模式直接执行复杂模式拆解子任务Executor执行 Tool 调用处理超时和重试线程池、超时控制、重试策略Memory管理短期记忆和长期记忆Redis、向量数据库、本地缓存ModelAdapter屏蔽不同模型的 API 差异OpenAI、Claude、本地模型的统一接口其中最容易忽略的是 ContextManager。多 Agent 协同场景下子 Agent 的结果会不断追加到主上下文如果不对历史做裁剪或摘要很快会触发模型上下文上限。常见的做法是每次 Tool 调用后只保留最近 N 条关键记录早期记录由摘要进程压缩后放入 Memory。2.3 主从模式下的调度链路在主从模式里Harness 的调度链路可以描述为主 Agent 接收原始任务。Planner 判断任务需要哪些子 Agent 或 Tool。主 Agent 依次调用对应的 subagent Tool。每个 subagent Tool 在内部分配一个独立的 Agent 实例拥有自己的 ContextManager 和 ToolRegistry。subagent 完成后结果以结构化 JSON 返回给主 Agent。主 Agent 汇总所有子结果生成最终输出。这里要注意子 Agent 并不是简单的函数调用。它拥有完整的 Agent 生命周期也就是说它内部也存在一个控制循环。这正是 subagent 作为 Tool 调用时的复杂度来源父循环里嵌套子循环必须限制子循环的最大迭代次数否则可能出现“Agent 套 Agent死循环无止境”的情况。2.4 模型调用的抽象层为了让 Harness 不绑定具体厂商模型调用需要抽象成统一的接口。定义的接口可以非常简洁public interface ChatModel { ModelResponse chat(ListChatMessage messages, ModelRequestOptions options); }不同厂商通过适配器实现。这样一来Harness 内部不关心底层是哪个模型只关心返回的决策是否符合约定格式。这也是 Harness Engineering 的基本要求模型是可替换的框架是稳定的。3. 环境准备搭建一个最小可运行的 Java 多 Agent Harness3.1 环境要求和依赖版本为了保持教程的可复现性这里选择 Java 17 Spring Boot 3这是目前企业后端项目的主流组合。如果原始团队没有锁定版本落地前先确认自己环境的 JDK 版本。推荐环境如下环境项推荐版本说明JDK17 或 2117 是 Spring Boot 3 的最低要求Maven3.9构建工具Spring Boot3.2.x稳定版本Lombok1.18.30减少样板代码Jackson随 Spring Boot 管理JSON 序列化学习环境可以直接使用本机 IDE生产环境需要额外考虑容器化、配置中心和监控。3.2 项目目录结构建议先创建标准 Maven 结构agent-harness-demo/ ├── pom.xml └── src/main/java/com/example/agent/ ├── AgentHarnessApplication.java ├── harness/ │ ├── Agent.java │ ├── Harness.java │ ├── Tool.java │ ├── ToolRegistry.java │ ├── ContextManager.java │ └── ToolResult.java ├── agent/ │ ├── MasterAgent.java │ └── SubAgent.java ├── tool/ │ ├── SubAgentTool.java │ └── CodeAnalysisTool.java └── model/ ├── ChatModel.java ├── ChatMessage.java └── ModelResponse.java这个结构把 harness、agent、tool、model 分到不同包职责清晰。后续新增 Skill 时只需在 model 或 resource 目录增加配置不需要改 Harness 核心类。3.3 依赖配置在pom.xml中加入核心依赖dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /dependency /dependencies这里没有引入任何具体的模型 SDK因为 Harness 核心不依赖模型厂商。接入 OpenAI、Claude 或本地模型只需要在 model 层补充适配器。3.4 核心接口设计定义 Agent 接口public interface Agent { String name(); AgentResponse execute(AgentContext context); }定义 Tool 接口public interface Tool { String name(); String description(); ToolResult execute(String inputJson); }定义 Harness 核心Component public class Harness { private final ContextManager contextManager; private final ToolRegistry toolRegistry; public AgentResponse run(Agent agent, AgentContext requestContext) { contextManager.start(requestContext); AgentResponse response agent.execute(contextManager.getContext()); contextManager.finish(); return response; } }这样的设计把“Agent 如何决策”和“Harness 如何运行”分离了。Agent 只负责 execute 内的业务逻辑Harness 保证执行环境。4. 实战案例用主从模式实现多 Agent 代码评审工作流4.1 案例场景与分工本案例选择“多 Agent 代码评审”这个场景。开发团队提交一段 Java 代码后由一个主 Agent 负责整体评审流程三个子 Agent 分别完成静态审视、规范检查和风险识别。Agent 名称职责输入输出ReviewerMasterAgent拆解评审任务汇总结果代码片段、评审要求结构化评审报告StaticAnalysisSubAgent分析类结构、方法边界Java 源码结构问题列表ConventionCheckSubAgent检查命名、注释、异常处理Java 源码规范问题列表RiskAssessmentSubAgent识别安全、性能、稳定性风险Java 源码风险评分与建议主 Agent 先把任务拆成三路分别调用三个子 Agent 的 Tool再汇总输出。4.2 定义 Agent 和 Tool 接口首先定义一个最简单的 ToolResultData Builder public class ToolResult { private boolean success; private String toolName; private String outputJson; }然后实现一个 ToolRegistryComponent public class ToolRegistry { private final MapString, Tool toolMap new ConcurrentHashMap(); public void register(Tool tool) { toolMap.put(tool.name(), tool); } public ToolResult execute(String toolName, String inputJson) { Tool tool toolMap.get(toolName); if (tool null) { return ToolResult.builder() .success(false) .toolName(toolName) .outputJson({\error\:\tool not found\}) .build(); } return tool.execute(inputJson); } }4.3 把子 Agent 包装成 Tool这是主从模式最核心的一步。子 Agent 内部有完整的提示词和小循环但对外表现为 Tool。Component public class StaticAnalysisSubAgentTool implements Tool { Override public String name() { return subagent_static_analysis; } Override public String description() { return 对Java源码进行静态结构分析返回类、方法、字段维度的问题列表。; } Override public ToolResult execute(String inputJson) { // 这里在真实项目中会构造子Agent上下文调用模型完成分析 SubAgent subAgent new SubAgent(static-analysis, getSystemPrompt()); String resultJson subAgent.run(inputJson); return ToolResult.builder() .success(true) .toolName(name()) .outputJson(resultJson) .build(); } }注意这里子 Agent 不应该访问主 Agent 的上下文。两者的上下文隔离是主从模式防止状态混乱的关键。4.4 主 Agent 的调度逻辑主 Agent 的核心逻辑是拆任务、调工具、汇总。Component public class ReviewerMasterAgent implements Agent { private final ToolRegistry toolRegistry; Override public String name() { return code-review-master; } Override public AgentResponse execute(AgentContext context) { String code context.getInput(code); ToolResult staticResult toolRegistry.execute(subagent_static_analysis, code); ToolResult conventionResult toolRegistry.execute(subagent_convention_check, code); ToolResult riskResult toolRegistry.execute(subagent_risk_assessment, code); String finalReport String.format( 评审完成。静态分析结果%s。规范检查结果%s。风险评估结果%s。, staticResult.getOutputJson(), conventionResult.getOutputJson(), riskResult.getOutputJson() ); return AgentResponse.success(finalReport); } }这里用三个子 Agent 并行汇聚生产环境可以替换为线程池并发执行并控制总超时时间。4.5 运行验证与输出分析编写一个命令行启动器来验证SpringBootApplication public class AgentHarnessApplication { public static void main(String[] args) { ApplicationContext ctx SpringApplication.run(AgentHarnessApplication.class, args); ReviewerMasterAgent master ctx.getBean(ReviewerMasterAgent.class); AgentContext context new AgentContext(); context.putInput(code, class UserService { public boolean check(String s){ return s.isBlank(); } }); AgentResponse response master.execute(context); System.out.println(response.getMessage()); } }运行后应看到主 Agent 把三个子任务的结果拼装成最终报告。如果某个子 Agent 的 Tool 返回失败主 Agent 不应直接崩溃而应捕获失败并继续汇总其他结果。注意验证多 Agent 项目时不要只看主流程能跑通还要验证子 Agent 失败、工具参数校验失败、上下文超限这三个边界场景。5. Skill 开发规范让 AI 按 Spring Boot 开发规范生成代码5.1 Skill、Prompt、Plugin 三者有什么区别在 Agent Harness 生态里Skill 是越来越重要的一层。很多人把 Skill 与 Prompt 模板混为一谈实际上它们的定位不同。Skill 的结构化程度远高于普通 Prompt。Skill 可以包含 Markdown 指令、参考文档、脚本、校验规则甚至内置测试样例。它更像一个“插件化的能力包”。类型形态作用例子Prompt一段文本告诉模型怎么回答“请用 Spring Boot 写接口”Plugin可执行代码扩展模型的外部能力文件搜索、数据库查询Skill文档 规范 脚本约束模型按团队标准生成内容Spring Boot API 开发规范 Skill5.2 Skill 的目录结构和元信息一个标准的 Skill 通常包含以下内容spring-boot-api-skill/ ├── SKILL.md ├── reference/ │ ├── api-design-standards.md │ └── error-handling-guide.md ├── templates/ │ └── controller-template.java └── scripts/ └── validate-project.shSKILL.md是入口文件需要在开头定义元信息--- name: spring-boot-api-developer description: 按照团队规范编写 Spring Boot REST API包括控制器、服务层、异常处理。 version: 1.0.0 tags: [spring-boot, java, rest-api] --- # Spring Boot API 开发 Skill 本 Skill 用于约束 AI 在生成 Spring Boot 接口时的代码风格与结构。元信息中的description是模型判断何时启用 Skill 的依据建议写得具体包含触发场景。5.3 写一个 Spring Boot API 开发 Skill这个 Skill 要解决的实际问题是AI 生成 Controller 时经常不遵守团队规范例如返回值包装不统一、异常处理缺失、校验注解缺失。在 SKILL.md 中写清楚约束## 强制规则 1. 所有接口返回值必须使用统一响应包装类 ApiResponse。 2. Controller 层不允许出现业务逻辑只做参数接收和结果转发。 3. 参数校验必须使用 jakarta.validation 注解。 4. 异常必须在 ControllerAdvice 中统一处理禁止 try-catch 吞异常。 5. 分页参数使用 PageRequest禁止自定义分页参数名。reference 目录下放更详细的规范文档scripts 目录放校验脚本例如检查是否引入了统一包装类#!/bin/bash # validate-controller.sh grep -rl class .*Controller --include*.java | while read file; do if ! grep -q ApiResponse $file; then echo 未使用 ApiResponse 包装$file exit 1 fi done echo Controller 规范校验通过5.4 在 Cursor 等 IDE 中安装与校验 Skill在 Cursor 等支持 Skill 的 AI IDE 中可以将 Skill 放在项目的.cursor/skills目录下或者通过 IDE 的命令面板导入。安装后需要校验三件事AI 是否能在正确场景自动触发 Skill。Skill 中的规范是否确实影响生成代码。校验脚本是否可用于 CI 流水线。推荐做法是在项目中增加.cursor/skills/spring-boot-api-developer目录并直接提交到代码仓库这样团队所有人都能共享同一套 Skill。Java 开发者通常还可以安装 Spring Boot 官方插件、Lombok 插件帮助 IDE 解析但 Skill 解决的是“AI 生成代码是否符合规范”两者互补。5.5 开发和调试 Skill 时的常见坑开发 Skill 时最容易犯的错误是规则写得太抽象。比如“代码要规范”“注意异常处理”这类描述模型无法落地。正确做法是给出具体的反例和正例## 错误示例 try { return userService.getUserById(id); } catch (Exception e) { log.error(error, e); return null; } ## 正确示例 GetMapping(/users/{id}) public ApiResponseUserDTO getUserById(PathVariable Long id) { return ApiResponse.success(userService.getUserById(id)); }另一个坑是 Skill 文件过长。模型上下文有限SKILL.md 应保持在合理长度详细规则放到 reference 文档中按需加载。6. 多 Agent 协同中的常见问题与排查路径6.1 问题现象与排查速查表问题现象常见原因检查方式处理建议主 Agent 反复调用同一个 Tool缺少最大迭代限制查看 Harness 日志中的 action 序列为循环设置上限增加“调用重复 Tool 时改变策略”的提示子 Agent 结果格式异常JSON Schema 校验不严格检查 Tool 输出是否经过反序列化对子 Agent 输出做结构化校验失败时重试上下文快速超限子 Agent 结果未压缩检查 ContextManager 的 token 统计启用摘要压缩只保留关键结论Skill 始终不生效SKILL.md 的 description 描述不准确检查模型是否匹配到该 Skill重写 description增加触发关键词主 Agent 汇总时丢失部分结果子 Agent 并发执行未收集全量结果检查线程池提交和 Future 收集逻辑使用 CountDownLatch 或 CompletableFuture 聚合6.2 典型排查案例一Agent 循环失控现象是主 Agent 在调用一个子 Agent Tool 后没有正常进入下一个步骤而是不断以相似参数重复调用直到超过迭代上限。排查路径查看 Harness 日志中的 action 序列确认重复调用的 Tool 是否相同。检查模型的决策输入确认上下文里是否有上一次 Tool 结果的摘要。如果上下文包含完整原始代码可能导致模型陷入“继续分析”的路径。解决方式在每次 Tool 返回后要求模型先输出“当前结论”再决定下一步同时设置最大迭代次数。6.3 典型排查案例二subagent Tool 调用失败场景是主 Agent 认为调用了subagent_risk_assessment但 ToolRegistry 返回tool not found。排查路径检查 Tool 是否在 Spring 启动时被注册进 ToolRegistry。如果使用了Component确认包扫描路径是否正确。如果 Tool 名称由常量定义检查主 Agent 提示词里的名称是否与常量一致。建议在主 Agent 的提示词中直接给出可用 Tool 列表避免模型自由编造名称。6.4 典型排查案例三上下文被截断导致协作结果漂移多 Agent 协同最隐蔽的问题是上下文截断。主 Agent 上下文包含多个子 Agent 的长输出当 token 超限后ContextManager 可能直接丢弃最早记录导致主 Agent 忘记最初任务要求。解决思路不是一味扩大上下文而是引入分层记忆短期记忆保存当前会话最近的决策。中期记忆保存每个子 Agent 的结论摘要。长期记忆保存任务目标和关键约束。ContextManager 在每次写入记录前先做摘要压缩再判断是否进入长期记忆。7. 企业级落地的最佳实践与扩展方向7.1 学习环境与生产环境的差异学习时可以把 Agent Harness 跑在本地 IDE直接调用远程模型 API使用内存 Map 做上下文和工具注册。但进入生产环境后必须有明显升级。能力项学习环境生产环境上下文存储本地内存分布式缓存或数据库日志追踪控制台输出结构化日志 链路追踪权限隔离无限制RBAC 操作审计模型切换硬编码配置中心动态切换失败恢复直接报错重试、降级、人工介入安全边界无工具执行沙箱化7.2 多 Agent 项目落地清单落地一个企业级多 Agent 项目前建议逐项检查是否定义统一的 Agent 接口和 Tool 接口而非各自为政。是否实现独立的 ContextManager避免上下文裸用全局变量。是否为子 Agent 设置了最大迭代次数和超时时间。是否记录了每次 Tool 调用的输入和输出便于复盘。是否对子 Agent 的输出做了结构化校验。是否把 Skill 纳入版本管理与项目代码一同发布。是否在 CI 中执行 Skill 自带的校验脚本。7.3 可扩展的工程方向Harness Engineering 的方向并不是“把更多 Agent 堆在一起”而是让系统更可控。接下来值得深入的方向包括将 subagent 调用标准化为可编排的工作流 DSL让流程定义与代码解耦。在 Harness 内置评估集用回归测试判断模型或 Skill 升级是否破坏了原有行为。把 Skill 与 CI 流水线结合让 AI 生成代码后自动跑规范校验和单元测试。在开源 open agent harness 生态中探索“模型无关”的 Harness 平台例如以 Codex 为底座的扩展实践前提是先理解控制循环和工具注册表的设计。7.4 给新人的练习路径建议如果没有接触过多 Agent Harness建议按这个顺序练习先写一个单 Agent 控制循环理解模型调用、工具调用、上下文追加。再写两个 Tool一个做文本处理一个做文件读取观察模型如何选择工具。然后把一个 Agent 包装成 Tool注册到另一个 Agent 的工具表中实现最小主从模式。接着给上下文加摘要压缩观察长任务下上下文是否稳定。最后写一个小的 Spring Boot Skill让模型按你的规范生成 Controller。学习 Harness Engineering 的正确心态是把它当作一门系统工程课而不是模型调优课。真正决定多 Agent 系统能否上线的往往不是模型聪明不聪明而是运行时是否可控、可观察、可回滚。把主从模式、Tool 封装、上下文管理和 Skill 规范这四层做扎实多 Agent 协同才会从演示项目变成可交付的企业能力。