Harness 是什么:从安装到跑通第一个任务
前段时间我用 Claude Code 写一个小工具,写得正爽,突然想试试 DeepSeek 的模型——结果发现换模型比换工作还麻烦。配置文件翻了半天,各种 endpoint、key、参数改了一通,改完还不确定会不会把之前的配置搞坏。那一刻我就想,这帮做 Agent 框架的,到底有没有考虑过"我就是想换个模型试试"这种朴素需求?
后来朋友丢给我一个链接,说你看看 DeepSeek Harness。我一开始以为又是个套壳的 Coding Agent,没太当回事。直到折腾了一圈才发现,这东西的思路跟 Claude Code、Codex 都不太一样——它不是又一个 Agent,它更像是一个"装 Agent 的底座"。
先说清楚,Harness 到底是个什么东西
DeepSeek 官方给了个公式:Agent = Model + Harness。我第一次看到的时候觉得这不是废话吗,哪个 Agent 框架不是模型加工具?但用了一阵才明白,这个公式真正想说的是分工——模型负责想,Harness 负责在真实环境里把活干成。
这跟我以前用 AI SDK 的感觉完全不一样。用 SDK 的时候,我是主控方,我调它的 API,我决定什么时候请求模型、什么时候调工具。但 Harness 反过来了——你写插件挂进运行时,运行时在每轮对话里反过来调你的插件。你不是在"用"它,你是在"扩展"它。
这个心智转变一开始挺别扭的。但理解了之后你会发现,它的所有设计都在围绕一个核心转:一切皆插件。
Harness 的底层是一个叫 Cordis 的微内核,这内核薄到什么程度呢?DeepSeek 把 Cordis 源码整个拷进自己仓库,就改了 18 处。18 处修改,一个通用插件框架就变成了 Agent 运行时。内核本身只干三件事:挂载插件、卸载插件、管理依赖关系。剩下的所有能力——模型适配、工具注册、会话日志、Agent 主循环、系统提示词、沙箱执行、UI——全是插件,挂在内核旁边。
画张图可能更直观:

这意味着什么?意味着没有任何零件是焊死的。你不喜欢默认的工具权限策略?换掉。想接一个国产模型?加个插件就行。连 Agent 主循环本身都能替换。我第一次意识到这一点的时候,脑子里冒出一个画面:以前用的那些框架像是品牌整机,想换个显卡都得看厂商脸色;Harness 像是一个主板和机箱都给你敞开的台式机,想插什么插什么。
还有一件事让我印象很深。Harness 的会话日志是 append-only 的,模型能看到的所有东西——系统提示词、推理过程、工具调用和结果、每次上下文注入——全部写进事件日志,一条都不能少。官方把这个叫"模型可见即已记录"。这不是一个可选功能,是运行时的硬性不变量:任何模型能看到的新输入,都必须由一条新的会话事件产生,否则框架直接断言失败。
我以前调 Agent 的时候最头疼的就是出了问题不知道哪一步出了错,黑盒一样。有了这个日志,你可以在关键节点分叉一条新路径试试别的方案,可以重放历史,可以搜索特定事件。调试 Agent 终于有点像调试普通代码了。
一行命令跑起来
说了这么多虚的,先跑起来看看。Harness 的安装简单到我都不敢相信:
npx @deepseek-ai/dsh web
就这一行。不需要克隆源码,不需要配环境变量,npx 会自动拉包启动。默认监听 http://127.0.0.1:3080,数据全在本地,不往云端传。rc.8 之后还会等 Web 配置加载完再自动打开浏览器,不会出现那种浏览器开了页面还白着的尴尬情况。不想自动开浏览器的话加个 --no-open 就行。
我第一次启动的时候大概等了十几秒,然后浏览器自动弹出一个界面,左边是对话区,右边有工具和会话管理。整体风格偏简洁,没有花里胡哨的东西。长这样:

如果你想从源码构建(比如想改内核或者贡献代码),流程稍微复杂一点:
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh web
项目用 pnpm monorepo 管理,仓库锁定了 pnpm@11.7.0。这里我先埋个伏笔,pnpm 版本这个事情后面会让我踩个大坑,待会儿细说。
启动之后第一件事是配模型。Harness 原生支持差不多 40 家模型厂商,国产的占了 15 家,OpenAI、Anthropic、Google 这些也都兼容。在 Web UI 的设置里填上 API Key 就行,不需要手写配置文件。成本和模型选择这块我就不展开了,大家根据自己的需求和预算选,DeepSeek 自家的模型性价比挺高的。
四种模式,别一上来就选错
新建会话的时候你会看到模式选择,一共四种。我一开始没仔细看,随便选了一个就开始用,后来才发现不同模式之间工具集差得挺多,而且一旦新建会话就不能中途切换模式——这是为了保证会话可复现。所以提前搞清楚每种模式是干嘛的很重要。
Standard 模式是功能最全的,文件编辑、Shell、检索、Skills、计划、子代理、工作流,全都有。日常写代码、做项目用这个就对了,我大部分时间都在这个模式下。
Code 模式也叫 PTC(Programmable Tool Calling),它有个很有意思的能力:模型可以用 TypeScript 把多个工具操作组合成一段程序,一次执行。你想想,普通模式下模型要读文件、改文件、跑测试,得一步步来,每步都要等模型请求返回;Code 模式下模型直接写一段脚本把这些事串起来一把跑完。批量操作的时候效率能高不少,官方说能提升 3 到 8 倍。不过这个模式对模型的代码能力要求也更高。
Minimal 模式是极简版,只保留持久 bash 和 str_replace_editor 两个工具。我一开始不理解这模式有啥用,后来看文档才知道主要是用来做模型性能评测的——工具越少,变量越少,越能看出模型本身的真实水平。你要是想轻量级地调试点东西也可以用。
Creator 模式是给开发者做插件和自定义 Agent 用的。它在 Standard 的基础上加了运行时检查、插件实验、预设创作引导这些东西。你想写自己的插件或者定制工作流,就用这个模式。
说实话第一次看到这四个模式的时候我有点选择困难。但用久了发现其实很好选:写代码用 Standard,批量干活用 Code,做评测用 Minimal,写插件用 Creator。就这么简单。
写你的第一个插件
光用现成的东西不过瘾,Harness 最吸引我的地方就是写插件特别简单。一个插件本质上就是一个导出 apply 函数的 TypeScript 模块。框架加载插件时调用 apply,给你传一个 ctx 上下文对象,你通过 ctx 注册各种能力。
先来个最简单的,感受一下:
// src/my-plugin.ts
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello-plugin'
export function apply(ctx: Context) {
console.log('[hello-plugin] plugin loaded!')
}
就这几行。name 是插件名,apply 是入口函数。创建一个 cordis.yml 补丁文件把插件注册进去:
# cordis.yml
- insert:
- id: hello
name: '/absolute/path/to/scratch-plugin/src/my-plugin.ts'
注意这里 name 字段要写插件文件的绝对路径。然后用 patch 参数启动:
pnpm dsh web --patch ./scratch-plugin/cordis.yml
启动的时候终端输出 [hello-plugin] plugin loaded!,就说明插件加载成功了。
不过光打个 log 没啥意思,咱来写个真正能让模型调用的工具。假设我想要一个打招呼的工具,模型调用它就能跟指定名字的人打招呼:
import type { Context } from '@deepseek-ai/cordis'
export const name = 'my-tool-plugin'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register({
name: 'greet',
description: '向用户打招呼',
parameters: {
type: 'object',
properties: {
name: { type: 'string', description: '用户名字' },
},
required: ['name'],
},
async execute({ name }) {
return `Hello, ${name}!`
},
})
}
注意第二行的 inject = ['tools'],这是依赖注入声明——告诉框架这个插件需要 tools 服务。框架会等 tools 服务就绪后才加载你的插件。如果 tools 服务消失了(比如被卸载或替换),插件会自动卸载并清理注册的所有工具。你不需要操心清理的事。
这个自动清理机制值得多说两句。通过 ctx 注册的所有东西——事件监听器、工具、定时器——都会绑定到当前插件的生命周期上,插件卸载时自动逆序清理。如果你有网络连接这种需要自定义清理逻辑的资源,用 ctx.effect() 包一下,返回一个清理函数就行:
import type { Context } from '@deepseek-ai/cordis'
export function apply(ctx: Context) {
ctx.effect(() => {
const timer = setInterval(() => {
console.log('heartbeat')
}, 5000)
return () => clearInterval(timer)
})
}
插件卸载的时候,这个 clearInterval 会被自动调用。不用手动 removeListener,不用 clearInterval,注册即副作用,卸载即清理。我写了这么多年 Node.js,被内存泄漏折磨过无数次,看到这个设计真的有点感动。
我踩过的那个 pnpm 的坑
好了,到了吐槽时间。
前面说从源码构建的时候提到仓库锁定了 pnpm@11.7.0,我当时没当回事。我本机装的是 pnpm 10,跑 pnpm install 的时候也没报错,我还以为没啥问题。结果 build 的时候各种奇怪的依赖错误,什么 hoisting 不对、peer dependency 冲突,折腾了快一个小时。
后来去翻 issues 才发现,项目用的 pnpm 11 跟 pnpm 10 在 node_modules 结构上有差异,monorepo 的 workspace 协议解析行为也不太一样。解决办法很简单,用 corepack 切到正确版本:
corepack enable
corepack prepare pnpm@11.7.0 --activate
删掉 node_modules 重新 install 就好了。
但这还不是最坑的。更坑的是装社区插件的时候。我看到一个挺火的视觉插件想试试,按照文档敲了安装命令,用了 @latest 标签。装完发现功能不对,怎么调都没有最新版本才有的那个特性。折腾半天才发现,pnpm 11 有个机制:发布不足 24 小时的包版本会被拦截,@latest 实际装到的可能是一天前的旧版。
所以社区里有经验的人装插件都会锁定具体版本号,比如:
npx -y @deepseek-ai/dsh plugin --profile web add @liustack/modlens@3.18.3
而不是图省事写 @latest。这个坑我觉得挺反直觉的,一般来说 @latest 不就应该是最新的吗?但 pnpm 11 这个安全策略确实会导致你拿到的不是真正最新的版本。记住这个教训,装插件锁版本号。
另外 Windows 用户可能会碰到 node-pty 的问题。如果你在 Windows 上启动后发现终端用不了,大概率是 node-pty 缺少预编译的二进制文件,需要装 Visual Studio Build Tools 然后重新编译。这个不是 Harness 本身的 bug,是 node-pty 的老问题了,但第一次碰到确实挺懵的。
配置体系简单聊两句
Harness 的能力组合靠三层配置来实现:Profile、Bundle、Patch。这个设计我觉得挺巧妙的,简单说说。
Profile 是最上层的运行模板,决定整套能力组合。前面说的四种模式就是四个不同的 Profile 预设。Bundle 是带配置层的 npm 包,声明"我贡献什么插件"——dsh-base、dsh-web-app 这些都是 Bundle,它们不是单个插件,是一组插件加配置的组合包。Patch 是 YAML 条目数组,按 id 插入或覆盖插件行,只服务于某台机器或某个部署的配置就写 Patch。
三层叠在一起,改能力不需要碰源码,改配置就行。粒度可以从"换整个模型提供商"到"改一个工具的参数",全看你需要。我之前写 Hello World 插件用的那个 cordis.yml 就是一个 Patch 文件。
说点实际的
写到这里,你应该对 Harness 有个基本概念了。它不是一个拿来即用的产品(至少现在还不是),它是一个框架、一个运行时。你得自己配模型、自己选模式、需要什么能力自己装插件或者自己写。
而且要提醒一句,当前版本是 v0.1.0-rc.8,发布于 2026 年 8 月 19 日,还处于开发者预览阶段。官方文档用全大写字母警告过会有破坏性变更,API 不保证稳定。rc.8 就有一个 SQLite 的不兼容变更,升级前如果用了自定义 SQLite 持久化,一定要先备份,官方不提供旧 Schema 迁移。生产环境我建议先观望,但个人项目和学习研究完全可以开始折腾了。
我用下来最大的感受是,Harness 把"换模型"这件事从一个工程任务变成了一个配置项。这件事听起来小,但当你真正在多个模型之间反复对比、在不同场景下切换不同模型的时候,你就会理解这种自由度有多重要。它不替你做选择,但它让选择变得足够便宜。
至于那个"一切皆插件"的架构到底能玩出什么花样,等你写了几个自己的插件之后就会有体会了。我们后面慢慢聊。
所有评论(0)