DeepSeek Harness 从零上手:从认识到写出第一个插件

这篇文章与其说是教程,不如说是我的踩坑记录。从认识 DeepSeek Harness到安装、到运行、到写出第一个能用的工具插件,每一步都来自真机实测。

为了防止你跟我一样绕弯,我尽量把"当时我卡住的那个点"讲出来。

全文按照我的理解顺序分三段:先弄明白它是什么,再把它用起来,最后动手真正动手写一个插件。


第一部分 · 先搞懂:DeepSeek Harness 到底是什么

1. 一个简单比喻

我发现,理解 DeepSeek Harness 只要记住一句话:

为方便,DeepSeek Harness(下面都用 dsh 简称)

Agent = Model + Harness(智能体 = 模型 + 运行框架)

拆开说是:

  • Model(模型) 负责"想"------写代码、回话、推理。这是智能体的那份"智力"。
  • Harness(运行框架) 负责"干"------让模型能看懂环境、能调工具、能一直干活。这是它的"身体"。

我在网页版 ChatGPT 上用模型时,其实只有"模型 ",没有"harness"。它在封闭的对话框里,既看不到本地文件,也跑不了本地命令。

dsh 多出来的就是这层"身体"。它让 AI 能在我真实的电脑上动手:读文件、执行命令、调用工具、一步步把事推完。官方英文定位是 A harness keeps agents working in real-world environments------让智能体在真实环境里持续干活。

记这条主线就够了:模型是大脑,harness 是身体,而身体由一堆插件拼起来,这就引出这一核心点。

2. 一切皆插件,不是口号

官方口号是 Everything is a plugin。头回听我以为只是宣传,后来才发现它是架构事实。

dsh 建立在 Cordis 插件系统上。在这个体系里,下面这些全部是插件:

  • 模型(models)------用哪家模型、怎么调
  • 工具(tools)------读文件、跑命令、搜网页这些模型能用的手脚
  • 技能(skills)------可复用的能力包
  • 会话(sessions)------对话状态怎么存、怎么续
  • 沙箱(sandboxes)------模型能摸文件系统的哪些地方
  • 存储(storage)------数据放哪
  • Agent Loop------智能体循环怎么转
  • 调度(scheduling)------后台任务怎么排
  • UI------连图形界面本身都是

我原来以为"扩展能力"是去改 dsh 源码,其实完全不用,换模型就是换插件,加能力就是加插件,想把几个能力拼成新模式就改配置,全程不碰源码。

Cordis 内核管插件挂载、卸载和依赖。插件之间靠 services(服务)events(事件) 协作。

举个具体的:我写一个工具插件,只要声明"我依赖 tools 服务",Cordis 会等 tools 就绪了再加载我的插件。这个细节后面写插件会直接用到。

3. 工具是"规范值 + 投影"

工具是模型在真实环境干活的"手"。dsh 里每个工具的定义抽象得很干净,分两层:

  • schema:声明入参和返回值的具体形状(我叫它"规范值")
  • render(投影):把返回值转成模型真正"看到"的内容

我一开始不太理解为什么拆两层。后来想明白了------给程序看的权威结果,和给模型看的展示内容,是两回事

我的工具可以先返回一个结构化对象,程序拿着它精确判断;render 再把它压成一段文本给模型理解。想换个展示样式,完全不用动计算逻辑。这个两层设计贯穿 dsh 整个工具执行管线,写插件那节我会亲手碰到它。

4. 会话可追踪

另一个主张是 Every run is traceable

模型看到的一切,都会记进一个 append-only(只追加)会话日志:系统提示词、推理过程、每次工具调用和结果、子代理调度、每次上下文注入,逐条记。

Trajectory(轨迹) 视图里,能按来源把这几样拆开检查。继续对话(Resume)、分叉(fork)、搜索(search)、重放(replay),全都是基于同一条事件流。这意味着我能随时回放"模型每一步为什么这么干、到底看了什么"。这套可追踪不是炫技,是我敢让它动本地文件的底气。

5. 权限和沙箱

让 AI 在我电脑上跑命令,头一个想的就是安全。dsh 用沙箱管这块。

沙箱只管文件系统效果,三档由松到严:

模式 允许什么 我什么时候用
danger-full-access 不设限制 需要全局操作时
workspace-write 工作区目录 + 临时区可写 日常干活
read-only 只读,禁止写入 只看不动

沙箱之外还有审批策略:超范围的操作是问我一下,还是直接拒绝(ask / never)。

界面上的权限档位,本质就是"沙箱 + 审批"的组合。我的实践很简单:日常 workspace-write,只查不改 read-only,full access 尽量别碰。

6. 有几种跑法

dsh 给不同场景配了几套模式:

  • Standard------完整工具集:文件编辑、shell、文件/网页搜索、技能、规划、目标、子代理、工作流
  • Code------能力不变,但工具通过 Code Mode SDK 暴露,模型可以用一个 TypeScript 程序把多步操作编排起来
  • Minimal------只有 bash + 文件编辑器,做模型基准测试用的
  • Creator------检查运行时、在内存里测插件、组合新模式,是插件开发者的场地

日常用 Standard 就够;想折腾插件,就上 Creator。


第二部分 · 怎么上手动用

概念捋过一遍,开始动手。这里我尽量把每一步和前面的概念对一下,让你知道界面上那个按钮背后到底是什么。

7. 装好跑起来

先有 Node.js(dsh 的运行底座,LTS 22 及以上),然后一条命令:

bash 复制代码
npx @deepseek-ai/dsh web

第一次会下载依赖。看到下面这行就是起来了:

arduino 复制代码
DeepSeek Harness
  http://127.0.0.1:3080/

浏览器打开 http://127.0.0.1:3080/,主界面出来就算环境就绪。

想先装好再启动也行:npm install -g @deepseek-ai/dsh,然后 dsh web,一个意思。

8. 把模型接上

dsh 自己不产模型,得告诉它用哪家、密钥在哪。

  1. platform.deepseek.com 注册,建一个 API 密钥(sk- 开头,只显示一次,当时我立即就复制了)
  2. 回到 dsh,右上角设置 → 模型
  3. 在 DeepSeek 提供方卡片点编辑,填密钥,保存
  4. 回主界面,对话区顶部能看到模型名(比如 DeepSeek-V4-Flash),就成了

想用更多模型,dsh 两种接法:

  • 添加提供方:从内置列表挑(OpenAI、OpenRouter、通义千问、月之暗面、智谱等二十多家),填密钥即可
  • 添加自定义提供方:填一个 OpenAI 兼容的服务地址,适合本地模型(Ollama / vLLM)、第三方中转、公司内网网关

一句提醒:API 密钥就是钱袋子,别截图、别提交进 git,万一泄露了赶紧去平台吊销重建。

9. 工作区与会话

工作区就是一个项目目录的持久化记录,agent 读文件、改代码、跑命令都以它为根。不选工作区,输入框是锁着的------因为 agent 没有地盘,没法干活。

添加工作区:

  1. 点对话区顶部的选择工作区 ,或侧边栏工作区面板的添加
  2. 在系统目录选择器里选项目根目录(别选 C 盘、用户主目录这种又大又全的)
  3. 确认后工作区进侧边栏,输入框解锁

会话则是一个独立对话。点左上角新会话就能开一个,每个会话有自己的上下文,互不干扰。一个工作区可以挂多个会话。

10. 第一次任务

第一跳指令我建议从"总结式"开始------只读、安全、立刻见效:

列出当前工作区目录下的文件,并简要说明这个项目是做什么的

点发送,dsh 的干活过程大致是这样:

  1. ------调工具读目录、开文件
  2. ------模型推理得出结论
  3. ------有必要就执行命令、写文件(超出权限会先问你)
  4. 交差------给结果

对话区会一条条滚动展示工具调用。切到轨迹视图,能看到它每一步读了什么、做了什么,前面说的"可追踪"这时候就落地在界面上了。

跑完这条,你就算会用了。剩下的切换模型、加附件、设目标、子代理、后台任务、工作流,都是绕这个循环增强,概念没变。


第三部分 · 真正动手写第一个插件

会开只是会"开车",自己写插件等于"造零件"。既然前面懂了"一切皆插件",那就亲手往 harness 这只"身体"上加一只手。下面是从零写一个真实能用工具插件的全过程。

11. 一个 dsh 插件长什么样

在 Cordis 里,插件就是一个导出 apply 函数的模块 。dsh 加载时拿上下文对象(ctx)调它,我通过 ctx 去注册能力。最小形态:

js 复制代码
// greet-tool.js ------ 最小插件骨架(纯 JS / ESM)
export const name = 'my-greet-tool'   // 可选,诊断日志标注用
export function apply(ctx) {          // 插件主体,挂载时调用
    console.log('[my-greet-tool] apply() ran')
}

Cordis 接受三种插件形态:函数 (最常见)、对象(想公开服务时用)。加工具用函数就够了。

两个关键地方:

  • inject :声明依赖的服务。export const inject = ['tools'] 等于说"等工具注册表就绪了再调我这个 apply"
  • 纯 JS(ESM)能零配置跑.ts 写法要额外参数,.js 不用

插件目录里放一个 package.json

json 复制代码
{
    "name": "dsh-my-tool",
    "version": "0.1.0",
    "private": true,
    "type": "module",
    "main": "greet-tool.js",
    "dsh": { "bundle": { "patch": "./bundle.patch.yml" } }
}

这里 "dsh.bundle" 是 bundle 清单,只有打算"装成 bundle"那条路才需要它。

12. 注册一个工具:defineTool

光有 apply 还不够,得把工具注册给模型用 。dsh 提供了 defineTool 来规范化工具定义,再注册进 ctx.tools。完整代码:

js 复制代码
// greet-tool.js ------ 一个最小的 dsh 自定义工具插件
import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'my-greet-tool'
export const inject = ['tools']

export function apply(ctx) {
    console.log('[my-greet-tool] apply() ran, registering "greet" tool')

    return ctx.tools.register(defineTool({
        name: 'greet',                       // 工具名:模型用它发起调用
        description: 'Greet someone by name.', // 模型看它决定何时调用
        parameters: {                        // 入参 JSON Schema,execute 前会校验
            name: { type: 'string', required: true, description: 'The name to greet' },
        },
        output: {                            // 输出契约:schema 规范值 + render 投影
            schema: { type: 'string' },
            render: (_args, value) => [{ type: 'text', text: value }],
        },
        async execute(args) {                // 真正干活
            return `Hello, ${args.name}!`
        },
    }))
}

defineTool 替我做三件事:把参数转成 JSON Schema、execute 前校验 args、做冲突检测。注意 output 里的 schema + render 双层------这就是前面概念 3 讲的分层,现在落到代码里了。

这套 defineTool({ name, description, parameters, output, execute }) + ctx.tools.register(...) 我实测跑通了:在 dsh 0.1.0-rc.6 真实启动后,apply() 确实执行到了注册那一步,日志见 14 节。

13. 把插件挂进 dsh:有三条路

先分清两个概念------boot profiledsh --profile web 启动的,只有 webheadless)和 agent-presetstandard / code / minimal / cordis 四份 YAML)。加工具 = 往 web profile 插件树顶层插一个插件。

路 A:--patch 覆盖层

写一个 insert-greet.patch.yml

yaml 复制代码
- insert:
    - id: my-greet-tool
      name: file:///D:/dsh-practice/my-tool/greet-tool.js

Windows 下这里有个坑 :路径必须 file:/// 前缀且盘符小写。我一开始写裸的 D:/...,或大写 file:///D:/...,一律报 ERR_UNSUPPORTED_ESM_URL_SCHEME

还有一个更关键的坑(模块解析) :用 --patchfile:/// 路径的插件时,dsh 不会帮你解析插件里的裸包名 import。也就是说 greet-tool.js 里的 import { defineTool } from '@deepseek-ai/dsh-tools',Node 会从 my-tool/ 目录往上找 node_modules,找不到就中断报 ERR_MODULE_NOT_FOUND。全局装的 dsh 不会背这个三包名解析的锅。

两种解法的实测结论:

  • 在插件目录里装一份 dsh-toolsmy-tool/ 下执行 npm i @deepseek-ai/dsh-tools,然后得用绝对路径 import(file:///c:/...)。干净,但要在插件里配依赖。
  • 改挂载方式 :走 bundle(路 C)而不用 file:/// 路径,插件会被装进 profile 的 node_modules,依赖自然能解析。

真 boot 后,stdout 出现下面这行标记,说明插件加载成功了。这是我验证的核心证据(实测输出,见 14 节):

arduino 复制代码
[my-greet-tool] apply() ran, registering "greet" tool
dsh web: http://127.0.0.1:3080

路 B:profile 用户层 cordis.patch.yml

内容跟路 A 一样,只是写进 $DSH_HOME/profiles/web/cordis.patch.yml(初始是空的 [])。boot 行为和路 A 相同。这条路藏在 profile 目录下,官方文档只在层序图里顺带提了一句,不显眼。

路 C:dsh plugin add 装成 bundle

bash 复制代码
node node_modules/@deepseek-ai/dsh/lib/bin.js plugin --profile web add D:/dsh-practice/my-tool

前提是环境里有 pnpm。注意 :没声明 dsh.bundle 的话,只会装成普通依赖、不进层栈;补上 bundle 清单再 remove + add 一次。bundle 里的插件行要按包名引用:

yaml 复制代码
- insert:
    - id: my-greet-tool
      name: dsh-my-tool

14. 验证它到底装上没有

这里有个最容易被坑的地方:dsh --dump-config 不代表加载了。dump 是 boot-free 的,只证明"配置树里有条目",不证明"真的加载"。要看加载,得看 boot 的 stdout。

我把验证拆成三层:

  1. --dump-config:配置树里出现插件图层(只证明"有条目")
  2. 加载日志 (最硬):boot stdout 出现标记行 [my-greet-tool] apply() ran, registering "greet" tool
  3. 真实调用:让模型真正触发 greet,看返回结果

验证①:--dump-config 看配置树

前提有坑:dsh 首次运行会在 home 目录写 profile 并建 symlink,Windows 上 EPERM 很可能挡路。解法是显式把 DSH_HOME 指到自己有权限的目录:

bash 复制代码
# Windows PowerShell;把 DSH_HOME 指到自己可写的目录
$env:DSH_HOME="D:\dsh-practice\home"
dsh --profile web --patch D:/dsh-practice/my-tool/insert-greet.patch.yml --dump-config

输出末尾会出现 patch 图层(实测确认):

yaml 复制代码
# == D:\dsh-practice\my-tool\insert-greet.patch.yml
- id: my-greet-tool
  name: file:///D:/dsh-practice/my-tool/greet-tool.js

到这一步只证明配置树有条目,插件根本没执行。

验证②:真实 boot 看加载日志

这才是决定性的。boot(dsh 0.1.0-rc.6,web profile):

bash 复制代码
dsh --profile web --patch D:/dsh-practice/my-tool/insert-greet.patch.yml

stdout 出现下面两行,就说明插件 apply() 真执行了、走到了 ctx.tools.register(...),Web UI 也正常起来:

arduino 复制代码
[my-greet-tool] apply() ran, registering "greet" tool
dsh web: http://127.0.0.1:3080

这行 apply() ran, registering "greet" tool 就是"插件真的被加载"的实锤。

是不是能自己起个最小 Cordis 环境来验证?我试过不行。ToolRegistry 构造时依赖 reflect 去找父级 Context,裸 new Cordis Context() 拿不到 ctx.tools。完整的 tools 服务是由 dsh-agent 通过 dsh 自身插件树装配好再注入的。所以验证只能走 dsh 本体 profile boot,另起炉灶拼 Cordis 既非官方路径,也根本跑不通。

验证③:真实调用

配好模型(API key)后,在会话里让它"用 greet 工具向某个名字打招呼",模型会调 greet,你会看到类似 Hello, ${name}! 的返回。这层要模型参与,不想配模型的话,前面验证②已经足够证明注册成功。

下面这组截图是我在本机真实跑通的记录(dsh 0.1.0-rc.6,web profile,用 OpenAI 兼容的内网网关接 Qwen3.6-35B-A3B):

为了不动我电脑上正式环境(端口 3080 那个),验证实例用的是独立的 DSH_HOME 快照 + 独立端口,配模型、挂插件全是隔离的。

启动后的首页 ------右侧已选好工作区 ai-uview-pro、标准模式、模型 Qwen3.6-35B-A3B:

输入提示词,让它调 greet:

对话结果 ------模型完整走了一遍"读提示 → Think(决定调 greet)→ 工具调用 → 读到结果 → 输出"。留意消息流里那个闪电图标行 Tool call · greet · Alice,工具确实被模型选中并执行了:

点开工具行看详情 ------这一张最能说明插件定义的效果。能看到入参 IN {"name": "Alice"}(正是我们 parameters 里声明的 schema)和返回值 OUT Hello, Alice!(正是 execute 的返回),右上角还有个 Inspect 按钮。前面概念 3 的"规范值 + 投影",在界面上就是这个样子:

隔离验证的小窍门:正式跑时用一个独立 DSH_HOME(比如 dsh-test/home)配独立端口 --port 3081,把模型密钥、插件都放这个"沙盒"里,截图演示完不用了直接删目录就行,不会弄脏日常用的 dsh。

15. Windows 踩坑清单

下面都是我实机踩过的,按"阻断程度"排:

现象 解法
file:/// 前缀且盘符小写 D:/...file:///D:/...ERR_UNSUPPORTED_ESM_URL_SCHEME file:///d:/...(小写盘符)
插件裸包名解析失败 import '@deepseek-ai/dsh-tools'ERR_MODULE_NOT_FOUND 插件目录装依赖,或用 bundle 挂载(路 C)
Windows symlink EPERM 首次运行写 ~/.dshEPERM: symlink $env:DSH_HOME 到有权限的目录
Node 版本太老 报缺 createZstdDecompress / stripTypeScriptTypes 升到 `^22.19.0
pnpm 缺失 dsh plugin add 装 bundle 失败 先装 pnpm
dsh.bundle 装了也不进层栈 package.jsondsh.bundle
.ts 插件 npm 安装版直接跑报错 用纯 .js,或加 NODE_OPTIONS
--dump-config 误判 以为 dump 有条目 = 已加载 dump 是 boot-free,看 boot stdout
模型调用无密钥 让它调 greet 报 401/空结果 自定义提供方走 apiKeyEnv 从环境变量读密钥(如 OPENAI_API_KEY),启动前先 $env:OPENAI_API_KEY=...

16. 绕了一圈,回到开头

写到这,第三部分其实和第二部分的"概念"对上了:

  • 第 2 节"一切皆插件" → 我写的 greet 工具,和 dsh 内置的 shell、文件编辑工具,在这套架构里是平级 的,都是注册进 ctx.tools 的一个条目
  • 第 3 节"规范值 + 投影" → 就是我代码里的 output.schemaoutput.render
  • 第 6 节"Creator 模式" → 就是给插件开发者准备的测试场

想动手的话

  1. 先最小实现 :把 greet-tool.jspackage.json 抄下来
  2. 看配置树 :设好 $env:DSH_HOME,跑 dsh --profile web --patch ... --dump-config,确认 patch 图层进树
  3. 真实 boot 复现dsh --profile web --patch ...,抓 stdout 里 [my-greet-tool] apply() ran 那行
  4. 稳定后再装 bundle :走路 C dsh plugin add,依赖解析更省心

等把第一个工具跑起来,你大概就有数了------dsh 那批社区插件就是这么来的。顺着前面那条主线,读文件、用工具、写插件,都是同一件事。

相关推荐
学习星球6 小时前
AI Agent 成本工程实战:从 OpenAI Codex 的 8 个“烧 Token“Bug 学起
人工智能·设计模式·微信小程序
一点一木8 小时前
🚀 2026 年 8 月 GitHub 十大热门项目排行榜 🔥
人工智能·github
ssshooter8 小时前
现在网页都能提供 MCP 了?!
前端·人工智能·程序员
京东云开发者8 小时前
Claude Code vs Codex 记忆体系深度对比
人工智能
不加辣椒9 小时前
第 4 章 什么是 Harness Engineering
人工智能
桃西西呀9 小时前
上下文窗口都卷到 100 万了,大模型为什么还在为"位置"发愁?
人工智能·llm·ai编程
阿拉斯攀登9 小时前
SpringBoot整合MQTT:后端订阅设备数据、解析传感器报文
人工智能
然我9 小时前
模型不是 Agent:从零实现一个最小 Agent Loop
前端·人工智能·agent
黄油面包9 小时前
Codex 额度三天见底后,我重新做了一周预算
前端·人工智能