给 DeepSeek Harness 开发功能:别急着写 Tool,先找对扩展层

在很多 Agent 框架里,开发功能几乎等同于"注册一个 Tool"。

DeepSeek Harness 不是这样。

Tool 只是模型侧的 Consumer。一个完整功能还可能涉及 Service、Provider、事件、持久化、权限和 UI。如果一上来就把所有逻辑塞进 Tool,最后得到的仍然是一个无法替换的单体模块。

这篇从一个最小 Tool 开始,逐步讲清功能应该怎么落在 Harness 里。

先做功能归属判断

写代码前,先确认需求属于哪一层:

需求 扩展位置
增加模型 ctx.llm Adapter
增加模型工具 ctx.tools
增加用户命令 ctx.commands
增加后台任务 ctx.jobs
替换文件系统 ctx.fs Provider
替换进程执行 ctx.subprocess Provider
增加执行策略 tools/*fs/* 事件
注入模型上下文 agent.inject()
保存会话事实 SessionEventMap
增加聊天 UI 节点 Conversation Node 与 Renderer

如果功能已经有明确扩展点,就不要修改 Agent Loop。

从最小插件开始

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

export const name = 'my-plugin'

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

这个模块已经是合法插件。

本地调试时,用 Patch 加载:

yaml 复制代码
- insert:
    - id: my-plugin
      name: '/absolute/path/to/my-plugin.ts'
bash 复制代码
pnpm dsh web --patch ./scratch-plugin/cordis.yml

不要为了验证一个想法就先创建完整 npm 包。Patch 更适合原型阶段,确认扩展点正确后再打包。

依赖不要靠加载顺序

插件需要 Tool Registry 时,应声明 inject

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

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

Cordis 会在 tools 服务就绪后调用 apply

下面这种方式不可靠:

ts 复制代码
import { globalToolRegistry } from 'some-provider'

它让插件依赖具体 Provider,也绕过了 Profile 的组合能力。换一个 Tool Provider 或隔离 Realm 后,代码很可能直接失效。

写副作用时顺手写清理逻辑

插件可能被卸载,也可能因为配置变更而热替换。

定时器、连接和外部进程必须可清理:

ts 复制代码
export function apply(ctx: Context) {
  ctx.effect(() => {
    const timer = setInterval(runTask, 5000)

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

如果插件注册了一个能力,还应该补一个生命周期测试:

text 复制代码
挂载插件
→ 断言注册存在
→ Dispose Fiber
→ 断言注册消失

这类测试比单纯验证"注册成功"更重要,因为残留注册通常只会在热更新或多次测试后暴露。

配置不要写死在常量里

不同环境可能变化的参数,都应该进入配置 Schema:

ts 复制代码
import Schema from '@deepseek-ai/schemastery'

export interface Config {
  endpoint: string
  timeoutMs: number
}

export const Config: Schema<Config> = Schema.object({
  endpoint: Schema.string().required(),
  timeoutMs: Schema.number().default(30_000),
})

export function apply(ctx: Context, config: Config) {
  // config 已经过运行时校验
}

一个简单判断方法是:

修改这个值时,运维或用户是否应该重新编译代码?

如果答案是否定的,它就不该是硬编码常量。

Schema 同时承担默认值和加载期校验。配置错误应该让插件明确加载失败,而不是悄悄回退到一个意外值。

一个合格的 Tool 至少有三层

下面是官方教程中的问候 Tool:

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

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

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'greet',
    description: 'Greet someone by name.',
    parameters: {
      name: {
        type: 'string',
        required: true,
        description: 'The name to greet',
      },
    },
    output: {
      schema: { type: 'string' },
      render: (_args, value) => [
        { type: 'text', text: value },
      ],
    },
    async execute(args) {
      return `Hello, ${args.name}!`
    },
  }))
}

这里故意分开了三件事:

text 复制代码
parameters     定义并校验模型输入
execute        返回规范业务值
output.render  转换成模型可见内容

不要让 execute 直接返回一段带 UI 含义的拼接文本。业务值与展示分离后,同一个结果才能被日志、测试、模型和 Web UI 分别消费。

什么时候需要 Capability Seam

假设要增加代码搜索能力。

如果永远只调用一个固定 SDK,可以先做单插件。

但如果未来需要本地搜索、远程搜索和沙箱搜索,就应该拆分:

text 复制代码
code-search              Service Definition
code-search-local        Local Provider
code-search-remote       Remote Provider
tool-code-search         Model Consumer

Tool 只依赖 code-search 接口,不关心搜索发生在哪里。

建立 Seam 前应确认至少满足一项:

  • 存在多种 Provider
  • 多个 Consumer 共享同一能力
  • 需要独立安全边界
  • 需要替换运行环境
  • 需要单独测试或发布

否则拆包成本可能高于收益。

模型能看到的内容必须进入日志

假设插件要把项目规则注入下一次模型请求。

直接监听 agent/request 并修改消息,看起来能工作,但恢复会话时无法重建这段内容。

Harness 要求模型可见内容能够从 Session Event 日志重放。

正确设计至少要回答:

  1. 这是什么持久事实?
  2. 对应哪一种 Session Event?
  3. 如何从日志投影成模型消息?
  4. Fork 时是否继承?
  5. UI 是否需要展示?
  6. Snapshot 如何固定行为?

这一步会增加代码量,但能避免"当前运行正常,恢复后行为改变"的问题。

拦截事件时不要忘记 next()

Waterfall 事件允许插件包装后续行为:

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

如果策略决定拒绝,可以显式返回错误或拒绝结果。

如果允许继续,就必须调用 next()。漏掉它会让后续执行链直接停止,这是 Harness 插件开发中非常典型的问题。

测试要覆盖真实组合

只写一个 ctx.plugin() 单元测试,不足以证明插件能在产品中运行。

Harness 官方测试大致分为:

bash 复制代码
pnpm run test           # Unit
pnpm run test:coverage  # 逐文件覆盖率门禁
pnpm run test:snapshot  # Keyless 行为与日志快照
pnpm run test:web       # 浏览器快照
pnpm run test:e2e       # 真实模型 API
pnpm run build          # 构建发布产物
pnpm run typecheck
pnpm run lint

产品可见插件至少需要一次真实 Loader 组合测试。测试应从 cordis.yml 启动插件,而不是只手工组装 Context。

如果功能修改了模型、协议或用户可见输出,还需要增加 Keyless Snapshot。

真实 API E2E 不应该检查 Agent 回答中是否包含"成功"两个字。文件任务要重新读取文件,命令任务要重新执行验证。

验证世界是否改变,不要验证 Agent 是否声称自己改变了世界。

把插件发布成 Bundle

Bundle 是带有 Cordis Patch 的 npm 包:

json 复制代码
{
  "name": "dsh-hello-plugin",
  "version": "0.1.0",
  "type": "module",
  "main": "index.js",
  "files": ["index.js", "cordis.patch.yml"],
  "dsh": {
    "bundle": {
      "patch": "./cordis.patch.yml"
    }
  }
}

cordis.patch.yml

yaml 复制代码
- insert:
    - id: hello
      name: dsh-hello-plugin

本地安装:

bash 复制代码
dsh plugin --profile demo add ./hello-plugin
dsh --profile demo --dump-config
dsh --profile demo

没有 dsh.bundle 的普通 npm 包也能成为 Profile 依赖,但不会自动贡献配置层。

从 GitHub 安装插件有执行风险

可以直接安装 Git 仓库:

bash 复制代码
dsh plugin --profile demo add \
  github:you/hello-plugin#<commit-sha>

但 Git 依赖拿到的是源码,不是预构建产物。TypeScript 插件需要提供 prepare 脚本,在安装后构建 lib/

pnpm 10 之后,安装脚本默认需要通过 allowBuilds 显式授权。

这不是一个普通配置项。授权意味着允许插件代码在安装阶段直接运行在宿主机上,而且不受 Agent Sandbox 保护。

因此至少要做到:

  • 审查源码
  • 锁定 Commit SHA
  • 只授权确切包名
  • 避免跟随可变分支
  • 能发 npm 包时优先发布预构建产物
  • 或通过 pnpm pack 交付 Tarball

我的开发顺序

实际开发一个 Harness 功能时,我会按下面的顺序推进:

text 复制代码
确认功能归属
→ 判断是否需要 Capability Seam
→ 定义 Service 或事件
→ 实现 Provider
→ 实现 Tool 或其他 Consumer
→ 接入 Patch
→ 测试卸载与错误路径
→ 测试真实 Loader 组合
→ 增加 Snapshot
→ 构建并验证 lib 发布入口
→ 打包成 Bundle

这个顺序刻意把 Tool 放在 Provider 之后。

因为 Tool 只是模型看到的入口,真正决定系统是否可替换、可测试和可恢复的,是它背后的能力边界。

相关推荐
JaydenAI2 小时前
[基于OpenEvals的自动化评估-10]针对Agent对话的评估[上篇]
ai·langchain·agent·evaluation·openevals
纯爱掌门人2 小时前
我把 DeepSeek Harness 源码跑了一遍,终于看懂了它的“一切皆插件”
agent·deepseek
kyriewen2 小时前
DeepSeek Harness开源第一天我就上手了——和Claude Code的差距比想象中大
前端·ai编程·deepseek
张彦峰ZYF2 小时前
LangGraph 深入理解 ReAct:让 AI Agent 真正学会「边想边做」
人工智能·llm·agent·react·langgroup
梦想很大很大2 小时前
如果有一个本地优先的 Workflow 工具,你们团队会愿意用吗?
python·agent·workflow
302wanger3 小时前
聊天记录翻到烦,我让 DeepSeek Harness给自己写了个插件
deepseek
阿里云大数据AI技术3 小时前
AI Search+ES 9.4.X最佳实践:“更快、更准、更安全的企业级搜索引擎”"为AI Agent提供坚实底座”
人工智能·elasticsearch·agent
特立独行的猫A3 小时前
DeepSeek Harness(dsh)插件开发实战:从零实现一个会话导出插件
deepseek
alwaysrun3 小时前
AI Agent之执行中幻觉问题与应对方案
人工智能·agent