我把 DeepSeek Harness 源码跑了一遍,终于看懂了它的“一切皆插件”

DeepSeek Harness 当前仍处于 Developer Preview,本文基于 0.1.0-rc.5

最近 DeepSeek 开源了一个叫 DeepSeek Harness 的 Agent 框架。

看到 Harness 这个词,很多人的第一反应可能是:又一个封装大模型、注册几个 Tool、再套个聊天页面的框架。

我一开始也是这么想的。

但把源码拉下来、跑完构建,再顺着 packages/corepackages/bundleapps/cli 看了一遍之后,我发现它真正想解决的不是"怎么调用模型",而是另一个更麻烦的问题:

当 Agent 开始拥有文件系统、Shell、权限、会话、子 Agent 和后台任务之后,怎么避免整个系统变成一团无法替换的胶水代码?

DeepSeek Harness 给出的答案是:把整个 Agent 产品拆成插件。

不是"支持插件",而是"一切皆插件"。

Agent Loop 也不是内核

传统 Agent 框架通常都有一段地位特殊的主循环:

text 复制代码
接收消息
→ 调用模型
→ 解析 Tool Call
→ 执行工具
→ 把结果交还模型
→ 继续下一轮

模型、工具、历史记录和权限逻辑会逐渐堆进这段循环。刚开始很好理解,后面每增加一种能力,都要在主流程里加新的条件分支。

DeepSeek Harness 没有把 Agent Loop 放在不可替换的位置。

模型适配器、工具注册表、会话日志、权限策略、文件系统,甚至 Agent Loop 本身,都是挂载在 Cordis 上的插件。它们通过共享上下文 ctx 协作,而不是互相引用具体实现。

text 复制代码
Cordis Context
├── ctx.sessions
├── ctx.tools
├── ctx.llm
├── ctx.agents
├── ctx.agentLoop
├── ctx.fs
├── ctx.subprocess
├── ctx.sandbox
└── ctx.jobs

这带来一个很直接的结果:新增能力时,默认动作不再是修改 Agent Loop,而是找到对应扩展点,挂载一个新插件。

例如:

  • 新模型注册到 ctx.llm
  • 新工具注册到 ctx.tools
  • Shell 换成本地或沙箱 Provider
  • 请求拦截使用 agent/* 事件
  • 工具审批使用 tools/* 事件
  • 后台任务注册到 ctx.jobs

只有现有扩展点真的无法表达需求时,才需要碰 Agent Loop。

Cordis 管的不只是依赖注入

Cordis 是 Harness 的底层插件框架。

一个最小插件只有几行:

ts 复制代码
import type { Context } from '@deepseek-ai/cordis'

export const name = 'hello-plugin'

export function apply(ctx: Context) {
  console.log('plugin loaded')
}

如果插件依赖工具注册表,需要显式声明:

ts 复制代码
export const inject = ['tools']

export function apply(ctx: Context) {
  ctx.tools.register(/* ... */)
}

Cordis 会等到 tools 服务可用后再加载插件。这不只是方便获取依赖,它同时解决了启动顺序和生命周期问题。

更关键的是副作用可逆。

ts 复制代码
export function apply(ctx: Context) {
  ctx.effect(() => {
    const timer = setInterval(() => {
      console.log('heartbeat')
    }, 5000)

    return () => clearInterval(timer)
  })
}

插件卸载时,Cordis 会执行清理函数。事件监听、工具注册和通过上下文创建的能力也会随插件一起撤销。

这正是 Harness 能支持热更新和配置重组的基础。否则每次修改配置都重新加载插件,旧定时器、事件监听器和工具注册会不断残留。

Profile 不是配置文件,而是一套 Agent 产品

Harness 启动的不是某个固定应用,而是一个 Profile。

一个运行中的 Profile,本质上是一棵由多层配置组合出来的插件树:

text 复制代码
Bundle 列表
→ Profile 自己的 cordis.patch.yml
→ $DSH_HOME/cordis.patch.yml
→ 命令行传入的 --patch

内置的 dsh-base 提供模型、工具、持久化、沙箱、审批和凭据等基础能力。

dsh-web-app 在它上面增加 Web 服务和前端界面;dsh-headless 则增加一次性任务执行器,不启动 Web 服务。

所以 Web 和 Headless 并不是两套独立实现,而是两种插件组合。

查看当前机器最终运行了什么,不需要猜:

bash 复制代码
dsh --profile web --dump-config

这条命令会打印组合后的配置树。

需要注意的是,Patch 替换的是目标行的整个 config,不是深度合并。覆盖已有插件配置时,必须把需要保留的字段一起写回来。

真正值得研究的是 Capability Seam

Harness 的包很多,但它并不是单纯把大模块拆成小包。

一项需要替换实现的能力,通常会被拆成三种角色:

text 复制代码
Service Definition
        ↓
Service Provider
        ↓
Consumer

以 Shell 为例:

  • Definition 定义执行请求和结果
  • Provider 决定在本机、远端还是沙箱里执行
  • Consumer 把它包装成 Bash、PowerShell 或终端 Tool

Tool 不应该知道进程到底在哪里运行。

这使得文件系统与进程 Provider 切换到远程沙箱后,Bash、PTY 和 LSP 可以整体迁移,不需要为每个 Tool 单独维护远程版本。

当然,这种设计不是免费的。

它会带来更多包、更长的依赖图,以及更高的理解成本。如果某项能力只有一个实现,也不会被复用,更不需要隔离,那么硬拆 Definition、Provider 和 Consumer 只是在制造形式主义。

Harness 的做法更适合那些真正需要替换运行环境的能力。

会话日志才是系统的事实源

我认为 Harness 最重要的约束不是插件,而是这句话:

模型可见,即已记录。

Harness 不把一个可变的 messages[] 当成会话本身,而是维护仅追加的 SessionEvent 日志。

text 复制代码
SessionEvent Log
├── 投影成模型历史
├── 渲染到 Web UI
├── 持久化到 JSONL 或 SQLite
├── 支持恢复与 Fork
└── 生成 Transcript 和遥测

这解决了 Agent 系统中一个很常见的问题:运行时给模型偷偷塞了上下文,但恢复会话时无法重建;或者页面显示了某个状态,模型却不知道它存在。

在 Harness 里,如果新内容会进入模型请求,就不能只在 agent/request 中临时拼接。它必须成为会话事件,并支持从日志重新投影。

代价是开发更严格,但换来的是可恢复、可回放和可审计。

一次 Turn 到底发生了什么

Harness 把一次模型请求加工具执行称为 Step,一次 Turn 可以包含多个 Step。

简化后的流程如下:

text 复制代码
turn/start
→ 领取输入
→ agent/pre-step
→ 组装提示词与 Tool Schema
→ step/start
→ 从日志派生模型历史
→ agent/request
→ llm/stream
→ assistant/message
→ tool/call
→ tools/pre-execute
→ tools/execute
→ tools/post-execute
→ tool/result
→ step/end
→ turn/end

其中 agent/requestllm/streamtools/* 的部分事件采用 Waterfall 机制。

监听器必须调用 next()

ts 复制代码
ctx.on('tools/pre-execute', async (request, next) => {
  validateRequest(request)
  return next()
})

漏掉 next() 不是什么都没做,而是直接截断后续处理链。开发策略插件时,这一点很容易踩坑。

它适合什么项目

如果只是做一个调用模型、执行几个固定函数的小应用,DeepSeek Harness 可能太重。

它更适合下面这些场景:

  • 同一套 Agent 需要 Web、Headless、ACP 等多种入口
  • 文件系统和进程需要在本地、沙箱、远端之间切换
  • 需要持久会话、恢复、Fork 和审计
  • Tool 执行需要权限、审批和策略拦截
  • 不同 Agent 需要不同能力集合
  • 希望第三方扩展能力,但不想 Fork 主仓库

DeepSeek Harness 最值得借鉴的地方,并不是"用了插件架构"。

而是它把 Agent 的运行状态、能力边界和副作用生命周期都变成了可以组合、替换和验证的对象。

这比再封装一层模型 API 难得多,也更接近真正的 Agent 工程。

相关推荐
kyriewen1 小时前
DeepSeek Harness开源第一天我就上手了——和Claude Code的差距比想象中大
前端·ai编程·deepseek
张彦峰ZYF1 小时前
LangGraph 深入理解 ReAct:让 AI Agent 真正学会「边想边做」
人工智能·llm·agent·react·langgroup
梦想很大很大1 小时前
如果有一个本地优先的 Workflow 工具,你们团队会愿意用吗?
python·agent·workflow
302wanger2 小时前
聊天记录翻到烦,我让 DeepSeek Harness给自己写了个插件
deepseek
阿里云大数据AI技术2 小时前
AI Search+ES 9.4.X最佳实践:“更快、更准、更安全的企业级搜索引擎”"为AI Agent提供坚实底座”
人工智能·elasticsearch·agent
特立独行的猫A2 小时前
DeepSeek Harness(dsh)插件开发实战:从零实现一个会话导出插件
deepseek
alwaysrun2 小时前
AI Agent之执行中幻觉问题与应对方案
人工智能·agent
princed3 小时前
在 Claude Code / Codex / Pi 里用 DeepSeek,怎么让它看见图?
ai编程·deepseek
会飞的胖达喵3 小时前
MCP 协议的前世今生:从 AI 的 USB-C 到无状态 Agent 基础设施
agent·mcp