开源即王炸:DeepSeek Harness一天83k星

昨天 DeepSeek 最重磅的消息并不是 DeepSeek-V4-Pro-0813 模型上线,而是 DeepSeek Harness(下文简称 dsh )的开源亮相。从官宣发布到本文发布时间为止,一天多的时间 Github 已经 83k Star,热度还在持续上升中。

DeepSeek 官方对 DeepSeek Harness 的定义如下:

DeepSeek Harness(dsh)是由 DeepSeek AI 开发的开源 agent harness(智能体框架)。

它采用一切皆插件的架构,并由 Cordis 驱动,其设计参见论文 A Programming Paradigm for Spatiotemporal Composability。

如果说 Claude Code、Codex是一辆整车,那么 dsh 就相当于提供了"底盘+发动机+变速箱+一套组装工具",用户完全可以用它自己去造一辆车。

对于正在研究 Agent Harness 的我而言,dsh 的开源无异于雪中送炭,第一时间我就开始学习 dsh 的源码。

1 零件清单:dsh 的包目录到底有什么

造车类比 dsh 组件 对应包
发动机 LLM 适配层 packages/llm/
变速箱 Agent 循环驱动 packages/core/agent-loop/
底盘 Cordis 插件框架 vendor/cordis/
方向盘 工具注册表 + 执行流水线 packages/core/tools/
仪表盘 Web UI apps/web/(React + Vite)+ packages/host/ + packages/client/
安全带+气囊 沙箱隔离 packages/sandbox/
行车记录仪 会话日志(Session Log) packages/core/session/ + packages/session/
导航系统 LSP 集成 packages/lsp/
油门刹车 Shell/终端能力 packages/shell/ + packages/terminal/
后视镜 子代理委派 packages/subagent/
改装接口 自修改引擎 packages/extensions/
行车电脑 Plan 模式 + 工作流 packages/plan/ + packages/workflow/

每个包都是独立可替换的零件

用户不需要全部用上,按需组装

所有零件通过 Cordis 的 inject 声明依赖,自动接线

npm scope 统一为 @deepseek-ai/dsh-*

2 组装手册:Profile + Bundle 怎么拼

这是"造车"的核心环节--选零件、组装、上路。

2.1 两个核心概念

Bundle = 一套预选好的零件包

dsh-base:所有车都要有的基础件(模型适配器、工具、持久化、沙箱、审批策略、设置、凭证、遥测)

dsh-headless:无人驾驶模式(一次性任务,无 server)

dsh-web-app:带仪表盘的完整车(浏览器应用)

Profile = 你的车型配置单

存放在 $DSH_HOME/profiles/<name>/

列出要用哪些 Bundle,按什么顺序叠

可以打补丁覆盖任意配置

2.2 组装顺序

复制代码

空根(光秃秃的底盘)

-> bundle: dsh-base (装上发动机、接线)

-> bundle: dsh-web-app (装上仪表盘、座椅)

-> profile patch (你定的颜色、配置)

-> home patch (你家车库的默认设置)

-> --patch(命令行) (临时改装,试驾用)

2.3 三步造车

复制代码

# 第一步:用默认配置跑起来,看看啥样

npx @deepseek-ai/dsh web

# 第二步:创建你自己的 Profile

dsh --profile my-agent # 首次使用自动创建

# 第三步:往你的车上加零件

dsh plugin --profile my-agent add @my-org/custom-tools

打开 packages/bundle/base/cordis.patch.yml,看看 dsh-base 这个"基础件包"到底装了什么:

复制代码

- insert:

- id: llm

name: '@deepseek-ai/dsh-llm'

- id: session

name: '@deepseek-ai/dsh-session'

- id: agent

name: '@deepseek-ai/dsh-agent'

- id: agent-default-model

name: '@deepseek-ai/dsh-agent-default-model'

config:

provider: deepseek-official

model: deepseek-v4-flash

- id: sandbox

name: '@deepseek-ai/dsh-sandbox-local'

- id: tool-bash

name: '@deepseek-ai/dsh-tool-bash'

disabled: !!js process.platform === 'win32'

- id: tool-fs

name: '@deepseek-ai/dsh-tool-fs'

- id: agent-loop

name: '@deepseek-ai/dsh-agent-loop'

config:

agents: []

每一行就是一个插件条目:id 是你在 patch 里引用它的名字,name 是 npm 包名,config 是传给插件的配置。!!js 表示内联 JavaScript 表达式--比如 tool-bash 在 Windows 上自动禁用。

想换模型?把 agent-default-modelconfig.model 改掉。想加搜索?插一行 tool-web。想换沙箱后端?把 sandboxname 换成远程沙箱的实现包。改配置,不改代码。

3 为什么 Cordis 是底盘

官方文档对 Cordis 的描述是:

Cordis 是 dsh 底层的框架:插件向共享上下文贡献服务、类型化事件和可逆的副作用。产品的每一部分都是插件,包括模型适配器、工具注册表、会话日志,以及 agent loop(智能体循环)本身,因此每一部分都可以从配置替换。

不存在需要打补丁的特权内核:扩展 dsh 的方式是把插件挂载到其他插件旁边,而各项注册都是副作用,会在其插件卸载时撤销。

正如官网 Slogan 所说:"一切皆插件",从插件目录可以看出,整个 dsh 基本就是基于 Cordis 用插件实现的。

通过分析代码,我总结如下:

插件 = Service 对象:每个插件提供一个服务,通过 inject 说"我需要谁"

Context = 服务仓库:所有服务挂在 ctx 上,ctx.fs 就是文件系统,ctx.llm 就是模型

自动接线:你不用管先装发动机还是先装变速箱,Cordis 自动解析依赖

类型化事件通信:四种模式(emit 广播 / waterfall 瀑布 / parallel 并行 / serial 串行)

可逆注册:插上就通电,拔了就断电,自动清理,不留垃圾

同时,通过三个角色的分离,实现了换"零件"不用改"线路":

角色 职责 例子
Service Definition 接口声明 ctx.fs 能力定义
Service Provider 具体实现 本地 FS / 远程 Sandbox FS
Consumer 使用方 Bash 工具、LSP 工具

比如把 ctx.fs 从本地文件系统换成远程沙箱, Bash、PTY、LSP 工具全部自动指向远程。就像用户想把轮胎从公路胎换成雪地胎,ABS、ESP、仪表盘全部自动适配。

并不是空口无凭,直接看代码:

Service Definitionpackages/fs/fs/src/index.ts)------定义接口,不写实现:

复制代码

declare module '@deepseek-ai/cordis' {

interface Context {

fs: FileSystem

}

}

export abstract class FileSystem extends Service {

constructor(ctx: Context) {

super(ctx, 'fs')

}

abstract resolve(path: string, opts?: { cwd?: string }): Promise<FsTarget>

abstract readText(target: FsTarget, signal?: AbortSignal): Promise<string>

abstract writeText(target: FsTarget, content: string, expected?: FsWriteIntent): Promise<FsWriteOutcome>

abstract editText(target: FsTarget, edit: FsEditRequest, expected?: { version: FsVersion }): Promise<FsEditOutcome>

// ...

}

通过 declare moduleFileSystem 注册到 ctx.fs,是个抽象类------只声明方法签名,不写实现。

Service Providerpackages/fs/fs-local/src/index.ts)------本地文件系统实现:

复制代码

export class LocalFileSystem extends FileSystem {

static Config = z.object({

cwd: z.string().default(process.cwd()),

})

override async writeText(

target: FsTarget,

content: string,

expected?: FsWriteIntent,

): Promise<FsWriteOutcome> {

return this.withLock(target.targetKey, async () => {

const existing = await probe(target.targetKey)

if (expected?.kind === 'replaceIfVersion') {

if (existing.version !== expected.version) {

throw new FsError(`file changed since it was read`, 'FS_STALE_VERSION')

}

}

await writeFileAtomic(target.targetKey, content, existing?.mode)

return { operation: existing ? 'update' : 'create', version: ... }

})

}

}

LocalFileSystem 继承 FileSystem,用 Node.js 原生 fs API 实现每个方法。注意 withLock 保证了 read-guard-write 的原子性,expected.version 是乐观锁防陈旧写入。想换成远程沙箱实现?写一个 RemoteSandboxFileSystem extends FileSystem 就行。

Consumerpackages/fs/tool-fs/src/index.ts)------模型面向的工具:

复制代码

export const inject = ['tools', 'fs', 'systemPrompt']

export function apply(ctx: Context, config: Config): void {

applyReadTool(ctx, { limit: config.readLimit, ... })

applyWriteTool(ctx, sandbox)

applyEditTool(ctx, sandbox)

}

工具包通过 inject = ['tools', 'fs', 'systemPrompt'] 声明"我需要这三个服务",然后注册 read/write/edit 工具。它不直接操作文件,而是调用 ctx.fs--至于 ctx.fs 背后是本地还是远程,它不关心。

4 上路试驾:Agent 循环的完整流程

车造好了,是时候点火跑一圈看看,dsh 的流程如下:

复制代码

turn/start(点火)

claim next-step input + one queued message(踩离合挂挡)

-> agent/pre-step reject | enter

reject, or first enter rewritten empty -> close the turn with no step

step/start

assemble prompt sections + tool schemas(组合油门信号)

append entered messages as user/message

derive model history from the log(看后视镜,回顾路况)

agent/request -> llm/stream -> assistant/chunk* -> assistant/message(发动机输出动力)

tool/call* -> tools/pre-execute -> tools/execute -> tools/post-execute -> tool/result*(变速箱换挡)

step/end

tools owe another request, or next-step input arrived -> claim -> next step

-> agent/turn-stopping(到站刹车)

turn/end(熄火)

看源码更直观。packages/core/agent-loop/src/agent.ts 里的 turn() 方法,就是上面那个流程的代码实现:

复制代码

private async turn(): Promise<boolean> {

const { signal } = phase.abort

const turn = phase.turn + 1

this.session.append('turn/start', { turn })

while (true) {

signal.throwIfAborted()

const step = phase.step + 1

// 1. pre-step:claim inbox 消息,决定是否进入

const decision = await this.preStep(target, { turn, step })

if (decision.kind === 'reject') {

turnEnds = { kind: 'blocked' }

return false

}

// 2. step/start -> 组装 prompt -> 调 LLM -> 执行工具 -> step/end

this.session.append('step/start', { turn, step })

for (const message of decision.messages) {

this.session.append('user/message', message)

}

const stepEnd = await this.step(decision.assembly)

this.session.append('step/end', { turn, step })

// 3. 检查是否该结束 turn

if (turnEnds && this.inbox.nextStep.length === 0) {

await this.dispatch.serial('agent/turn-stopping', { turn, signal })

}

if (turnEnds && this.inbox.nextStep.length === 0) break

target = 'next-step'

}

this.session.append('turn/end', { turn, reason: turnEnds })

return true

}

整个 turn 就是一个 while (true) 循环:pre-step 拿消息 -> step 执行 -> 检查结束条件。每一步都往 this.session 里 append 日志,这就是 Session Log 作为"唯一真相源"的来历。

step() 方法内部长这样:

复制代码

private async step(assembly: PromptAssembly): Promise<StepEndReason | null> {

const system = renderPrompt(assembly)

while (true) {

// 从 session log 投影出模型历史

const { request } = await this.buildRequest(

turn, step, assembly.tools, system,

this.session.deriveMessages(), // <-- 关键:从日志重建

signal,

)

// 流式调 LLM

const stream = this.loopCtx.llm.stream(request)

for await (const chunk of stream) {

this.session.append('assistant/chunk', { turn, step, chunk })

assembler.push(chunk)

}

// 处理工具调用

const message = createAssistantMessage({ content: assembler.blocks() })

// ... tool/call -> execute -> result

}

}

this.session.deriveMessages() 是关键------模型看到的对话历史不是内存里维护的,而是每次都从 Session Log 重新投影出来的。这意味着你可以随时 fork 一个 session,换个模型继续跑,历史完全可以重建。

值得注意的几个设计:

日志是唯一真相源,模型看到的任何东西都必须从 Session Log 重建,deriveMessages() 从日志投影出模型历史。

三种插队方式:

followup():下一轮再说(红灯排队)

steer():立即插队(紧急变道)

inject():悄悄塞进去但不唤醒(副驾递水)

并行换挡:默认最多 10 个工具并行执行,maxParallelToolCalls 可配,设 1 则串行

Waterfall 事件:agent/pre-step、agent/request、llm/stream、tools/* 为瀑布流,listener 必须调用 next() 委托;agent/turn-stopping 为 serial,无 next()

5 改装车间:自修改能力------Agent 能改自己的车

这个是我目前看到的最疯狂的设计:已经上路在开了,车子能自己换零件。

当 Agent 发现需要搜索能力的时候,自己挂载 web 搜索插件;

当 Agent 发现某个工具在浪费 token,自己卸载它;

当 Agent 遇到新文件类型时,自己挂载对应的 LSP 。

这个逻辑并不是预先加载所有零件给 Agent 挑选,也不是"配置文件热更新",是 Agent 在运行过程中的自我重构,到目前为止,这个是大多数 Agent 框架想都没想过的方向。

packages/extensions/ 提供 4 个子包:

tool-cordis/:模型面向的运行时检查工具

cordis-host-runner/:host 端动态包执行

cordis-client-runner/:browser 端动态包执行

ui-cordis/:浏览器界面

packages/extensions/tool-cordis/src/index.ts 的源码,Agent 能调用 6 个工具:

复制代码

export const inject = ['tools', 'systemPrompt', 'dynamicCordisRunner', 'cordisInspect']

export function apply(ctx: Context): void {

// 1. 自省:查看当前运行时里有哪些插件和服务

ctx.tools.register(defineTool({

name: 'cordis_inspect_list',

description: 'List every Cordis Inspect Provider currently known to the Host...',

parameters: {},

execute() {

return Promise.resolve({ providers: ctx.cordisInspect.list() })

},

}))

// 2. 自省:查看某个插件的详细信息

ctx.tools.register(defineTool({

name: 'cordis_inspect_self',

description: 'Inspect dynamic Cordis objects owned by the current Session...',

parameters: { pluginId: { type: 'string' }, packageId: { type: 'string' } },

execute(args, exec) {

// 返回插件 / 包的详细信息

},

}))

// 3. 造零件:定义一个新的动态插件

ctx.tools.register(defineTool({

name: 'cordis_define',

description: 'Define an immutable Cordis Package...',

parameters: {

name: { type: 'string', required: true },

purpose: { type: 'string', required: true },

code: { type: 'object', properties: { host: { type: 'string' }, client: { type: 'string' } } },

},

execute(args, exec) {

const receipt = ctx.dynamicCordisRunner.define({

sessionId: requireAgent(exec).id,

plugin: args.plugin,

name: args.name,

purpose: args.purpose,

code: { ... },

})

return Promise.resolve({ pluginId: String(receipt.pluginId), packageId: String(receipt.packageId) })

},

}))

// 4. 装上去:激活一个动态插件

ctx.tools.register(defineTool({

name: 'cordis_run',

description: 'Activate one exact Package of a dynamic Plugin...',

parameters: { pluginId: { type: 'string', required: true }, packageId: { type: 'string', required: true } },

async execute(args, exec) {

await ctx.dynamicCordisRunner.run(

requireAgent(exec),

CordisDynamicPluginId(args.pluginId),

CordisDynamicPackageId(args.packageId),

args.mode, exec.signal,

)

},

}))

// 5. 拆下来:停止一个动态插件

ctx.tools.register(defineTool({

name: 'cordis_stop',

description: 'Stop the current Run of a dynamic Plugin...',

parameters: { pluginId: { type: 'string', required: true } },

async execute(args, exec) {

await ctx.dynamicCordisRunner.stop(requireAgent(exec), CordisDynamicPluginId(args.pluginId))

},

}))

// 6. 扔掉:永久删除一个动态插件

ctx.tools.register(defineTool({

name: 'cordis_undefine',

description: 'Permanently remove a dynamic Plugin owned by the current Session...',

parameters: { pluginId: { type: 'string', required: true } },

async execute(args, exec) {

await ctx.dynamicCordisRunner.undefine(requireAgent(exec), CordisDynamicPluginId(args.pluginId))

},

}))

}

6 个工具,对应"看、造、装、拆、扔"五个动作。Agent 先用 cordis_inspect_* 看清楚自己当前有哪些零件,再用 cordis_define 写一段 JS 代码定义新插件,用 cordis_run 挂载上去,不需要了用 cordis_stop 停掉,最后 cordis_undefine 彻底删除。

最后

目前 dsh 的版本号是 0.1.0-rc.5,还处在开发者预览阶段,官方也明确提示:"未来将出现破坏兼容性的变更"。现在就拿来"造车"可能还稍微早了点,但是这个框架本身的很多思路和设计确实是很值得学习和借鉴的。

相关推荐
您^_^1 天前
DeepSeek-Harness 升级排障完全指南:三类本地残留逐个拆解
人工智能·windows·个人开发·deepseekharness·deepseekv4pro
deephub2 天前
DeepSeek Harness 架构解析:从 Preset、Tool Pipeline 到 Agent Runtime
大语言模型·ai agent·agent runtime·deepseekharness·cordis
学Linux的语莫4 天前
告别命令行:把 DeepSeek Harness 变成可以双击启动的桌面应用
deepseekharness
向上的车轮6 天前
DeepSeek Harness:用 Creator 模式开发自定义插件,从 0 到可调用
人工智能·deepseekharness
番茄不是西红柿kk12 天前
deepseek-harness跨平台桌面端二开项目(附git仓库地址+安装包)
git·agent·codex·deepseek·deepseekharness
comedate12 天前
DeepSeek Harness 配置 deepseek-v4-flash-vision-exp 视觉模型
agent·skill·deepseekharness
ss27313 天前
小鲸鱼长眼睛了!DeepSeek Harness v0.1.1-rc.1 正式上线,多模态视觉模型来了
deepseek·deepseekharness
北漂燕郊杨哥14 天前
DeepSeek Harness 工具执行流水线:一次工具调用的守卫、执行与定格
tool·deepseek·deepseekharness
梦想的颜色15 天前
DeepSeek‑Harness 插件系统源码级拆解:热插拔、自动装卸原理与自研 Web 插件架构落地指南
架构·deepseekharness·cordis内核·可插拔架构·web框架自研·后端架构设计·插件系统源码