DeepSeek Harness 与 Cordis 微内核架构:从零到一的 Agent 插件开发指南
参考链接:https://deepseek-harness.github.io/deepseek-harness/develop/basic/
目录
- 导言:AI Agent 时代的软件工程挑战
- 底层设计哲学:为什么是 Cordis 微内核?
- 核心机制拆解
- 依赖注入与插件上下文(
ctx) - 生命周期与副作用清理(Fiber 与 Effect)
- 超级事件总线与五种分发模式
- 配置系统与 YAML 动态求值
- Harness 高阶架构设计
- 能力的三层拆分范式(Definition / Provider / Consumer)
- LLM 适配器与 StreamChunk 状态机
- 插件开发实战:从零构建与集成
- 实战 1:定义并注册 Tool 工具
- 实战 2:编写旁路 Log 监听器
- 实战 3:Profile 组合与启动
- 工程化与分发部署
- 组合包(Bundle)与 Profile 机制
- 本地
link与 npm / Git 分发陷阱
- 总结与展望
1. 导言:AI Agent 时代的软件工程挑战
随着大语言模型(LLM)能力的演进,构建简单的 AI 应用已不再具备门槛。然而,当应用场景走向复杂的 AI Agent(智能体) 系统时,开发者往往会遭遇严重的软件工程挑战:
- 能力耦合严重:Tools(工具)、Prompt(系统提示词)、LLM 交互与本地 Shell 执行环境交织在一起,难以测试和独立替换。
- 环境切换困难:开发环境需要在本地终端跑命令,而生产环境需要无缝切到隔离的 Docker 或 Sandbox 中。
- 资源泄漏与状态混乱:热重载(HMR)或动态卸载插件时,残留的定时器、未解绑的事件监听器导致系统内存飙升。
DeepSeek Harness 正是为解决这些痛点而生的 AI Agent 框架。它建立在 Cordis 这个高度解耦、具备严格生命周期管理的 TypeScript 依赖注入微内核之上,引入了高度优雅的插件机制与分发体系。
2. 底层设计哲学:为什么是 Cordis 微内核?
传统的微服务或单体 Agent 框架往往采用强依赖注册机制。而在 DeepSeek Harness 中,所有的扩展能力均以 插件(Plugin) 的形式存在。
Cordis 的核心哲学是 控制反转(IoC)与生命周期感知:
- 控制反转(IoC):插件不需要自己去实例化复杂的依赖服务。你只需要在插件头部静态声明所需依赖(例如
export const inject = ['tools', 'llm']),框架就会确保底层服务就绪后再初始化当前插件。 - 生命周期闭环:每一个插件在运行时都是一个受控的 Fiber(运行节点)。当插件被卸载或重新加载(HMR)时,其产生的每一个副作用(Effect)都会被优雅地“逆序回卷”并清理。
3. 核心机制拆解
3.1 依赖注入与插件上下文(ctx)
在 Harness 插件中,ctx(Context,上下文)是插件访问系统能力的唯一接口。
ctx 是谁创建的?
ctx 由 Cordis 的根容器(Root Context / Loader)在系统启动时创建,并在加载插件时为该插件派生(fork)出一个专属的子 Context 实例传入入口函数:
import type { Context } from '@deepseek-ai/cordis'
export const name = 'my-plugin'
export const inject = ['tools'] // 声明依赖的服务
export function apply(ctx: Context) {
// ctx 是框架自动注入的专属实例
ctx.tools.register(...)
}
ctx 包含的核心能力:
- 服务访问:调用
ctx.tools、ctx.llm或自定义注册的 Service。 - 副作用管理:通过
ctx.effect()手动挂载清理钩子。 - 事件订阅与发布:调用
ctx.on()、ctx.emit()。 - 作用域隔离:使用
ctx.plugin()或ctx.isolate()挂载子插件。
3.2 生命周期与副作用清理(Fiber 与 Effect)
Cordis 内部通过状态机追踪插件的生命周期:
当插件处于 UNLOADING 状态时,框架必须安全释放其占用的资源:
- 原生 Effect 自动清理:通过
ctx.on()监听的事件、通过ctx.tools.register()挂载的工具,在插件销毁时无需手动写解绑代码,框架会自动安全注销。 - 非框架资源清理(
ctx.effect):针对持久性外部资源(如 Socket 连接、setInterval定时器),需要包裹在ctx.effect()中并返回清理回调(Disposer):
ctx.effect(() => {
const timer = setInterval(() => console.log('heartbeat'), 1000)
// 返回的函数会在插件卸载或 HMR 时自动触发
return () => clearInterval(timer)
})
3.3 超级事件总线与五种分发模式
事件机制是 Harness 内部实现“零耦合协作”的桥梁。不同于普通 EventEmitter,Cordis 针对异步与拦截场景提供了 5 种分发模式:
| 模式 | 对应 API | 通俗比喻与运行规则 | 典型应用场景 |
|---|---|---|---|
| 广播(emit) | ctx.emit() |
大喇叭广播:同步触发所有监听器,忽略返回值。 | 日志上报、指标采集 (stats/report) |
| 并发(parallel) | await ctx.parallel() |
小组同步并发:所有人同时开始做,等全部完成才继续。 | 异步并行初始化、多方数据预加载 |
| 短路接力(serial) | await ctx.serial() |
接力棒(异步):按顺序执行,一旦某个监听器返回非空值则直接终止并返回结果。 | 多级缓存查找、策略寻址 |
| 同步短路(bail) | ctx.bail() |
接力棒(同步):逻辑同 serial 但全过程同步。 |
权限拦截(任意一插件否决则中断) |
| 洋葱模型(waterfall) | ctx.waterfall() |
流水线安检:每个听众需显式调用 next() 传递控制权,可拦截或重写数据。 |
请求/响应拦截器、中间件 |
⚠️ Waterfall 核心铁律:只负责观察或记录的
waterfall监听器,**必须显式调用next()**,否则下游的逻辑会被直接截流短路。
3.4 配置系统与 YAML 动态求值
Harness 遵循 Fail-Fast(阻断式报错) 原则,绝不允许插件带病启动。
1. Schema 强类型校验
插件通常使用 Schemastery 声明强类型契约:
import { Schema } from '@deepseek-ai/cordis'
export interface Config {
port: number
}
export const Config: Schema<Config> = Schema.object({
port: Schema.number().default(8080),
})
若用户在 cordis.yml 中传入非法数据类型,Loader 会直接拒绝启动并抛出定位到具体 JSON Path 的错误。
2. !!js 动态求值标签
为了在静态 YAML 中支持动态环境感知,Harness Loader 扩展了自定义 YAML 标签 !!js:
- name: './my-plugin.ts'
config:
greeting: !!js process.env.DEMO_GREETING ?? 'Hello'
disabled: !!js process.platform === 'win32'
- **不加
!!js**:字符串会被解析为普通的文本死代码"process.env.DEMO_GREETING"。 - **加上
!!js**:Loader 加载该文件时会实时执行该 JS 表达式,将计算后的值注入插件。
4. Harness 高阶架构设计
4.1 能力的三层拆分范式
在大模型 Agent 系统中,为了实现底层的“多端无缝切换”,Harness 提出了非常巧妙的 三层拆分架构(DIP 依赖倒置原则的极致应用):
┌───────────────────────┐
│ Service Definition │ (例如 dsh-shell: 定义 Shell 服务契约)
└───────────────────────┘
▲ ▲
│ │
┌──────────────┐ ┌──────────────┐
│ Provider │ │ Consumer │ (例如 dsh-tool-bash: 将 Shell 封装为 Tool 供 LLM 调用)
│(dsh-bash-loc)│ │(dsh-tool-ba) │
└──────────────┘ └──────────────┘
(例如 本地 Exec 实现)
- Service Definition(定义层):仅定义 TypeScript 抽象接口与服务名,不写任何实现。
- Service Provider(提供方):实现该接口。开发环境可以引入
@deepseek-ai/dsh-bash-local(本地执行);生产环境替换为dsh-bash-docker(容器沙箱执行)。 - Consumer / Tool(消费方):将服务包装为模型可见的 Tool,负责描述 Prompt、定义 JSON Schema 及渲染输出结果(Render)。
优势:Consumer 和 Provider 完全解耦。优化底层 Docker 性能或替换 Exec 实现,上层大模型的 Prompt 和工具定义无感;调整提示词,底层安全沙箱逻辑也无需重新测试。
4.2 LLM 适配器与 StreamChunk 状态机
不同的模型厂商(DeepSeek、OpenAI、Anthropic)接口协议各异。Harness 的 LLM 适配器(Adapter) 充当桥梁,统一输出为基于 AsyncIterable 的 StreamChunk:
[block-start (text)] ──> [text-delta] ──> [block-end]
[block-start (tool-call)] ──> [tool-call-delta] ──> [block-end] ──> [finish]
适配器严格要求:
- 不能静默丢弃参数:若底层 API 不支持某字段,必须抛出明确的
LlmError。 - 必须支持取消:必须将
options.signal与底层 HTTP 请求绑定,当 Agent 中断决策时立刻释放网络连接。
5. 插件开发实战:从零构建与集成
下面通过一个完整的案例,展示两个互不相识的插件如何在 Harness 中协同工作。
实战 1:定义并注册 Tool 工具 (greet-tool.ts)
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
import { CallId } from '@deepseek-ai/dsh-llm'
export const name = 'greet-tool'
export const inject = ['tools'] // 注入 tools 服务
export function apply(ctx: Context) {
// 1. 注册打招呼工具
ctx.tools.register(
defineTool({
name: 'greet',
description: 'Greet the named person.',
parameters: {
name: { type: 'string', required: true, description: 'Who to greet' },
},
output: {
schema: { type: 'string' },
render: (_args, value) => [{ type: 'text', text: value }],
},
async execute(args) {
return `Hello, ${args.name}!`
},
})
)
// 2. 模拟 LLM 发起调用
void (async () => {
const result = await ctx.tools.execute({
callId: CallId('demo-1'),
name: 'greet',
arguments: { name: 'Cordis' },
signal: new AbortController().signal,
})
console.log('tool replied:', JSON.stringify(result.content))
})()
}
实战 2:编写旁路 Log 监听器 (tool-logger.ts)
import type { Context } from '@deepseek-ai/cordis'
import type {} from '@deepseek-ai/dsh-tools' // 触发类型合并声明
export const name = 'tool-logger'
export const inject = ['tools']
export function apply(ctx: Context) {
// 监听系统总线上的工具执行结果事件
ctx.on('tools/result', (exec, result) => {
const text = result.content
.map(block => (block.type === 'text' ? block.text : ''))
.join('')
console.log(`[tool-logger] ${exec.name} -> ${text}`)
})
}
实战 3:Profile 组合与启动 (cordis.yml)
在配置中挂载提供方与插件:
- name: '@deepseek-ai/dsh-system-prompt'
- name: '@deepseek-ai/dsh-tools'
- name: './tool-logger.ts'
- name: './greet-tool.ts'
运行单文件启动器:
node --import tsx ../../vendor/cordis/bin.js
输出日志:
[tool-logger] greet -> Hello, Cordis!
tool replied: [{"type":"text","text":"Hello, Cordis!"}]
tool-logger 与 greet-tool 之间没有一行直接的代码依赖,全靠系统的 tools 服务与 tools/result 事件完成了解耦协作!
6. 工程化与分发部署
当插件需要在多台机器或团队内部发布时,使用裸脚本就不够严谨了。Harness 引入了 组合包(Bundle)与 Profile 机制。
6.1 两个 Manifest 概念
根据 打包与安装插件文档,系统中明确分清了两个角色:
- 组合包(Bundle):回答“这个包贡献什么?”。它是一个带
cordis.patch.yml的 npm 包,在其package.json中声明:
{
"name": "dsh-hello-plugin",
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}
- Profile:位于
$DSH_HOME/profiles/<name>下,回答“这套配置由哪些组合包组合而成?”。由 CLI 自动维护。
安装插件到指定 Profile:
dsh plugin --profile demo add ./hello-plugin
6.2 本地 link 与 npm / Git 分发对比
在 Harness 中,不同安装方式的物理与运行机制如下:
[开发模式: link:] ──> 操作系统软链接 ──> 源码保存 ──> 触发 HMR 热重载
[发布模式: npm] ──> Registry 下载 ──> 预构建产物(lib/) ──> 稳定运行
[源码模式: Git] ──> 拉取 GitHub 源码 ──> 触发 prepare 脚本 ──> 需要 allowBuilds 授权
本地软链接 (link:)
- 执行
dsh plugin --profile demo add ./hello-plugin后,package.json中添加"dsh-hello-plugin": "link:..."。 - 修改本地代码保存后,配合 HMR 机制实时生效,无需重新打包。
从 Git 直接安装的“构建陷阱”
若执行 dsh plugin --profile demo add github:you/hello-plugin:
- Git 安装拉取的是源码而非构建产物(没有
lib/输出)。 - pnpm 10 默认会阻止自动执行 prepare 构建脚本。
- 解决方案:必须在 Profile 的
pnpm-workspace.yaml中显式授权:
allowBuilds:
dsh-hello-plugin: true
或者将代码编译后发布到 npm 注册表/提供 .tgz 包,免去构建权限授权。
7. 总结与展望
DeepSeek Harness 通过将 Cordis 的微内核控制反转架构 与 Agent 领域的三层能力拆分理念 相结合,为构建大规模、高鲁棒性的 AI 智能体应用提供了一套极具前瞻性的工程范式。
掌握了 ctx 的依赖注入、生命周期控制、事件流水线以及层级配置组装,你就能像搭积木一样,轻松构建出高复用、易测试、能够适应各种复杂沙箱环境的专业级 AI Agent 系统!