把 Agent Harness
读得明明白白
18 章正文,基于开源项目 deepseek-harness-java,
从跑起来到读懂 ReAct 循环、事件溯源、统一配置、插件、MCP、Skills、审批治理、SSE 工具流与版本演进。
一图看清:7 个 Maven 模块如何协同
DDD 六边形架构 · 依赖方向永远指向 Domain 核心 · 点击模块查看详情并跳转对应章节
App · 启动与装配
整个系统的入口:Spring Boot 3.3 启动类、全局配置装配(AgentFactory 手工注入端口实现)、H2/MySQL 数据源切换,以及自带原生 JS 的 Web 控制台(static/index.html + app.js)。所有 Bean 在这里被"接线"成一棵可运行的树。
Trigger · REST + SSE 入口
18 个 Controller 按 CQRS 拆成 command/query 两侧,统一前缀 /api/agent、/api/harness/*、/api/workflow。它只做协议转换:把 HTTP 请求翻译成用例调用,把用例的流式输出翻译成推理、回答、工具调用、工具结果与终态事件。
API / Case · 策略树与责任链
API 层定义用例接口(IAgentUseCase),Case 层用"策略树 = 责任链"实现消息流转:AbstractStrategyRouter 统一 doApply + getNext,Agent 链 Resolve→Intent→Dispatch→Collect,任务提交链 SubmissionRoot→PermissionCheck→Enqueue。
Domain · 系统的心脏
唯一不依赖任何外层的模块。ReactLoopAgent 用 turn/step 两级循环驱动"思考-行动-观察",单 turn 上限 50 步、max_tokens 受控续写最多 4 次;SessionLog 以内存 WAL + SurfaceOp 投影实现事件溯源;ToolCallExecutor 串起 Hook 链与审批闸口。
Types (SPI) · 端口定义
六边形架构的"端口":只定义接口与值对象,不包含任何实现。Domain 依赖它表达对外需求(如 LLM 流式调用),Plugins 与 Infrastructure 实现它完成"适配"。sealed 接口 ContentBlock/StreamChunk 让协议演进可编译期检查。
Plugins · 双模式插件体系
三段式生命周期:Registry(安装/启停)→ Bridge(识别 DSH_PACKAGE / JAVA_NATIVE 等包型)→ Runtime(Java = 进程内 URLClassLoader 隔离加载;Node = sidecar 子进程 JSON-RPC)。MCP 通过 StdioMcpClient / HttpMcpClient 支持 stdio、SSE 与 streamable-http。
Infrastructure · 技术实现
Domain 端口的具体实现:MyBatis DAO 持久化会话/任务/审批等 12 张表;DeepSeekAdapter 手解 SSE 对接 LLM 网关(HTTP 总超时 120s,重试在 InMemoryLlmRuntimePort);LocalShellExecutor 提供本地命令执行能力。
一次对话的旅程:从敲下回车到看到流式回答
跟着一条消息走完整个 Harness——每个节点都对应书中一章
发起 SSE 连接第1章 →Controller 层AgentController
POST /api/agent第7章 →策略责任链Resolve→Intent
→Dispatch第6章 →ReAct 循环ReactLoopAgent
思考-行动-观察第4章 →工具执行器ToolCallExecutor
Hook 链 + 审批第8章 →SSE 事件流reasoning/chunk
tool_result/done第7章 →
stream() 调用里——其余全是 Harness 的工程:协议、路由、状态、工具、治理。这正是"模型之外的外壳"的含义。