中文
DeepSeek Harness Java (dsh-java) 是一个模块化的智能体运行时框架,其中模型适配器、工具注册表、会话事件日志和 ReAct 代理循环,都通过统一的端口与插件机制组织,可以按需替换或扩展。项目基于 Java 17、Spring Boot 3.3 与 DDD 六边形架构构建:Domain 层定义端口与领域能力,Infrastructure 层提供默认适配器,Java SPI、Node Bridge 和 MCP 插件负责挂载外部能力;插件停止或卸载时,宿主会自动回收其工具、系统提示词和 Hook 等注册项。
该项目目前处于开发者预览阶段,接口与插件协议迭代较快,后续版本可能包含不兼容变更。
English
DeepSeek Harness Java (dsh-java) is a modular agent runtime framework in which model adapters, tool registries, session event logs, and the ReAct agent loop are organized behind unified ports and plugin mechanisms, allowing them to be replaced or extended as needed. Built on Java 17, Spring Boot 3.3, and a DDD hexagonal architecture, the Domain layer defines ports and domain capabilities, the Infrastructure layer provides default adapters, and Java SPI, Node Bridge, and MCP plugins mount external capabilities. When a plugin stops or is unloaded, the host automatically revokes its tool, system prompt, and hook registrations.
The project is currently in developer preview. Its APIs and plugin protocols are evolving quickly, and future releases may introduce breaking changes.
- 仓库:https://gitcode.net/KnowledgePlanet/deepseek-harness-java
- 端口:
8090(启动即开箱) - License:见仓库根目录
- 1. 项目介绍
- 2. 快速体验
- 3. 整体架构设计
- 4. 领域设计
- 5. 核心机制深入
- 6. 工具系统
- 7. 插件与扩展
- 8. REST API 概览
- 9. Web 控制台
- 10. 关键配置
- 11. 持久化模型
- 12. 安全与执行边界
- 13. 生产化注意事项
- 14. 当前边界与已知隐患
- 15. 文档索引
deepseek-harness-java 是一个单实例可运行的 Agent Harness。它提供一个完整的运行时环境,让 LLM 驱动的智能体可以:
- 通过 ReAct 循环(Reason → Act → Observe)自主调用工具完成任务;
- 操作文件系统、Shell、Web 搜索/抓取等真实环境;
- 通过 Java SPI 插件 / Node sidecar 插件 / MCP Server 扩展能力;
- 接受权限评估与人工审批的治理约束;
- 将全过程以事件溯源方式落库,支持回放与审计。
项目自带一个原生 JS 实现的 Web 控制台(无前端构建链),打开浏览器即可对话、管理工作区、审批任务、查看插件与模型。
| 场景 | 项目提供的能力 |
|---|---|
| 本地智能体工作台 | 浏览器 UI、SSE 对话、Markdown 渲染、会话与工作区管理 |
| Java 原生 Agent 运行时 | Spring Boot 装配、ReAct Loop、工具注册与执行、会话事件 |
| 企业内部扩展平台 | Java SPI 插件、Node sidecar 插件、MCP 工具适配、插件生命周期 API |
| 任务自动化系统 | 任务提交、队列、审批、目标、计划、待办、工作流、后台作业 |
| 可替换基础设施 | Domain 定义端口,Infrastructure 提供数据库、LLM、执行环境适配 |
定位说明:它当前是单实例 Agent 运行时,不是开箱即用的分布式多租户 SaaS。生产化部署前仍需根据企业要求补充鉴权策略、审计、集群协调、可观测性与完整集成测试(见 13. 生产化注意事项)。
- 🤖 Agent 对话:阻塞式
POST /api/agent/message与 SSE 流式POST /api/agent/stream,支持取消、状态查询、agentId会话复用 - 🔁 ReAct 循环:
ReactLoopAgent驱动模型输出 → 工具调用 → 结果回填 → 续步,单 turn 上限 50 步 - 🧰 工具系统:
fs_*文件工具、shell_execute、web_search/web_fetch、ask_user_question,统一ToolCallExecutor+ PRE/POST Hook - 🧩 双模式插件:Java Native(进程内
URLClassLoader隔离)与 Node Bridge(sidecar 进程 JSON-RPC),另支持 MCP stdio 工具 - 🛡️ 审批链路:提交期权限矩阵评估 + 审批决策,高风险工具(Shell、写文件、插件、子进程)默认需人工审批
- 💾 持久化:会话/任务/目标/审批/模型设置落库;默认 MySQL,standalone profile 用 H2 文件库
- 🖥️ Web 控制台:原生 JS(约 3300 行)+ marked + DOMPurify + highlight.js,本地化加载无外网依赖
- 🐳 Docker Compose:一条命令拉起完整环境
| 软件 | 版本 | 必需性 |
|---|---|---|
| JDK | 17+ | 必需 |
| Maven | 3.9+ | 构建必需 |
| Node.js | 18+ | 仅使用 Node Bridge Plugin 时必需 |
| MySQL | 5.7+ | 仅默认(MySQL)profile 需要 |
| Docker | 24+ | 可选,用于 Compose 启动 |
git clone git@github.com:fuzhengwei/deepseek-harness-java.git
cd deepseek-harness-java
# 完整构建(含测试)
mvn clean verify
# 快速打包(跳过测试)
mvn clean package -DskipTests测试用例与后续功能补测流程见 docs/md/test-cases.md。
构建产物:
deepseek-harness-java-app/target/deepseek-harness-java-app-1.0.0-SNAPSHOT.jar
deepseek-harness-java-app/target/deepseek-harness-java-app-0.1.6.jar
如需分发一个非 Docker 的本地启动包,可执行:
scripts/package-local.sh --zip产物位于 dist/deepseek-harness-java-local 和 dist/deepseek-harness-java-local-<version>.zip。解压后运行:
cd deepseek-harness-java-local
./start.sh该包默认启用 standalone Profile,使用 H2 文件库,只需要本机安装 JDK 17+;Windows 可运行 start.bat。
适合本机快速体验和演示,无需安装 MySQL——使用内嵌 H2 文件库:
export LLM_API_KEY='your-model-api-key'
java -jar deepseek-harness-java-app/target/deepseek-harness-java-app-0.1.6.jar \
--spring.profiles.active=standaloneH2 数据文件默认写入 ./data/deepseek-harness-java.mv.db。
如需覆盖默认模型服务:
java -jar deepseek-harness-java-app/target/deepseek-harness-java-app-0.1.6.jar \
--spring.profiles.active=standalone \
--harness.llm.deepseek.base-url='http://127.0.0.1:8777/v1' \
--harness.llm.deepseek.api-key="$LLM_API_KEY" \
--harness.llm.deepseek.default-model='gpt-5.5'推荐直接导入仓库内的 MySQL 8 dump,它包含 12 张 Harness 表:
mysql -u root -p < docs/dev-ops/mysql/sql/deepseek_harness_java.sql脚本会创建并切换到 deepseek_harness_java。如果使用已有数据库实例,也可在启动时让 Spring SQL 初始化自动创建 schema.sql;导入完整 dump 适合需要拿到与开发环境一致的表结构,或手动管理库表的场景。
默认 application.yml 面向外部 MySQL。通过环境变量覆盖连接信息,数据库名必须与导入的库名一致:
export SPRING_DATASOURCE_URL='jdbc:mysql://127.0.0.1:3306/deepseek_harness_java?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true'
export SPRING_DATASOURCE_USERNAME='root'
export SPRING_DATASOURCE_PASSWORD='your-db-password'
export LLM_API_KEY='your-model-api-key'
java -jar deepseek-harness-java-app/target/deepseek-harness-java-app-1.0.0-SNAPSHOT.jar如果导入的是仓库 dump,应用启动时仍会执行 deepseek-harness-java-app/src/main/resources/schema.sql;其中的 CREATE TABLE IF NOT EXISTS 不会清空已有数据。
数据库导入完成后,需要把模型服务配置保存到 Harness,而不是只依赖启动参数。打开 Web 控制台的 设置 → 模型:
- 从 渠道模板 下拉选择供应商(OpenAI / DeepSeek / 通义千问 / 智谱 GLM / 豆包 / Kimi / Anthropic Claude / Ollama / 自定义…),自动填充协议与默认地址;
- 补全 API Key,点击 ⟳ 同步模型 从上游拉取可用模型列表(按协议自动选择
/models或/api/tags,失败不会清空已保存配置); - 选择模型并保存。
| 字段 | 说明 |
|---|---|
| 渠道编码 | 运行时选择模型渠道的主键,如 default / qwen-max / local-ollama |
| Provider | 渠道标识,任意编码均可(模板之外的 provider 会按其协议动态路由) |
| 协议 | openai(OpenAI 兼容,默认)/ anthropic(Messages 协议)/ ollama(原生) |
| 模型 | 模型编码,可用同步按钮从上游拉取后下拉选择 |
| Base URL | 渠道基础地址 |
| API Key | 渠道密钥(Ollama 本地可留空) |
| Enabled | 开启 |
channelCode=default 是 Agent 的全局默认模型渠道。保存后,数据库配置优先生效;只有未配置可用渠道时,才会回退到 LLM_* 环境变量和 harness.yml 文件兜底。web / headless 只用于工具与审批画像,不再需要各自重复配置模型。
也可以调用 API 保存:
curl -X POST http://localhost:8090/api/harness/settings/models \
-H 'Content-Type: application/json' \
-d '{
"channelCode": "default",
"providerCode": "deepseek",
"modelCode": "glm-5.3-flash",
"baseUrl": "http://127.0.0.1:8777/v1",
"apiKeyRef": "your-model-api-key",
"protocol": "openai",
"enabled": true
}'渠道相关 API:
| 接口 | 说明 |
|---|---|
GET /api/harness/channels/presets |
查询内置渠道模板(协议、默认地址、推荐模型),只读不落库 |
GET /api/harness/settings/models |
查询全部模型渠道及当前 active 渠道 |
POST /api/harness/settings/models |
保存模型渠道配置(含 protocol 字段,缺省按 openai) |
POST /api/harness/settings/models/active |
激活指定渠道,请求体:{ "channelCode": "qwen" } |
DELETE /api/harness/settings/models/{channelCode} |
删除指定模型渠道 |
POST /api/harness/settings/models/discover |
同步上游模型列表,请求体:{ "baseUrl", "apiKeyRef", "protocol" } |
export LLM_API_KEY='your-model-api-key'
docker compose up --build -dCompose 配置:暴露 8090:8090;默认 standalone Profile;挂载 ./data、./plugins、./.dsh、./workspaces;通过 LLM_API_KEY / OPENAI_API_KEY 注入模型凭据。
启动后访问:
http://localhost:8090/
控制台支持:新建/切换/重命名/删除工作区、创建和恢复 Agent 会话、流式对话与工具卡片渲染、取消运行、模型设置管理、插件启停、待审批任务处理、历史会话回放。
阻塞式对话:
curl -X POST http://localhost:8090/api/agent/message \
-H 'Content-Type: application/json' \
-d '{
"agentId": "local-agent",
"message": "Hello!",
"channelCode": "default",
"maxTokens": 8192,
"cwd": "."
}'
agentId和message必填;channelCode、maxTokens、cwd省略后使用harness_model_setting中当前激活的模型渠道;文件配置只在数据库无可用渠道时用于首次初始化。
SSE 流式对话:
curl -N -X POST http://localhost:8090/api/agent/stream \
-H 'Content-Type: application/json' \
-d '{"agentId": "local-agent", "message": "介绍这个项目的架构"}'如果配置了 harness.auth.api-keys,需额外携带 -H 'X-API-Key: your-api-key' 或 -H 'Authorization: Bearer your-api-key'。
2d-weekend-mall 的商城服务可以独立运行;但商城页面里的 AI 客服 依赖先在 Harness 中导入 mall-weekend-assistant 插件。没有插件时,Harness 中不存在商城订单、商品和物流查询工具。
cd /Users/fuzhengwei/DevOps/2d-weekend-mall
mvn clean package -DskipTests
# 先启动商城,端口 18080
mvn -pl mall-app spring-boot:run
# 再在另一个终端启动 Harness,端口 8090
cd /Users/fuzhengwei/DevOps/deepseek-harness-java
java -jar deepseek-harness-java-app/target/deepseek-harness-java-app-1.0.0-SNAPSHOT.jar打开 Harness 控制台的 设置 → 插件,选择:
/Users/fuzhengwei/DevOps/2d-weekend-mall/mall-agent-plugin/target/mall-agent-plugin-1.0.0-SNAPSHOT.jar
控制台会解析 META-INF/plugin.yaml,确认后安装并启动插件。
也可以用 API 安装、激活和运行:
curl -X POST http://localhost:8090/api/harness/plugins/install \
-H 'Content-Type: application/json' \
-d '{
"pluginId": "mall-weekend-assistant",
"displayName": "2D Weekend Mall Assistant",
"pluginVersion": "1.0.0-SNAPSHOT",
"runtimeType": "JAVA_NATIVE",
"sourcePath": "/Users/fuzhengwei/DevOps/2d-weekend-mall/mall-agent-plugin/target/mall-agent-plugin-1.0.0-SNAPSHOT.jar",
"entrypoint": "mall-agent-plugin-1.0.0-SNAPSHOT.jar"
}'
curl -X POST http://localhost:8090/api/harness/plugins/activate \
-H 'Content-Type: application/json' \
-d '{"pluginId":"mall-weekend-assistant"}'
curl -X POST http://localhost:8090/api/harness/plugins/run \
-H 'Content-Type: application/json' \
-d '{"pluginId":"mall-weekend-assistant"}'插件启动成功后,Agent 可用工具包括 plugin__mall-weekend-assistant__search_products、plugin__mall-weekend-assistant__get_product、plugin__mall-weekend-assistant__query_orders、plugin__mall-weekend-assistant__get_order 和 plugin__mall-weekend-assistant__query_logistics。
最后在插件卡片点击 配置,保存两组键值:
| Key | Value |
|---|---|
mall.base-url |
http://127.0.0.1:18080 |
mall.service-token |
与商城 MALL_SERVICE_TOKEN / mall.security.service-token 一致 |
保存配置后,建议停用并重新启用插件,确保 configure(context) 读取到最新值。然后在商城页面右上角打开 AI 客服即可通过 Harness 完成对话和工具调用。
整体为六边形架构(端口-适配器)+ DDD 分层,共 10 个 Maven 模块,依赖严格单向:
flowchart TB
subgraph Clients["接入层"]
Browser["浏览器 Web 控制台<br/>(原生 JS + SSE)"]
Curl["curl / 第三方系统"]
end
subgraph Host["deepseek-harness-java-app(Spring Boot 启动器 :8090)"]
direction TB
subgraph Trigger["trigger 模块 · 接入适配"]
Controllers["19 个 REST Controller<br/>(CQRS 拆 command/query)"]
Auth["ApiKeyAuthInterceptor"]
end
subgraph API["api 模块 · 应用门面"]
Facade["Facade + DTO<br/>(隔离外部协议与内部用例)"]
end
subgraph Case["case 模块 · 用例编排"]
UseCase["AgentUseCase / TaskUseCase ..."]
Strategy["策略树 = 责任链<br/>AbstractStrategyRouter"]
end
subgraph Domain["domain 模块 · 27 个限界上下文"]
direction LR
AgentCore["agent<br/>ReactLoopAgent"]
SessionD["session<br/>HarnessSessionAggregate<br/>SessionLog(事件溯源)"]
TaskD["task<br/>submission/permission<br/>approval/queue/execution"]
ToolD["tool / guard / hooks<br/>ToolRegistry · ToolCallExecutor"]
PluginD["plugin<br/>Registry / Bridge / Runtime"]
RuntimeD["runtime / llm / credentials<br/>ModelSetting · ILlmRuntimePort"]
AgentRunD["agent.run<br/>AgentRun · AgentRunFactory"]
OtherD["goal / plan / todo / workflow<br/>schedule / jobs / terminal / sandbox ..."]
end
subgraph Types["types 模块 · SPI 契约(不依赖 Spring)"]
SPI["JavaHarnessPlugin · AbstractTool<br/>ToolDefinition"]
end
subgraph Infra["infrastructure 模块 · 端口实现"]
DAO["MyBatis DAO<br/>(MySQL / H2)"]
LLMGW["LLM Gateway<br/>DeepSeekAdapter · OpenAiCompatibleAdapter"]
ExecGW["LocalShellExecutor · Node sidecar JSON-RPC"]
MCP["StdioMcpClient + McpToolAdapter"]
end
end
subgraph External["外部世界"]
DB[("MySQL / H2")]
LLM["LLM API<br/>(OpenAI 兼容网关)"]
Node["Node.js sidecar"]
Shell["/bin/sh"]
McpSvr["MCP Servers (stdio)"]
end
subgraph Plugins["plugins/"]
JavaJar["Java Native 插件 JAR<br/>(URLClassLoader 隔离)"]
NodePlugin["Node 插件包<br/>(dsh-demo-plugin)"]
end
Browser -- "HTTP / SSE" --> Controllers
Curl -- "REST" --> Controllers
Controllers --> Auth --> Facade
Controllers --> UseCase
UseCase --> Strategy --> Domain
Facade --> Case
Infra -.实现端口.-> Domain
Domain --> Types
Plugins --> Types
DAO --> DB
LLMGW --> LLM
ExecGW --> Node
ExecGW --> Shell
MCP --> McpSvr
PluginD -.加载.-> Plugins
完整架构图(draw.io 源文件)见
docs/architecture.drawio,可用 draw.io / diagrams.net 打开编辑。
App → Trigger → API/Case → Domain ← Infrastructure
Domain → Types(SPI) ← Plugins
| 层 | 约束 |
|---|---|
Trigger |
不写业务规则,只负责协议、认证、参数绑定和路由 |
API |
暴露应用层 Facade 和 DTO,只依赖 Case,隔离外部协议与内部用例 |
Case |
编排跨领域流程,不直接依赖具体数据库、HTTP Client 或进程实现 |
Domain |
持有实体、值对象、领域服务、**端口(Port)**和仓库接口 |
Infrastructure |
实现 Domain 端口,负责 MySQL/H2、LLM、Node、Shell、MCP 等外部适配 |
Types |
插件与工具的轻量 SPI 契约包,不依赖 Spring |
Case 内部采用两种固定形态,避免“所有模块都套策略树”或“所有模块都直接调领域服务”:
- 薄用例:单步查询/命令直接放在
*CaseImpl,只做命令校验、DTO 映射和领域服务委托。 - 流程用例:跨多个领域服务或包含分支决策的流程,统一放在
factory+node:*Factory创建CasePipeline;*Node执行一个步骤并通过getNext()显式路由;DynamicContext承载步骤间状态;orchestration只提供通用执行器,不写业务规则。
| 模块 | 职责 |
|---|---|
deepseek-harness-java-app |
Spring Boot 启动器、静态 UI、Profile 配置、预置插件加载 |
deepseek-harness-java-trigger |
19 个 REST Controller、API Key 拦截器、Web MVC 配置 |
deepseek-harness-java-api |
应用层 Facade、Command/Query DTO、响应对象 |
deepseek-harness-java-case |
Agent、Session、Task、Plugin 等用例编排;复杂流程使用 factory + node |
deepseek-harness-java-domain |
27 个限界上下文、实体、值对象、端口、领域服务与 AgentRun 运行域 |
deepseek-harness-java-infrastructure |
Repository、DAO、Gateway、协议和执行环境适配 |
deepseek-harness-java-types |
JavaHarnessPlugin、AbstractTool、ToolDefinition 等 SPI 契约 |
plugins/* |
示例插件(Java JAR + Node demo)、插件 Archetype |
docs/ |
架构图、领域设计详解、插件开发指南 |
以 POST /api/agent/stream 为例,一条消息穿过系统的完整路径:
浏览器 UI → AgentController → AgentUseCase
→ AgentResolveNode 按 agentId 复用/创建 Agent,解析模型、Token、工作目录
→ AgentIntentNode 正则规则分类 CHAT / CODE_QUESTION / TASK_EXECUTION / CLARIFICATION
→ AgentDispatchNode 构造用户消息,agent.send(userMessage, NEXT_TURN, true)
→ AgentCollectNode 等待空闲,投影 User/Assistant/ToolCall/ToolResult 消息,按 callId 合并
→ ReactLoopAgent LLM 流式 → append AssistantChunk → ToolCallExecutor → append ToolCall/ToolResult
→ SSE 推送 meta / chunk / step_break / done
POST /api/harness/tasks/submit
→ SubmissionRootNode → ProfileResolutionNode → PermissionCheckNode → ToolResolutionNode → EnqueueNode
│ │
│ ├─ PermissionPolicyService.assess(tools) 权限矩阵评估
│ └─ ApprovalPolicyService.decide(profile, assessment)
│
├─ 免审批 → executeSession(RUNNING)
└─ 需审批 → PENDING_APPROVAL
│
POST /api/harness/approvals/{sessionId}/approve
└─→ 状态 QUEUED 落库 → HarnessExecutionService.executeSession → RUNNING → COMPLETED/FAILED
任务状态机:CREATED → PENDING_APPROVAL → QUEUED → RUNNING → COMPLETED/FAILED。执行入口对 PENDING_APPROVAL 状态直接抛异常兜底,防止绕过审批。
domain 模块共 27 个一级子包,遵循事件风暴(Event Storming)建模,六色图例贯穿下文。
flowchart LR
subgraph SESSION["会话领域"]
direction TB
s_obj["HarnessSessionAggregate"]:::obj
s_cmd1["POST /api/agent/stream 发送消息"]:::cmd
s_evt1["UserMessage 已写入"]:::evt
s_pol["意图识别策略(CHAT / CODE / TASK)"]:::policy
s_evt2["TurnStart 开启"]:::evt
s_cmd2["取消运行"]:::cmd
s_evt3["TurnEnd / SessionEndSeed"]:::evt
s_cmd1 -.-> s_evt1
s_pol -.-> s_evt2
s_cmd2 -.-> s_evt3
end
subgraph AGENT["Agent 领域"]
direction TB
a_obj["ReactLoopAgent"]:::obj
a_cmd1["LLM 流式生成"]:::cmd
a_evt1["AssistantChunk 追加"]:::evt
a_cmd2["发起工具调用"]:::cmd
a_evt2["ToolCall 已记录"]:::evt
a_pol["上下文压缩策略(保留 1/4)"]:::policy
a_ext["LLM 网关(DeepSeek / OpenAI)"]:::ext
a_rm["SSE meta / chunk / step_break / done"]:::read
a_cmd1 -.-> a_evt1
a_cmd2 -.-> a_evt2
end
subgraph TOOL["工具领域"]
direction TB
t_obj["ToolCallExecutor / ToolRegistry"]:::obj
t_cmd["执行工具"]:::cmd
t_pol["超时与重复调用守卫"]:::policy
t_evt["ToolResult 已回填"]:::evt
t_ext1["本地 Shell(/bin/sh)"]:::ext
t_ext2["MCP Server(stdio)"]:::ext
t_ext3["文件系统 / Web"]:::ext
t_rm["工具卡片(running / success)"]:::read
t_pol -.-> t_evt
end
subgraph PLUGIN["插件领域"]
direction TB
p_obj["HarnessPluginEntity"]:::obj
p_cmd["安装 / 激活 / 运行"]:::cmd
p_pol["Bridge 识别(Java / Node / MCP)"]:::policy
p_evt["插件工具注册完成"]:::evt
p_ext["Node sidecar(JSON-RPC)"]:::ext
p_rm["插件状态面板"]:::read
p_pol -.-> p_evt
end
subgraph TASK["任务领域"]
direction TB
k_obj["HarnessTaskEntity"]:::obj
k_cmd1["POST /api/harness/tasks/submit"]:::cmd
k_evt1["任务已创建 CREATED"]:::evt
k_pol["权限矩阵 + 审批决策"]:::policy
k_evt2["任务已入队 QUEUED"]:::evt
k_cmd2["执行会话"]:::cmd
k_cmd1 -.-> k_evt1
k_pol -.-> k_evt2
end
subgraph APPROVAL["审批领域"]
direction TB
v_obj["ApprovalDecisionVO"]:::obj
v_evt1["待审批 PENDING_APPROVAL"]:::evt
v_cmd["POST /approvals/{id}/approve"]:::cmd
v_evt2["审批通过 → 恢复执行"]:::evt
v_rm["待审批列表"]:::read
v_cmd -.-> v_evt2
end
subgraph EXEC["执行领域"]
direction TB
e_obj["RuntimeExecutionPlanVO"]:::obj
e_cmd["HarnessExecutionService.executeSession"]:::cmd
e_evt1["RUNNING"]:::evt
e_evt2["COMPLETED / FAILED"]:::evt
e_rm["控制台会话回放"]:::read
e_cmd -.-> e_evt1 -.-> e_evt2
end
subgraph RUNTIME["运行时领域"]
direction TB
r_obj["ModelSetting / Channel"]:::obj
r_cmd["模型发现 / 保存设置"]:::cmd
r_evt["工具目录绑定更新"]:::evt
r_ext["MySQL 持久化"]:::ext
r_rm["运行时模型目录"]:::read
r_cmd -.-> r_evt
end
s_evt1 == 驱动 ReAct 循环 ==> a_cmd1
a_cmd2 ==> t_cmd
p_evt == 注册进 ToolRegistry ==> t_obj
k_evt2 ==> v_evt1
v_evt2 ==> e_cmd
e_evt1 == 复用 Agent 链路 ==> a_cmd1
r_ext -. 事件落库供回放 .-> e_rm
r_obj -. 模型与工具目录支撑 .-> a_obj
classDef cmd fill:#B5D4F4,stroke:#85B7EB,color:#0C447C
classDef evt fill:#FAC775,stroke:#EF9F27,color:#633806
classDef ext fill:#F4C0D1,stroke:#ED93B1,color:#72243E
classDef policy fill:#D4537E,stroke:#993556,color:#ffffff
classDef read fill:#9FE1CB,stroke:#5DCAA5,color:#085041
classDef obj fill:#EF9F27,stroke:#BA7517,color:#412402
style SESSION stroke-dasharray:5 4
style AGENT stroke-dasharray:5 4
style TOOL stroke-dasharray:5 4
style PLUGIN stroke-dasharray:5 4
style TASK stroke-dasharray:5 4
style APPROVAL stroke-dasharray:5 4
style EXEC stroke-dasharray:5 4
style RUNTIME stroke-dasharray:5 4
两条业务主线:对话主线(会话 → Agent ReAct 循环 → 工具执行 → 插件扩展)与任务主线(任务提交 → 权限/审批 → 排队 → 执行,执行阶段复用 Agent 链路);运行时领域横向提供模型渠道、执行画像与工具目录支撑,过程事件落库供控制台回放。
| 能力面 | Bounded Contexts | 核心对象 |
|---|---|---|
| Agent 执行核心 | agent, llm, session |
ReactLoopAgent、Phase(Idle/Running/Maintenance)、ILlmRuntimePort、HarnessSessionAggregate、SessionLog |
| 任务与目标 | task, goal, plan, todo, workflow, schedule, jobs |
HarnessTaskEntity、GoalAggregate、WorkflowRun |
| 工具与插件 | tool, plugin, typert, sdk |
ToolRegistry、ToolCallExecutor、RuntimeApprovalGate |
| 运行时与环境 | runtime, credentials, coderuntime, terminal, lsp, sandbox, e2b, acp |
ModelSetting、Profile、各类端口 |
| 治理与存储 | guard, hooks, storage |
超时策略、Hook 引擎、KV 存储 |
| 共享与技能 | shared, skill |
HarnessStatusEnumVO 等跨域枚举 |
task 域按职责拆为五个子域:
| 子域 | 职责 | 关键对象 |
|---|---|---|
submission |
提交参数校验与过滤 | TaskSubmissionVO |
permission |
权限矩阵评估 | PermissionAssessmentVO、IPermissionMatrixPort |
approval |
审批决策与命令 | ApprovalDecisionVO、ApprovalPolicyService、ApprovalCommandService |
queue |
任务队列持久化 | IHarnessTaskQueueRepository |
execution |
会话执行 | HarnessExecutionService、IModelExecutionPort |
plugin 域拆为三条边界:Registry(PluginRegistryService,安装/元数据/启停)、Bridge(PluginBridgeService,识别 JAVA_NATIVE/DSH_NODE_BRIDGE/CODEX/CORDIS 并生成绑定计划)、Runtime(PluginRuntimeService,启动/停止/查询实例)。
想按"模块名"反查"哪个包里有什么、关键类与端口在哪"——见
docs/md/domain-modules-reference.md(领域模块速查:逐个讲解 37 个能力点的用途、子包分层、关键类、端口)。本节是"为什么这样分 + 怎么流"的叙事视角,模块速查是"每个包里有什么"的字典视角。
- Agent:聚合根服务
ReactLoopAgent,持有SessionLog、Inbox、ILlmRuntimePort、ToolRegistry、ToolCallExecutor、SystemPromptAssembler。单 turn 上限 50 步,max_tokens 受控续写最多 4 次,工具调用后经midTurnContinuation标志跳过 inbox 检查直接续步;取消用AtomicBoolean+ 100ms 轮询。 - 会话:聚合根
HarnessSessionAggregate;SessionLog为内存追加式 WAL,SurfaceProjector将事件投影为SurfaceOp(Append/Replace);BasicCompactionEngine保留最近 1/4 事件,其余由 LLM 总结。 - 任务:
HarnessTaskEntity,状态机CREATED → PENDING_APPROVAL → QUEUED → RUNNING → COMPLETED/FAILED。 - 目标:
GoalAggregate(GoalAction、GoalSnapshotVO、GoalStatus),IGoalRepository持久化。
无通用 DomainEvent 基类,事件机制即会话事件日志的事件溯源。SessionEvent 为 sealed 接口:
| 类别 | 事件 |
|---|---|
| 轮/步边界 | TurnStart、TurnEnd(reason)、StepStart、StepEnd |
| 消息 | UserMessage、AssistantChunk、AssistantMessage |
| 工具 | ToolCall(callId, name, arguments)、ToolResult(callId, ...) |
| 其他 | TodoWrite、RequestHeader、RequestContext、SessionEndSeed、PlanModeChange、AgentInboxSpliced |
流式对话的 SSE 事件与领域事件对应:meta(起始帧)、chunk(AssistantChunk 文本增量)、step_break(工具调用边界,前端据此插入工具卡片并重置消息气泡)、done(终态 + 全量 messages 含 tool 角色,供前端重建工具卡片状态)、error。
跨领域编排在 case 模块完成,统一用 AbstractStrategyRouter 责任链(doApply 处理当前节点,getNext 返回后继)。
- Agent 消息链:
AgentResolveNode → AgentIntentNode → AgentDispatchNode → AgentCollectNode(见 3.4)。 - 任务提交链:
SubmissionRootNode → ProfileResolutionNode → PermissionCheckNode → ToolResolutionNode → EnqueueNode(见 3.5)。 - 审批恢复:
approve(sessionId)→ 状态 QUEUED 落库 →HarnessExecutionService.executeSession→ RUNNING → COMPLETED/FAILED。 - 插件生命周期:
install(落HarnessPluginEntity)→activate(Bridge 识别 + recordBinding)→run(PluginRuntimeService.start)。插件工具以plugin__<pluginId>__<toolName>注册进ToolRegistry,之后与内置工具走同一套 Hook/守卫/事件回填链路。 - 工作流:
IWorkflowService.start→IWorkflowEnginePort(LocalWorkflowEnginePort)→WorkflowRun(CompletableFuture),流式复用ReactLoopAgent的 deltaSink/toolCallSink 推 SSE。
Domain 定义端口,Infrastructure 实现。关键端口:
| Domain 端口 | Infrastructure 实现 |
|---|---|
ILlmRuntimePort |
InMemoryLlmRuntimePort(流式生成,重试在此层) |
IModelExecutionPort |
ModelExecutionPort |
IModelProviderPort |
ModelProviderPort |
IRuntimeApprovalBroker |
RuntimeApprovalBroker(Domain AgentRun) |
IPermissionMatrixPort / IApprovalPolicyPort |
PermissionMatrixPort / ApprovalPolicyPort |
IToolCatalogPort |
ToolCatalogPort |
IPluginArtifactInstallerPort / IPluginRuntimeBridgePort / IPluginProcessPort |
PluginArtifactInstallerPort / PluginRuntimeBridgePort / PluginProcessPort |
IHarnessSessionRepository / ISessionEventStore / IHarnessTaskQueueRepository / IGoalRepository |
MyBatis Repository 对应实现 |
IWorkflowEnginePort |
LocalWorkflowEnginePort |
AgentRunFactoryPort |
AgentRunFactory(Domain AgentRun:装配 AgentRun + ToolCallExecutor) |
更完整的领域说明(事件清单、全部端口、子域细节)见
docs/domain-design.md;**逐模块速查(含 37 个模块的用途/子包/关键类/端口/关系)**见docs/domain-modules-reference.md。
- turn 循环:每个 turn 最多 50 步;每步 = 一次 LLM 流式生成 + 可选的工具调用。
- max_tokens 续写:输出被截断时受控续写最多 4 次。
- 工具续步:工具执行后经
midTurnContinuation标志跳过 inbox 空检查直接续步,保证工具结果一定回到模型上下文。 - Phase 状态机:
Idle / Running / Maintenance;取消用AtomicBoolean+ 100ms 轮询,协作式中断。 - 超时:LLM HTTP 总超时 120s;工具超时用
orTimeout协作式取消。
AgentIntentNode.classify() 用纯正则规则分类(无额外模型调用):
| 意图 | 说明 | 处理方式 |
|---|---|---|
CHAT |
闲聊 | 可经 [no-tools] 前缀跳过工具,直接对话 |
CODE_QUESTION |
代码问答 | 进入工具路径 |
TASK_EXECUTION |
任务执行 | 注入"先调工具"指令后进入工具路径 |
CLARIFICATION |
澄清 | 可跳过工具 |
SessionLog 是内存追加式 WAL;SurfaceProjector 把事件投影为 SurfaceOp(Append/Replace)供 UI 渲染。上下文过长时 BasicCompactionEngine 启动压缩:保留最近 1/4 事件原文,其余由 LLM 总结为摘要事件,控制 token 占用(context-window 默认 128000)。
POST /api/agent/stream 的事件序列:
| 事件 | 含义 |
|---|---|
meta |
起始帧(会话、模型、时间信息) |
chunk |
文本增量(对应 AssistantChunk) |
step_break |
工具调用边界:携带 toolName/callId/args/status,前端据此完成当前气泡、插入工具卡片、开新气泡 |
done |
终态 + 全量 messages(含 tool 角色),前端重建工具卡片最终状态 |
error |
错误帧 |
设计要点:
step_break是独立事件而非混在 chunk 文本里的字符串标记,避免多步工具调用时文本拼接到同一气泡产生乱码。
内置工具覆盖常见 Agent 操作面:
| 类别 | 工具 / 机制 | 说明 |
|---|---|---|
| 文件系统 | fs_read, fs_search, fs_write, str_replace_editor |
读取、搜索、写入和精确替换文件内容 |
| 进程执行 | shell_execute |
通过本地 Shell 执行命令,受审批与沙箱策略约束 |
| 网络 | web_search, web_fetch |
搜索与抓取网页内容 |
| 插件 | plugin__<pluginId>__<toolName> |
Java Native 或 Node Bridge 插件工具 |
| MCP | mcp__<serverName>__<toolName> |
MCP Server 工具适配 |
| 交互 | ask_user_question |
向用户发起澄清问题 |
工具调用经过统一 ToolCallExecutor:参数解析 → PRE_TOOL_USE Hook → 执行 → POST Hook → 结果回填会话事件。默认配置中 shell_execute、fs_write、plugin.run、subprocess.spawn 被列为需要审批的高风险能力;web profile 整体需要审批。
Java Native Plugin 以 JAR 方式安装,宿主使用隔离 URLClassLoader 加载(进程内、类隔离):
- 实现
JavaHarnessPlugin或继承AbstractHarnessPlugin。 - 实现
AbstractTool,声明name()、description()、parameters()、run()。 - 在
META-INF/plugin.yaml中声明插件元数据。 - 在
META-INF/services/cn.xiaofuge.deepseek.harness.domain.spi.JavaHarnessPlugin中注册入口类。 - 打包为 JAR 并放入
harness.extensions.plugins.install-root指向的目录(默认./plugins)。
插件工具注册后的名称格式:plugin__<pluginId>__<toolName>。
DSH_NODE_BRIDGE 用于兼容 Node 侧插件:
- 插件包复制到
harness.extensions.plugins.install-root; - 识别
.codex-plugin/plugin.json、package.json、cordis.yml入口; - 对可执行的 Node 插件生成受限
node <entrypoint>运行计划,通过 sidecar 进程 + JSON-RPC 通信,不经 Shell 拼接; - 入口限制在插件安装目录内的
.js、.mjs、.cjs文件。
StdioMcpClient:通过 stdin/stdout 与 MCP Server 做 JSON-RPC 2.0 通信;HttpMcpClient:支持sse与streamable-http传输,请求头可在headers中配置;McpToolAdapter把远端工具注册为mcp__<serverName>__<toolName>,与内置工具同链执行;- 在
harness.extensions.mcp.servers中配置transport,stdio 使用name/command/args/env/cwd,HTTP 使用name/transport/url/headers,启动时自动连接。
install → activate → run → disable → uninstall
预置插件可在 harness.extensions.plugins.preset 中声明(支持 auto-start / 旧字段 auto-enable),启动时自动装载。完整 Archetype 命令、代码模板和 HTTP 安装示例见 docs/md/java-plugin-development.md。
完整 Java 插件示例见 plugins/sample-tools-plugin;完整 Node Bridge 示例见 plugins/dsh-demo-plugin。
| 你要扩展什么 | 推荐方式 | 适合场景 |
|---|---|---|
| 新增 Agent 可调用的工具 | Java Native Plugin | 类型安全、进程内直调,适合 Java 生态和企业内网能力 |
| 复用已有 Node.js 能力 | Node Bridge Plugin | 通过 sidecar + JSON-RPC 调用,适合 Node 生态和进程隔离 |
| 接入第三方工具服务 | MCP Server | 只需配置 harness.extensions.mcp.servers,无需写 Harness 插件代码 |
| 修改 Agent 策略 / 领域逻辑 | 直接修改 case / domain 模块 |
只适合 fork 后深度定制,不建议插件作者这样扩展 |
推荐继承 AbstractHarnessPlugin,并把每个能力实现成 AbstractTool。插件只依赖 deepseek-harness-java-types,不要直接依赖 Spring Boot 宿主。
<dependency>
<groupId>cn.xiaofuge</groupId>
<artifactId>deepseek-harness-java-types</artifactId>
<version>1.0.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>package com.acme.weather;
import cn.xiaofuge.deepseek.harness.domain.model.entity.AbstractTool;
import cn.xiaofuge.deepseek.harness.domain.model.entity.ToolDefinition;
import cn.xiaofuge.deepseek.harness.domain.model.entity.ToolExecutionResult;
import cn.xiaofuge.deepseek.harness.domain.model.entity.ToolRunContext;
import cn.xiaofuge.deepseek.harness.domain.spi.AbstractHarnessPlugin;
import cn.xiaofuge.deepseek.harness.domain.spi.PluginContext;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class WeatherPlugin extends AbstractHarnessPlugin {
public WeatherPlugin() {
super("acme-weather");
}
@Override
public List<ToolDefinition> tools() {
return List.of(new CurrentWeatherTool());
}
@Override
public void configure(PluginContext context) {
super.configure(context); // 不要省略;tools() 的注册依赖这里
context.registerSystemPrompt("about", 100,
"## Acme Weather\n- current_weather 可查询城市当前天气。");
}
static class CurrentWeatherTool extends AbstractTool {
@Override public String name() { return "current_weather"; }
@Override public String description() { return "查询指定城市的当前天气。"; }
@Override
public Map<String, Object> parameters() {
return objectSchema()
.prop("city", stringSchema("城市名称,例如 Hangzhou"))
.required("city")
.build();
}
@Override
protected CompletableFuture<ToolExecutionResult> run(
Map<String, Object> args, ToolRunContext ctx) {
String city = str(args, "city");
if (city.isBlank()) {
return fail("city 不能为空", "MISSING_CITY");
}
return ok("{\"city\":\"" + city + "\",\"weather\":\"sunny\"}");
}
}
}JAR 内必须包含:
META-INF/plugin.yaml
META-INF/services/cn.xiaofuge.deepseek.harness.domain.spi.JavaHarnessPlugin
plugin.yaml 的 entrypoint 填插件类全限定名:
id: acme-weather
name: Acme Weather
version: 1.0.0
author: Acme
description: Weather query plugin.
entrypoint: com.acme.weather.WeatherPluginSPI 文件内容只需要一行:
com.acme.weather.WeatherPlugin
mvn package
curl -X POST http://localhost:8090/api/harness/plugins/install \
-H 'Content-Type: application/json' \
-d '{
"pluginId": "acme-weather",
"displayName": "Acme Weather",
"pluginVersion": "1.0.0",
"runtimeType": "JAVA_NATIVE",
"sourcePath": "/absolute/path/to/acme-weather-1.0.0.jar",
"entrypoint": "acme-weather-1.0.0.jar"
}'
curl -X POST http://localhost:8090/api/harness/plugins/activate \
-H 'Content-Type: application/json' \
-d '{"pluginId":"acme-weather"}'
curl -X POST http://localhost:8090/api/harness/plugins/run \
-H 'Content-Type: application/json' \
-d '{"pluginId":"acme-weather"}'加载成功后,Agent 看到的工具名是:
plugin__acme-weather__current_weather
sourcePath 按 Spring Boot 进程的工作目录解析。为避免 Maven/IDE 工作目录差异,HTTP 安装时优先使用绝对路径;harness.extensions.plugins.install-root 指向宿主管理的插件目录,Java JAR 和 Node 插件目录都会复制到这里。
也可以在 harness.yml 预装:
harness:
extensions:
plugins:
install-root: ./plugins
preset:
- plugin-id: acme-weather
display-name: Acme Weather
plugin-version: 1.0.0
runtime-type: JAVA_NATIVE
source-path: /absolute/path/to/acme-weather-1.0.0.jar
entrypoint: acme-weather-1.0.0.jar
auto-start: trueconfigure(PluginContext context) 不只用来注册工具,还可以扩展 Agent 的运行时观测面:
| 能力 | 方法 | 典型用途 |
|---|---|---|
| 工具 | registerTool(tool) |
默认 tools() 已会自动注册;适合动态工具集 |
| 系统提示词 | registerSystemPrompt(name, order, text) |
告诉模型插件能力、调用时机和约束 |
| 事件订阅 | subscribe(eventName, handler) |
观察 tool.called、plugin.started 等插件事件 |
| 事件发布 | emit(eventName, payload) |
把插件内部状态广播给其他插件 |
| Hook | registerHook("PRE_TOOL_USE", hook) |
审计、限流、注入上下文;返回 BLOCK/DENY 可阻止动作 |
| 资源释放 | registerDisposer(autoCloseable) |
关闭连接池、线程池、文件监听器等资源 |
| 配置读取 | getConfig(key, defaultValue) |
读取宿主注入的插件配置;默认实现为内存态 |
插件停止、卸载或热重载时,宿主会逆序回收这些注册项;插件作者不应自己长期持有对 ToolRegistry、HookRegistry 等宿主组件的引用。
Node 插件是独立 sidecar 进程。宿主通过 stdin/stdout 发送换行分隔的 JSON-RPC 2.0 消息,插件至少实现:
| 方法 | 说明 |
|---|---|
initialize |
返回 serverInfo,完成握手 |
notifications/initialized |
宿主发出的初始化通知,插件可忽略 |
tools/list |
返回 { "tools": [ { "name", "description", "inputSchema" } ] } |
tools/call |
执行工具,返回 { "content": [ { "type": "text", "text": "..." } ] } |
Node Bridge 当前主要扩展 Agent 可调用的工具;PluginContext 提供的 Hook、事件订阅和系统提示词注册能力目前主要面向 Java Native Plugin。如果需要深度治理 Agent 执行链路,优先选择 Java 插件。
最小 package.json:
{
"name": "acme-node-plugin",
"version": "1.0.0",
"main": "index.js"
}完整可运行的 JSON-RPC 模板见 plugins/dsh-demo-plugin/index.js。安装 Node 插件时,sourcePath 指向插件目录,entrypoint 指向目录内的 .js/.mjs/.cjs 文件:
{
"pluginId": "acme-node-plugin",
"displayName": "Acme Node Plugin",
"pluginVersion": "1.0.0",
"runtimeType": "DSH_NODE_BRIDGE",
"sourcePath": "/absolute/path/to/acme-node-plugin",
"entrypoint": "index.js"
}pluginId、plugin.yaml.id、SPI 实现类返回的pluginId()三者必须一致。plugin.yaml.entrypoint是 Java 类全限定名;HTTP 安装请求里的entrypoint是宿主定位制品的文件路径。- Java 插件依赖
deepseek-harness-java-types使用provided,业务依赖打进插件 JAR,不要重复打包宿主已有框架。 - 插件 ID 使用小写短横线命名,避免和数据库主键的大小写敏感策略产生歧义。
configure()只做注册和轻量初始化,不要执行长阻塞任务;耗时资源建议懒加载并交给线程池。- 替换安装目录中的 JAR 可触发热重载;插件会先注销旧工具/Hook,再加载新 JAR。
- 高风险操作应设计成显式工具,并依赖宿主的沙箱、审批、Hook 和审计链路;不要在插件构造函数中隐式执行危险动作。
- 插件抛出的业务失败应返回
fail(message, code),让模型看到结构化错误,而不是直接抛异常终止 Agent。
| 样例 | 说明 |
|---|---|
plugins/sample-tools-plugin |
Java 插件:天气查询 + 文件摘要,并演示 PluginContext 注册提示词、事件和 Hook |
plugins/dsh-demo-plugin |
Node Bridge 插件:最小 JSON-RPC sidecar,演示 initialize、tools/list、tools/call |
智能客服场景的插件接入、示例工具、安装验证和生产化边界见 docs/md/smart-customer-service-plugin.md。
flowchart TD
subgraph INSTALL["① 安装 install"]
SRC1["HTTP 接口上传/安装"] --> CASE["InstallHarnessPluginCase"]
SRC2["预置声明 harness.extensions.plugins.preset<br/>(启动时 PresetPluginLoader 扫描)"] --> CASE
CASE --> REG["PluginRegistryService.installPlugin()"]
REG --> ART["ArtifactInstallerPort.install()<br/>制品复制到 harness.extensions.plugins.install-root"]
ART --> DB1[("落库 HarnessPluginEntity<br/>状态 REGISTERED")]
end
subgraph LOAD["② 激活 activate"]
DB1 --> ACT["ActivateHarnessPluginCase<br/>PluginBridgeService.inspect()"]
ACT --> RT{"runtimeType"}
RT -->|"JAVA_NATIVE"| MGR["JavaPluginRuntimeManager.start()"]
MGR --> LD["JavaPluginLoader.load()<br/>新建隔离 URLClassLoader"]
LD --> MF["读取 META-INF/plugin.yaml<br/>生成 PluginManifest"]
MF --> EP{"plugin.yaml 声明 entrypoint?"}
EP -->|"是"| CLS["加载入口类并实例化"]
EP -->|"否"| SPI["ServiceLoader SPI<br/>META-INF/services/...JavaHarnessPlugin"]
CLS --> RUN["onStart() 并收集 tools()"]
SPI --> RUN
RT -->|"NODE_BRIDGE"| NB["识别 .codex-plugin/plugin.json、<br/>package.json、cordis.yml 入口<br/>生成受限 node 运行计划"]
RUN --> ADAPT["工具适配为 PluginToolDefinition<br/>命名 plugin__<pluginId>__<toolName>"]
NB --> ADAPT
ADAPT --> TR["注册到 ToolRegistry<br/>并注入 SystemPrompt 能力说明"]
TR --> RDY{"bridgeReady?"}
RDY -->|"是"| OK[("状态 ACTIVE<br/>可被 Agent 调用")]
RDY -->|"否"| FAIL[("状态 FAILED")]
end
subgraph RUNTIME["③ 运行与治理 run / disable / uninstall"]
OK --> CALL["Agent 循环按名称调用<br/>ToolRegistry.lookup(plugin__...)"]
CALL --> EXEC["Java 插件进程内直调<br/>Node 插件走 sidecar JSON-RPC"]
OK --> MGMT["disable / uninstall"]
MGMT --> STOP["注销工具 + 移除提示词段落<br/>onStop() 并关闭 ClassLoader"]
end
FAIL -.->|"修复后重新 activate"| ACT
以下是最常用的 API 分组;路径均为 Spring Boot 实际暴露路径。
| 分组 | 端点示例 | 说明 |
|---|---|---|
| Agent | POST /api/agent/message, POST /api/agent/stream |
普通对话与 SSE 流式对话 |
| Agent 控制 | GET /api/agent/{agentId}/status, POST /api/agent/{agentId}/cancel |
查询状态和取消运行 |
| 工作区 | GET /api/agent/workspaces, POST /api/agent/workspaces |
查询、创建、重命名、排序、删除工作区 |
| 任务 | POST /api/harness/tasks/submit |
提交 Harness Task |
| 目标 | GET /api/harness/goals/{sessionId}, POST /api/harness/goals/{sessionId} |
查询与维护目标状态 |
| 审批 | GET /api/harness/approvals/pending, POST /api/harness/approvals/{sessionId}/approve |
查询待审批项并审批 |
| 模型设置 | GET/POST /api/harness/settings/models, POST /api/harness/settings/models/active, DELETE /api/harness/settings/models/{channelCode} |
查询、保存、切换、删除模型渠道 |
| 模型发现 | POST /api/harness/settings/models/discover, GET /api/harness/runtime/models |
发现模型和查询运行时目录 |
| 插件 | GET /api/harness/plugins, POST /api/harness/plugins/install |
插件列表、安装、激活、运行、启停、卸载 |
| 终端 | POST /api/harness/terminal/sessions, GET /api/harness/terminal/sessions/{id}/read |
打开、发送、读取、关闭终端 |
| 工作流 | POST /api/workflow/start, POST /api/workflow/stream |
启动工作流、取消和 SSE 输出 |
| 控制台 | GET /api/harness/console/sessions, GET /api/harness/console/sessions/{id}/messages |
会话与消息回放 |
启动后访问 http://localhost:8090/。内置静态前端位于 deepseek-harness-java-app/src/main/resources/static,原生 JavaScript 实现(约 3300 行,无构建框架),本地化加载以下渲染库:
marked.min.js:Markdown 解析;purify.min.js:HTML 安全过滤;highlight-core.min.js/highlight-common.min.js:代码高亮。
控制台覆盖的主要操作:
- 新建、切换、重命名、删除工作区;
- 创建和恢复 Agent 会话;
- 发送普通消息或流式消息(工具调用渲染为独立工具卡片);
- 取消正在运行的 Agent;
- 查询、保存、删除和发现模型;
- 查看和启停插件;
- 查看待审批任务并执行审批;
- 查看历史会话与消息。
运行时能力统一在 deepseek-harness-java-app/src/main/resources/harness.yml 中配置。部署时可以在进程工作目录放置同名文件,Spring Boot 会用它覆盖包内默认值;模型、审批、沙箱、skills、MCP 和插件使用同一个入口。旧的 harness-extensions.yml 仍以 optional 方式兼容加载,但新部署建议只使用 harness.yml。
仓库内置了一个 Baidu AI Search 示例 skill:skills/baidu-ai-search/SKILL.md。它会被 harness.extensions.skills.roots 中的 ./skills 根目录发现,并指导 Agent 使用 mcp__baidu-ai-search__<tool-name> 工具。
| 配置项 | 作用 | 说明 |
|---|---|---|
server.port |
HTTP 端口 | 默认 8090 |
spring.datasource.* |
JDBC 数据源 | 默认 MySQL;standalone profile 用 H2 |
harness.llm.deepseek.base-url |
模型 API 地址兜底 | 支持 OpenAI 兼容 Chat Completions;数据库无对应设置时生效 |
harness.llm.deepseek.api-key |
模型 API Key 兜底 | 务必用环境变量注入(${LLM_API_KEY}),不要硬编码提交 |
harness.llm.deepseek.default-model |
默认模型种子 | 例如 gpt-5.5;数据库已有默认设置时不覆盖 |
harness.llm.deepseek.context-window |
上下文窗口 | 默认 128000 |
harness.auth.api-keys |
宿主 API Key 列表 | 多个 Key 用英文逗号分隔;为空则本地放行 |
harness.approval.required-tools |
需要审批的工具 | 默认含 shell_execute、fs_write、plugin.run、subprocess.spawn |
harness.approval.required-profiles |
需要审批的 Profile | 默认含 web |
harness.approval.runtime-timeout-ms |
运行期审批等待超时 | 默认 600000(10 分钟) |
harness.sandbox.default-mode |
沙箱模式 | READ_ONLY / WORKSPACE_WRITE / DANGER_FULL_ACCESS |
harness.extensions.skills.roots |
额外技能目录 | 支持 <name>/SKILL.md 和 <name>.md |
harness.extensions.skills.home |
用户技能根目录 | 留空时回退到 harness.credentials.home |
harness.extensions.mcp.servers |
MCP Server 列表 | 支持 stdio、sse、streamable-http |
harness.extensions.plugins.install-root |
插件安装根目录 | 默认 ./plugins |
harness.extensions.plugins.preset |
启动预装插件 | 支持设置 auto-enable |
harness.agent.temperature |
LLM 采样温度 | 系统属性/环境变量;为空则不发送该参数(适配只接受 temperature=1 的网关) |
GET /api/harness/config/effective |
查看有效配置 | 返回 Spring 环境合并后的配置;API Key、Password、Secret、Token 会被脱敏 |
schema.sql 当前初始化的主要表(共 12 张)包括:
| 表 | 用途 |
|---|---|
harness_session |
Session 聚合基础状态 |
harness_session_header |
会话请求头与上下文折叠状态 |
harness_session_event / harness_session_event_log |
会话与 Agent 过程事件 |
harness_task |
任务提交与执行状态 |
harness_goal_state |
目标状态 |
harness_plugin_installation |
插件安装元数据 |
harness_plugin_runtime_binding |
插件运行绑定信息 |
harness_model_setting |
模型渠道与 Provider 配置 |
harness_tool_catalog / harness_tool_profile_binding |
工具目录与 Profile 绑定 |
MySQL 承载结构化状态和事件(默认 profile);standalone profile 使用 H2 文件库(MODE=MySQL 兼容模式),两者共用同一套 schema,sql.init.mode=always 幂等重跑。
ApiKeyAuthInterceptor支持X-API-Key与Authorization: Bearer ...;harness.auth.api-keys为空时 API 默认放行(本地开发模式),静态资源与/actuator始终放行。guard提供工具超时策略和重复调用提醒。hooks支持生命周期钩子,阻止类 Hook 输出优先。sandbox提供三档模式(READ_ONLY / WORKSPACE_WRITE / DANGER_FULL_ACCESS),本地开发默认WORKSPACE_WRITE——写操作限制在工作区内、shell cwd 不得越界;e2b提供可替换的远程沙箱端口。
生产部署前建议完成以下检查:
- 认证:配置
harness.auth.api-keys,不要将本机服务直接暴露到公网。 - 密钥管理:通过环境变量、启动参数或外部密钥管理系统注入模型 API Key(切勿硬编码进 yml 提交仓库)。
- 审批策略:确认 Shell、文件写入、插件执行和子进程等高风险工具是否必须审批。
- 沙箱边界:限制
cwd、workspaces、plugins、data的目录权限;优先使用独立容器或远程沙箱。 - 数据库:使用外部 MySQL,配置连接池、备份和事件表归档策略。
- 可观测性:接入日志采集、Metrics、Tracing 和关键业务审计。
- 横向扩展:当前设计为单实例 Agent 运行时;多实例部署前需要处理 Agent 内存状态、任务锁和会话亲和性。
- 项目已从早期骨架演进为可运行 Agent Harness,但仍不是承诺企业级 SLA 的完整产品。
- 部分外部能力依赖可选环境:E2B、ACP、MCP、Codex/Claude Code Subagent 等需要对应服务或命令存在。
- Agent 的运行状态和流式执行链路偏向单实例模型;分布式任务调度尚未完整引入。
- API 认证目前是简单 API Key 拦截器,还不是多租户身份体系。
- 运行期审批 gate 默认未启用:审批只在任务提交期拦截(
PermissionCheckNode);AgentRunFactory默认构造时approvalBroker=null,工具执行器走 allowAll,对话链路里的高风险工具不受运行期拦截。 shell_execute零沙箱:LocalShellExecutor直接/bin/sh -c,无命令白名单,仅靠提交期审批兜底;生产环境务必配合审批策略与容器隔离。ScheduleService无驱动方:drive()无@Scheduled/Quartz 调用方,且仓储为 InMemory,重启即丢。- 线程池与阻塞:
ReactLoopAgent.kick()与JobRegistryService用无界newCachedThreadPool;AgentCollectNode用.join()阻塞请求线程,高并发下需关注。 - 长会话性能:
deriveMessages全量重建 O(n²);token 预算用length/4粗估。 - Stub/死路径:E2B 沙箱只有 Stub 实现;CODEX/CORDIS 插件包型识别后 runnable=false;MCP 与插件 Bridge 互不打通;工作流 SSE 只推终态事件。
| 文档 | 内容 |
|---|---|
docs/architecture.drawio |
总体架构图(draw.io 源文件) |
docs/domain-design.md |
领域设计详解:事件风暴、全部事件清单、端口与子域细节 |
docs/domain-modules-reference.md |
领域模块速查:逐个讲解 37 个领域模块的用途、子包分层、关键类、端口、与谁打交道 |
docs/domain-course/ |
领域模块课程(教程分章):9 章逐模块讲解,按"学习目标 / 关键源码 / 设计意图 / 与谁打交道 / 思考题"组织;文件名不带序号,方便后续增删调序 |
docs/draw.io/dsh-java-plugin-integration.drawio |
DSH Java 插件接入架构图:整体架构、对话时序、插件开发与安装流程 |
docs/java-plugin-development.md |
插件开发指南:Archetype 命令、代码模板、HTTP 安装示例 |
docs/plugin-development-tutorial.md |
插件开发实战教程:基于 dsh-java-mysql,从 Tool 编写到 JAR 上传验证 |
docs/plugin-design-implementation.md |
插件设计与实现方案:理论架构、ClassLoader、生命周期、对话时序与安全边界 |