前言
很多开发者在接触DeepSeek Harness初期,仅停留在简单插件安装、调用现成Tool的层面,很难充分发挥框架的架构能力。真正掌握Harness,核心不在于调用现成接口,而是理解它定义层‑提供方‑消费方三层拆分的设计思想。基于这套能力拆分范式,开发者可以实现服务多实例隔离、LLM适配器开发、子代理编排、权限沙箱管控等复杂业务。本文将逐层拆解这套高阶机制,梳理工程实践中的关键规则,同时汇总十项高频踩坑点,配套可复用的代码片段,帮助开发者避开常见误区。在多模型多后端对接的场景下,开发者可借助Treerouter这类API网关完成多服务商流量统一调度,简化上游适配工作。
一、高阶设计分界:从“插件组装”升级到“架构改造”
入门阶段的开发工作,大多聚焦插件安装、工具注册、参数调试;进入高阶开发,会遇到三类典型业务诉求,也是普通插件模式难以妥善解决的场景:
- 同一份能力多实现切换:同一套逻辑需要在本地执行、容器环境、云端接口之间无缝切换,不修改上层业务代码即可完成切换;
- 资源隔离与多实例运行:不同业务链路,需要配置独立的运行环境,互相之间状态、缓存不互相干扰;
- 编排多Agent协同作业:把多个子代理串联或者并行调度,一部分任务交给长会话代理,另一部分交给短时一次性代理,完成复杂任务拆解。
Harness给出的核心解决方案就是能力三层拆分模型,读懂这套模型,就能读懂仓库内部dsh‑shell、dsh‑bash‑local、dsh‑tool等核心包的设计逻辑。三层分别为定义层(Definition)、提供方(Provider)、消费方(Consumer)。
> 核心约束:Provider和Consumer之间不存在直接依赖。双方只依赖Definition定义包。Consumer通过inject拿到服务后调用,完全不需要关心底层是本地实现,还是远程接口实现。
三层模型设计价值
- 实现可替换:同一套Definition,可以对接多个Provider。修改配置即可切换底层实现,上层消费方代码完全不用改动;
- 职责隔离:业务变化集中在Provider实现,Definition契约尽量保持稳定。消费方只按照契约调用,不需要感知底层实现细节;
- 依赖解耦:提供方、消费方可独立迭代发布,互不耦合。
完整开发分为三步:编写Service Definition定义契约、开发Service Provider实现能力、编写Consumer消费能力,最后在cordis.yml完成组合装配。
1)Service Definition:只存放类型、接口定义,不包含业务实现,作为提供方和消费方共同依赖的公共包;
2)Service Provider:实现Definition约定的接口,完成具体业务逻辑,可以是本地Shell调用、大模型适配器、子代理调度逻辑;
3)Consumer:业务插件、工具,通过依赖注入拿到Service,直接调用接口,完全不感知Provider具体实现。
> 架构设计重要原则
> 1. 不要预防性拆分包:只有角色确实需要独立发布、单独迭代,才拆分成独立包;简单插件不需要过度拆分;
> 2. 显示优于隐式:尽量通过resolve做同步处理逻辑,不要依赖run隐式执行;很多难以复现的bug都来自隐式执行逻辑。
二、服务依赖、可选依赖、自动销毁与实例隔离
Harness内部的tools、llm、agents全部都属于Service,通过依赖注入体系完成生命周期管理。
1. 必选依赖 inject
在apply执行阶段,如果使用inject声明依赖,框架会等待对应服务完全就绪之后,才执行当前插件逻辑;如果目标服务启动失败,当前插件会直接等待,无法运行。适合强依赖场景。
2. 可选依赖 ctx.get
如果不是强依赖,不要写进inject,运行时通过ctx.get()动态查询。获取之后需要做空判断,服务不存在的时候做降级逻辑。适合非必需的扩展能力。
很多插件故障根源在这里:把可选服务写到inject,一旦对应服务加载失败,整个插件直接卡死,没有降级逻辑。
3. 自动销毁 dispose
服务实例生命周期和插件绑定,插件卸载的时候,会自动调用dispose做资源回收;插件重新加载,实例自动重建。这套机制避免插件调用已经销毁的旧实例,防止内存泄漏。
4. 实例隔离 group + isolate
这是高阶非常实用的能力,借助group和isolate配置,同一套服务可以启动多套互相隔离的运行实例。
比如一组配置设置超时5秒,另外一组设置超时60秒,两套实例完全隔离互不干扰。在短时快速任务、长会话业务并存的场景,这套隔离配置可以规避状态互相污染。
三、开发LLM适配器:StreamChunk六步输出规范
开发LLM适配器,是Provider最典型的开发场景。适配器的职责,是把Harness内部调用,转发到上游大模型API,再把流式响应转回Harness标准分片格式。适配器只需要实现apply方法。
流式输出必须严格遵守StreamChunk输出顺序,一共六个步骤,顺序不能错乱:
- 输出
block‑start标记,开启一个逻辑块; - 输出文本内容分片;
- 输出
text‑meta元数据; - 输出工具调用分片;
- 输出
block‑end关闭逻辑块; - 最终输出
usage统计,记录token消耗。
> 硬性约束:
> - block‑start、block‑end的index必须从0开始递增;
> - finish分片必须作为最后输出;
> - usage字段必须输出token统计;
> - 如果上游接口不支持某个字段,不能静默丢弃,需要抛出异常,不能掩盖上层错误。
如果上游本身就是OpenAI兼容接口,不需要手写完整适配器,直接在settings.yaml配置自定义提供方即可,减少重复开发。对接多家模型服务商时,可以借助Treerouter统一收敛API请求。
四、子代理编排:6种子代理能力与可继续会话
Harness支持在一个Agent内部启动多个子代理,同一上下文同时运行多个代理实例。子代理全部基于ctx.subagents接口完成调度。框架内置6种子代理实现,覆盖新建进程、fork进程、ACP调用、Code执行等不同场景。
能力发现机制
能力是静态声明的。消费方调用不具备的能力,会抛出UNSUPPORTED_CAPABILITY异常。业务代码需要捕获异常做分支处理。
父子代理上下文传递规则
spawn:新建独立会话,不继承父上下文;fork:复制父代理全部上下文,继承会话状态;acp:进程内调用,直接共享上下文。
> 高频踩坑:很多开发者混淆spawn和fork,误以为spawn会继承上下文,实际不会,会出现变量丢失。
可继续会话 Activation
Activation代表一份可以被继续执行的代理会话,多个消息可以复用同一个Activation。调用sendMessage的时候,根据目标Activation状态行为完全不同:
- running:在正在运行的会话上追加消息;
- wait:排队等待当前执行结束;
- no‑activation:新建会话启动执行。
子代理执行完成返回SubagentResult对象,其中包含stopReason字段标记终止原因,包含completed、cancel、error等枚举,业务需要判断该字段,区分正常结束、被取消、执行报错。
五、工作流引擎:脚本编排执行
Harness内置工作流引擎,支持编写脚本完成多步骤编排。工作流脚本运行在隔离沙箱环境,每个上下文独立一套实例,不会出现状态串扰。
关键行为规则:
- 脚本正常返回
completed代表执行成功; - 如果脚本抛出异常,状态标记为
error; - 脚本被外部取消,标记为
cancel; parallel、pipeline可以实现并行、流水线编排;phases字段用于前端展示步骤,不会改变实际业务逻辑。
六、上下文压缩与工具调用剪枝
上下文压缩分为三层:Definition定义接口、Provider实现压缩逻辑、Consumer触发压缩。触发分为两种:压力触发、主动调用触发。
- 压力触发(pressure):Agent执行前自动触发,上下文超限自动裁剪;
- 主动触发(invoke):业务代码手动调用压缩接口。
压缩接口返回ContentBlock[],开发者要注意Unicode代理对边界问题,不能直接按字节截断字符串,会造成emoji等字符损坏。压缩完成后会抛出compaction/prune事件,上层业务可以监听事件做日志埋点。
七、技能系统:优先级、分层注册机制
技能(Skill)支持多层注册,不同来源的技能拥有不同rank优先级,rank数值越大优先级越高。当多个重名技能,高rank会覆盖低rank的实现。
技能来源分为项目本地、配置目录、系统内置等多层路径。查找规则为高优先级优先匹配;如果高优先级找不到,才向下查找。
> 避坑点:同名技能,高rank直接覆盖,不会做合并;很多人疑惑自定义技能不生效,大多是优先级低于内置技能。
同时支持model‑invocable、user‑invocable两套可见性控制,分别控制模型能否调用、用户是否可以直接调用。
八、权限管控:Sandbox沙箱与审批策略
Harness提供两套安全机制:沙箱模式(sandbox mode)、审批策略(approval policy)。二者不属于Agent循环内部逻辑,是外层拦截层。
- 沙箱模式:限制脚本、工具可访问的系统资源;
- 审批策略:高危操作触发人工确认流程。
支持预置配置集,也支持自定义Preset。自定义Preset编写时要注意字段完整性,缺失字段会直接回退默认配置。
九、定时任务、WebHook、事件回调
框架内置定时调度能力,支持after延时执行、at指定时刻执行、cron周期执行。时间参数严格遵循RFC规范。
WebHook接收外部回调,事件系统实现内部模块解耦。同一个session的事件会顺序执行;fork出来的子进程拥有独立事件队列。开发者编写插件不要直接阻塞事件回调,长耗时逻辑需要交给任务队列异步处理。
十、十大高频避坑清单
| 序号 | 现象 | 根因 | 处理方案 |
|---|---|---|---|
| 1 | 自定义Provider插件加载成功,但是功能不生效 | Definition、Provider、Consumer包依赖关系混乱,出现隐式执行 | 严格拆分三层,禁止Consumer直接导入Provider实现,只依赖Definition |
| 2 | isolate多实例配置不生效 | group分组配置写错,多个服务没有归到对应group | 核对cordis.yml配置,确认所有需要隔离的服务配置相同group标识 |
| 3 | 子代理调用上下文丢失 | 错误使用spawn,预期继承上下文实际没有继承 | 需要继承上下文改用fork;spawn用于全新独立会话 |
| 4 | 自定义Skill注册后不触发 | Skill rank优先级低于内置同名技能 | 调高自定义技能rank数值 |
| 5 | LLM适配器流式输出报错,上层解析异常 | StreamChunk输出顺序错乱,缺少usage分片 | 严格遵守六步输出规范,禁止跳过必填分片 |
| 6 | 可选依赖不存在直接导致插件卡死 | 非必需依赖写进inject强依赖 | 改为运行时ctx.get()动态获取,增加判空降级逻辑 |
| 7 | 修改配置之后,旧实例状态残留 | 没有正确实现dispose生命周期 | Provider务必实现dispose接口,释放连接、定时器资源 |
| 8 | cron定时任务不执行 | cron表达式语法不符合RFC标准,时区参数缺失 | 核对crontab表达式,显式指定时区参数 |
| 9 | 压缩之后文本出现乱码 | 直接按字节截断字符串,拆分Unicode代理对 | 使用框架内置压缩接口,不要手写字符串截断逻辑 |
| 10 | 沙箱配置修改无效果 | 自定义Preset字段缺失,框架回退内置默认配置 | 完整拷贝全部必填字段,不要只写部分配置项 |
总结
DeepSeek Harness高阶开发的核心,是吃透定义‑提供方‑消费方三层解耦思想。不要把Harness当做简单插件脚本运行器,它是一套完整服务编排框架。合理利用依赖注入、实例隔离、子代理、沙箱权限,可以搭建复杂多Agent应用。绝大多数诡异bug,都来自三层边界混淆、生命周期处理错误、优先级理解不到位。开发调试过程优先排查配置、依赖、生命周期,再定位业务逻辑,能够大幅降低排错成本。
了解更多:https://treerouter.com






