从源码视角看 DeepSeek Harness:Cordis 插件系统是如何工作的
从源码视角看 DeepSeek Harness:Cordis 插件系统是如何工作的
🔭 一句话总结:Harness 的灵魂是 Cordis 插件系统——它用”服务注入 + 事件总线”的组合,让所有 Agent 能力在配置层面自由拼装,而无需改一行源码。
先搞清楚几个概念
在深入源码之前,先理清 DeepSeek 给出的几个关键定义:
Model + Harness = Agent
这是 DeepSeek 内部对 Agent 的公式化定义:
- Model(大模型):负责”思考”,做推理、决策、规划
- Harness(运行框架):负责”执行”,做工具调用、上下文管理、任务调度
Cordis 是什么
Cordis 是 Harness 的底层插件元框架,名字来自拉丁语,意为”心脏/核心”。
它的职责极简:
- 加载 / 卸载插件
- 管理插件间依赖
- 提供服务注册与发现
- 事件总线的通信机制
Cordis 不包含任何业务逻辑,所有具体能力都是插件。
Cordis 核心机制:服务与事件
服务(Service)
插件通过 ctx.provide(key, factory) 注册服务,其他插件通过 ctx.get(key) 获取服务。
// 注册一个服务
ctx.provide('fileSystem', async (path: string) => {
return await fs.readFile(path, 'utf-8');
});
// 获取这个服务
const content = await ctx.get('fileSystem')('/path/to/file');
服务支持依赖注入——声明式地声明自己依赖哪些其他服务:
ctx.provide('codeSearch', async (deps) => {
const fs = await deps.get('fileSystem');
const llm = await deps.get('model');
// 使用 fs 和 llm 实现代码搜索
}, ['fileSystem', 'model']);
事件(Event)
插件可以发布事件或订阅事件:
// 订阅事件
ctx.on('agent:task:start', (task) => {
console.log(`Task started: ${task.id}`);
});
// 发布事件
ctx.emit('agent:task:start', { id: 'task-123', type: 'code_review' });
这让插件之间的耦合完全基于事件协议,而非直接调用——插件可以完全不知道彼此的存在,只需遵循同一套事件规范。
插件结构:每个 Cordis 插件由哪些部分组成
interface CordisPlugin {
name: string; // 插件唯一标识
version: string; // 版本号
metadata?: {
description?: string;
author?: string;
tags?: string[];
};
dependencies?: string[]; // 声明依赖的其他插件
// 核心生命周期
register(ctx: CordisContext): Promise<void>; // 注册服务和事件
init(ctx: CordisContext): Promise<void>; // 初始化逻辑
destroy?(ctx: CordisContext): Promise<void>; // 清理资源
}
register vs init 的区别
register:这个阶段插件注册自己的服务,其他插件可以在此阶段依赖它。所有插件的 register 完成后,服务才能被正确解析。init:所有插件 register 完成后调用,此时可以安全地调用其他插件注册的服务。
配置驱动的插件拼装
Harness 的插件组合不是在代码里写死的,而是通过配置文件声明:
{
"mode": "standard",
"plugins": {
"model": "@deepseek-ai/model-deepseek-v4",
"tools": [
"file-system",
"terminal",
"browser",
"code-editor"
],
"skills": ["code-review", "doc-generator"],
"ui": "web"
}
}
切换模式时,只需要改变配置:
{
"mode": "minimal",
"plugins": {
"model": "@deepseek-ai/model-deepseek-v4",
"tools": ["shell", "file-edit"]
}
}
这就是”配置层面自由拼装“的真正含义——改配置表,不用改代码。
Loop 插件:Agent 的循环控制逻辑
Agent 运行时的”是否继续循环”由 Loop 插件控制,这是最有技术含量的插件之一。
interface LoopPlugin {
// 决定是否继续下一轮
shouldContinue(ctx: CordisContext, state: AgentState): Promise<boolean>;
// 决定最大轮次
maxIterations?: number;
// 循环结束时的回调
onComplete?(ctx: CordisContext, result: TaskResult): Promise<void>;
}
Harness 默认提供的 Loop 策略:
| 策略 | 说明 |
|---|---|
| maxIterations | 达到 N 轮后强制退出 |
| converged | 模型输出收敛(连续两轮输出相同)时退出 |
| toolNotFound | 所需工具不存在时退出 |
| costLimit | 达到预设成本上限时退出 |
| humanApproval | 每轮需要人类确认后继续 |
创造模式下,你可以实现自己的 Loop 策略,注册为新插件。
Sandbox 插件:安全隔离的艺术
Agent 执行 shell 命令、运行测试时可能产生危险操作——Sandbox 插件负责隔离执行。
interface SandboxPlugin {
// 执行命令
exec(cmd: string, env?: Record<string, string>): Promise<ExecResult>;
// 文件系统隔离
mount(hostPath: string, sandboxPath: string): void;
// 网络隔离控制
setNetworkAccess(allowed: boolean): void;
// 资源限制
setResourceLimits(cpu: number, memory: number): void;
}
DeepSeek 官方的 Sandbox 实现支持:
- 进程级隔离:命令在独立进程运行,超时自动 kill
- 文件系统白名单:只允许访问特定目录
- 网络访问控制:可完全禁用网络或仅允许特定域名
- 资源配额:CPU 时间、内存上限
Scheduler 插件:多 Agent 任务调度
多 Agent 协作时,谁来执行哪一步由 Scheduler 插件决定:
interface SchedulerPlugin {
// 决定下一个由哪个 Agent 执行
selectNextAgent(agents: Agent[], task: Task): Promise<Agent>;
// 决定任务并行还是串行
planExecution(tasks: Task[]): Promise<ExecutionPlan>;
}
内置的调度策略:
- round-robin:轮询分配
- capability-based:根据 Agent 能力标签匹配任务
- priority-based:按优先级排队
- dependency-based:根据任务依赖关系构建 DAG
源码目录结构(基于项目 README 推断)
deepseek-harness/
├── packages/
│ ├── cordis/ # 插件元框架核心
│ │ ├── src/
│ │ │ ├── context.ts # CordisContext(服务+事件核心)
│ │ │ ├── plugin.ts # 插件接口定义
│ │ │ ├── loader.ts # 插件加载器
│ │ │ └── events.ts # 事件总线实现
│ │ └── package.json
│ │
│ ├── dsh/ # CLI 主入口
│ │ ├── src/
│ │ │ ├── commands/
│ │ │ │ ├── web.ts # npx @deepseek-ai/dsh web
│ │ │ │ ├── tui.ts # npx @deepseek-ai/dsh tui
│ │ │ │ └── headless.ts
│ │ │ └── index.ts
│ │ └── package.json
│ │
│ ├── plugins/ # 官方插件集
│ │ ├── model-deepseek-v4/
│ │ ├── model-claude/
│ │ ├── tool-file-system/
│ │ ├── tool-terminal/
│ │ ├── tool-browser/
│ │ ├── tool-code-editor/
│ │ ├── skill-code-review/
│ │ ├── skill-doc-generator/
│ │ ├── sandbox-default/
│ │ ├── loop-default/
│ │ └── scheduler-default/
│ │
│ └── ui/ # 交互界面插件
│ ├── ui-web/
│ ├── ui-tui/
│ └── ui-headless/
│
├── README.md
├── LICENSE (MIT)
└── package.json
插件开发:写一个自定义 Model 插件
假设你想让 Harness 接入 Claude 模型,而不是默认的 DeepSeek-V4:
// model-claude.ts
import { CordisPlugin, CordisContext } from '@deepseek-ai/cordis';
export const claudeModelPlugin: CordisPlugin = {
name: 'model-claude',
version: '0.1.0',
async register(ctx: CordisContext) {
ctx.provide('model', async (deps) => {
const { Anthropic } = await import('@anthropic-ai/sdk');
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
return {
name: 'claude-3-5-sonnet',
async complete(prompt: string, options?: any) {
const response = await client.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: options?.maxTokens ?? 4096,
messages: [{ role: 'user', content: prompt }]
});
return response.content[0].text;
}
};
});
},
};
然后改配置:
{
"mode": "standard",
"plugins": {
"model": "model-claude" // 替换成你的插件
}
}
整个框架无需修改,模型就这么换了。
Cordis 的设计启示
Cordis 插件系统的设计借鉴了现代后端框架的成熟经验:
- Angular 的依赖注入:服务延迟实例化 + 声明式依赖
- Node.js 的 EventEmitter:事件驱动解耦
- Docker 的插件模型:插件是独立可替换的运行单元
这种设计让 Harness 在保持核心稳定的同时,获得了无限的可扩展性——这正是 MIT 协议 + 插件架构组合在一起的深意。
相关链接
- GitHub:
https://github.com/deepseek-ai/deepseek-harness - npm:
@deepseek-ai/dsh - Cordis(框架源码):
https://github.com/deepseek-ai/cordis
- 🔭 本文基于项目 README、技术媒体报道及源码结构推断整理,正式 API 以官方文档为准。*
评论区
登录后可评论。