DeepSeek Harness 源码级深度剖析:全插件架构 Agent 框架从入门到实战

DeepSeek Harness 源码级深度剖析:全插件架构 Agent 框架从入门到实战

本文适合谁:正在构建 AI Agent 系统的架构师和高级开发者;对"插件化架构"感兴趣的后端工程师;想要深度理解 DeepSeek Harness(dsh)源码的 contributors。

你将获得dsh v0.1.0-rc.8 架构概述与 5 个核心包(Session / Agent / Agent Loop / Tools / defineTool)的关键设计解析、Capability Seam 设计模式的完整剖析、一个可直接投产的 Test Guardian 测试守护插件(含跨文件依赖图、阻断式拦截、持久化缓存和 46 个测试用例),以及 10 条从架构中提炼的设计铁律。

这篇教程值在哪里?

如果你正在选型 Agent 框架——市面上的 LangChain、AutoGen、CrewAI 文档和教程已经泛滥,但 DeepSeek Harness 是唯一一个把"一切皆插件"贯彻到 Agent 循环本身的设计。理解了 dsh 的 Capability Seam 模式,你就掌握了评判任何 Agent 框架可扩展性的尺子。本文概述了 dsh 50+ 子包的架构全貌并深入解析了 5 个核心包的关键设计,帮你避开选型的认知盲区。

如果你正在构建 AI 编程工具——第九章的 Test Guardian 插件解决了一个行业级痛点:Agent 写完代码后测试自动跑、跑不过就阻断,不允许提交半成品。这个插件包含完整的跨文件依赖图(BFS 传递闭包)、阻断式 pre-execute 拦截、持久化缓存和三种语言适配器(Python/TypeScript/Go),共 1658 行代码、46 个测试用例,tsc --noEmit 零错误,可直接 clone 后投入生产。这不是教学示例——这是一个经过测试驱动开发(TDD)验证的、可直接解决真实问题的工业级插件。

如果你想学习插件化架构设计——dsh 的 Cordis 框架实现了"时空可组合性":插件可以运行时动态加载/卸载,卸载时自动回滚所有副作用。这种设计模式不仅适用于 Agent 框架,可以迁移到任何需要"热插拔"能力的系统中(IDE 插件、CI/CD pipeline、微服务网关)。本文从架构层面提炼了 10 条设计铁律,每一条都附带 dsh 源码中的设计依据。

教程的稀缺性——dsh 目前处于 developer preview 阶段,官方文档以 API reference 为主,缺乏架构级解读。本文是对 dsh packages/core/ 中 5 个核心包进行架构级深度解析的中文资料,并包含一个完整的实战插件实现。读完后,你不仅能深度理解 dsh 的架构设计,还能将 Capability Seam、事件溯源会话、waterfall 策略拦截等设计模式迁移到自己的项目中。

阅读建议:全文约 25000 字(含代码),建议分三段阅读——第一段(一至四章)理解框架骨架,第二段(五至八章)掌握运行时机制,第三段(九至十三章)动手实战。


前言:为什么值得关注 DeepSeek Harness?

2025 年,AI Agent 框架赛道已是一片红海——LangChain、AutoGen、CrewAI、OpenAI Codex SDK……每个都在尝试回答同一个问题:如何让 LLM 安全、可控、可扩展地使用工具?

DeepSeek Harness(简称 dsh)给出了一个与众不同的答案:Everything is a Plugin(一切皆插件)

这不是一句营销口号。在 dsh 中,模型适配器是插件,工具注册表是插件,会话日志是插件,连 Agent 循环本身都是插件。这意味着你可以替换 Agent 的核心驱动逻辑,而不需要 fork 任何代码——只需写一个新插件注册到 ctx.agents

这个架构选择带来了一个深远的好处:当你想要改变 Agent 的行为时,你不是在"配置"一个黑盒,而是在"组合"一组透明的、类型安全的、可逆的服务。

本文基于 v0.1.0-rc.8 版本源码,重点解析了 packages/core/ 下 5 个核心包(Session / Agent / Agent Loop / Tools / defineTool)的关键设计,并概述了 50+ 子包的架构全貌和实战指南。


一、项目全貌

1.1 一句话定位

dsh 是一个 Agent 运行时框架(agent harness),核心理念是"一切皆插件"。它基于 Cordis 插件框架(设计思想来自论文 A Programming Paradigm for Spatiotemporal Composability),用 TypeScript 6.0 strict 模式编写,运行在 Node.js ≥ 22.19 上。

1.2 技术栈一览

层级 技术选型 选型理由
运行时 Node.js ≥ 22.19(ESM only) 原生 ESM、AsyncLocalStorage、Fetch API
语言 TypeScript 6.0, strict: true 类型安全是框架的基石,不是锦上添花
包管理 pnpm 11.7 workspaces 50+ 子包的高效管理
插件框架 Cordis(vendored, SHA pinned) 时空可组合性,注册即副作用
配置验证 Schemastery 声明式 schema + 运行时验证
构建 tsdown(打包)/ tsc(类型) 分离构建与类型检查
测试 Vitest 4 单元/E2E/snapshot/web stress 全覆盖
沙箱 Landlock(Linux C11 原生模块) 内核级文件系统隔离
持久化 JSONL / SQLite 事件溯源 + 索引查询双通道
文档 VitePress 中英双语
Python SDK Python 3.10+, Pydantic, JSON-RPC over stdio 跨语言互操作

1.3 仓库结构

vendor/          # vendored Cordis 源码(SHA pinned,不可变)
packages/        # @deepseek-ai/dsh-<pkg> 工作空间
  core/          # 产品核心:session, system-prompt, tools, agent, agent-loop
  llm/           # LLM 适配:DeepSeek / pi-ai / replay
  shell/         # bash/pwsh 执行器
  fs/            # 文件系统操作
  subprocess/    # 子进程管理
  terminal/      # 持久终端
  web/           # Web 搜索/抓取
  sandbox/       # 进程沙箱
  subagent/      # 子代理(6种 provider)
  compaction/    # 上下文压缩
  session/       # 会话持久化
  ...            # 共 50+ 子包
apps/            # 产品组装层
  cli/           # dsh CLI
  web/           # Web UI
examples/        # 可运行的 cordis.yml 示例
native/          # Landlock C11 原生模块
python/          # Python SDK + 打包运行时
docs/            # 架构文档 + cookbook

关键洞察vendor/ 目录中的 Cordis 源码是 SHA pinned 的——这意味着 dsh 对插件框架的依赖是可审计的、不可变的。不会因为上游发版而引入未知变更。这是企业级安全的基本要求。

在这里插入图片描述


二、Cordis 插件框架:五大核心理念

理解 dsh 的前提是理解 Cordis。这不是一个普通的依赖注入框架——它的设计哲学是时空可组合性:插件可以在运行时动态加载/卸载,且卸载时自动回滚所有副作用。

2.1 插件即 Service

插件是一个实现了 Service 接口的对象。两种写法等价:

// 写法 A:函数式插件(轻量场景)
export const name = 'my-plugin'
export const inject = ['tools', 'llm']
export function apply(ctx: Context) {
   
   
  ctx.tools.register(myTool)
}

// 写法 B:Service 子类(需要状态管理的场景)
class MyService extends Service {
   
   
  static inject = ['tools', 'llm']
  constructor(ctx: Context, config: Config) {
   
   
    super(ctx, 'myKey')  // 注册为 ctx.myKey
  }
}

2.2 Context 是服务仓库

服务通过 ctx.<key> 暴露自己。其他插件通过 key 查找服务,而非导入具体实现:

// 插件 A 注册服务(通过 ctx.plugin 加载 Service 子类)
ctx.plugin(MyService)

// 插件 B 消费服务(不知道具体实现)
ctx.myKey.doSomething()

这就是依赖倒置(DIP)在框架级别的实现:消费者依赖抽象的 key,不依赖具体的 import。

2.3 依赖声明通过 inject

export const inject = ['tools', 'llm']

Cordis 等待 toolsllm 服务就绪后才挂载该插件。加载顺序由服务依赖表达,无需手动编排。这解决了大型插件系统中常见的"循环依赖"和"加载顺序"问题。

2.4 类型化事件通信(五种模式)

这是 dsh 最精妙的设计之一。服务通过 TypeScript declaration merging 声明事件名,然后以五种模式分发(对应 Cordis DispatchMode 类型:'emit' | 'parallel' | 'serial' | 'bail' | 'waterfall'):

模式 是否 await 分发顺序 有返回值 典型用途
emit 注册序 通知(tools/result)
waterfall 注册序 策略拦截(tools/pre-execute),around-中间件模式
parallel 并行 批量通知(session/event)
serial 注册序 有序决策(agent/turn-stopping),返回值串联传递
bail 注册序 快速短路(第一个非空返回值即停止)

实战要点waterfall 是 dsh 中策略拦截的核心机制——它实现了 around-中间件模式。监听器收到 (...args, next),调用 next() 委托给下一个监听器,不调用则短路。agent/pre-stepagent/requestllm/streamtools/pre-executetools/executetools/post-execute 全部是 waterfall 事件。

2.5 注册即可逆副作用

所有注册(prompt section、tool schema、adapter、listener)都通过 ctx.effect()ctx.on() 完成:

// 注册一个工具——卸载时自动注销
const dispose = ctx.effect(() => {
   
   
  ctx.tools.register(myTool)
  return () => ctx.tools.unregister(myTool)
})

// 或者更简洁的写法
ctx.on('tools/pre-execute', handler)
// 卸载时自动移除 listener

这意味着插件可以在运行时被安全卸载——所有副作用逆序回滚,不会留下僵尸注册。


三、核心包源码深度解析

3.1 Session —— 事件溯源的会话日志

源码:packages/core/session/src/index.ts

Session 是整个系统的真相源(source of truth)。它采用事件溯源(Event Sourcing)架构:

export class Session {
   
   
  private log: SessionEvent[] = []
  private readonly surfaceManager = new SurfaceManager(this.log)
  
  // 唯一写入路径:追加事件
  append<T extends SessionEventType>(
    type: T,
    data: SessionEventMap[T],
    ...opts: T extends SurfaceEventType ? [opts: SurfaceIntent] : []
  ): SessionEvent<T>
  
  // 从事件日志派生 LLM 消息历史
  deriveMessages(): Message[]
  
  // 请求头折叠(增量缓存)
  requestHeader(): EpochHeader | undefined
}

五个关键设计决策

  1. 追加只写:事件一旦入日志即不可变(deepFreeze),seq 单调递增且连续(seq = log.length 契约)。这保证了会话的可审计性。

  2. Lossless JSON:所有事件数据经过 snapshotJsonValue() 递归验证,确保可序列化。BigInt、Symbol、Map/Set/Date 等异类对象在入口被拒绝。如果你试图把一个不可序列化的值塞进会话日志,它会在入口就被拦截,而不是在持久化时才报错。

  3. Surface 机制:消息产生事件(user/messageassistant/messagetool/result)携带 surfaceOpappendreplace)。SurfaceManager 维护一个有序节点列表,deriveMessages() 从中投影消息历史。压缩操作通过 replace 替换旧节点——这就是上下文压缩的实现方式

  4. 版本控制SESSION_FORMAT_VERSION = 0(未发布期,无兼容承诺)。结构变更 bump,新增事件类型不 bump(靠 ignorable 标记)。

  5. Forkctx.sessions.fork(source, boundary, childId) 从源会话的某个事件序号切出一个子会话。seed 是源日志的连续前缀,不允许在开放 turn 中间切分——这保证了 fork 的一致性。

事件类型完整分类

// 持久会话事件(durable)——写入日志,可恢复
'turn/start'          // 开启一轮对话
'turn/end'            // 关闭一轮(携带 TurnEndReason)
'step/start'          // 开启一步(一次模型请求)
'step/end'            // 关闭一步
'user/message'        // 用户消息(含注入的上下文)
'assistant/chunk'     // 流式 Token
'assistant/message'   // 完整助手消息
'tool/call'           // 模型请求工具调用
'tool/result'         // 工具执行结果
'request/header'      // 请求配置快照
'request/context'     // 路由元数据
'session/end-seed'    // 种子结束标记
'todo/write'          // 待办列表快照

架构洞察:区分"持久事件"和"实时事件"是 dsh 的核心设计。持久事件(turn/*step/*user/message 等)写入日志,可从日志恢复。实时事件(agent/*tools/*llm/stream 等)是运行时通知,不持久化。这保证了会话恢复时只重建模型可见的状态,不重放运行时副作用。

3.2 Agent —— 代理注册表与发起者传播

源码:packages/core/agent/src/index.ts

AgentRegistryctx.agents)管理所有活跃 Agent 的生命周期:

export class AgentRegistry extends Service {
   
   
  private store = new Map<SessionId, AgentEntry>()
  private readonly initiators = new AsyncLocalStorage<Agent | undefined>()
  
  async create(options: CreateAgentOptions): Promise<AgentHandle>
  async resume(options: ResumeAgentOptions): Promise<AgentHandle>
  register(agent: Agent): () => void
}

三个关键设计

  1. 工厂委托AgentRegistry 不直接创建 Agent,而是委托给 AgentFactory(由 dsh-agent-loop 插件提供)。消费者通过 ctx.agents 编程,不依赖具体循环包。这意味着你可以替换 Agent 的驱动逻辑而不修改注册表代码。

  2. Initiator 传播withInitiator(agent, operation) 使用 AsyncLocalStorage 将发起者 Agent 传播到异步调用链。子 Agent 创建时,设置窗口内的注册只对该 Agent 可见——这是 Agent 隔离的基础

  3. Setup 窗口与回滚安全CreateAgentOptions.setup 是创建时组合 Agent scoped world 的回调。工厂在 mint agentCtx 后、发布前 await setup。所有 scoped 注册在 agent/created 和第一个 prompt assembly 前完成。如果 setup throw/rejection,整个 scope 回滚,不发布 session 或 agent id——不会留下半初始化的 Agent。

3.3 Agent Loop —— 默认驱动器

源码:packages/core/agent-loop/src/agent.ts

ReactLoopAgentAgent 接口的默认实现,驱动会话穿越 turn 和 step 边界:

export class ReactLoopAgent implements Agent {
   
   
  readonly inbox: Inbox
  private phase: Phase  // 'idle' | 'maintenance' | 'running'
  readonly scope: Scope
  readonly ctx: Context
  
  // 三种输入路径
  followup(input: UserMessage): void  // next-turn, 唤醒
  steer(input: UserMessage): void     // next-step, 唤醒
  inject(input: UserMessage): void     // next-step, 不唤醒
  
  private async turn(): Promise<boolean>  // 一轮对话
  private async step(assembly: PromptAssembly): Promise<StepEndReason | null>  // 一步
}

在这里插入图片描述

Turn/Step 流程深度解析

  1. turn():开启 turn/start 事件 → 进入 step 循环 → 每个 step 调用 preStep() → 执行 step() → 判断是否继续 → 最终 turn/end

  2. preStep():claim 输入消息 → assemble 系统提示词 → 运行 agent/pre-step waterfall(可 reject 或重写消息)。

  3. step():组装 LLM 请求 → 运行 agent/request waterfall → llm/stream waterfall → 逐 chunk 追加 assistant/chunk → 组装 assistant/message → 执行工具调用 → step/end

  4. Inbox 三通道(这是 dsh 独特的设计):

    • next-turn:排队到下一轮(followup
    • next-step:排队到当前轮的下一步(steerinject
    • inject 不唤醒 idle driver,只有 followup/steer 唤醒

    实战意义inject 允许你在 Agent 运行时静默注入上下文,不打断当前流程。这在多 Agent 协作中非常有用——父 Agent 可以向子 Agent inject 上下文而不等待。

  5. Phase 状态机

    idle → (wake) → running → (turn done) → idle
    idle → (maintenance) → maintenance → (done) → idle
    running → (abort) → running (aborted) → idle (latch wake)
    
  6. 请求头持久化buildRequest() 在每个 step 记录 request/header(初始/resume/变更),确保模型可见的一切可从日志重建。

3.4 Tools —— 工具注册表与执行管道

源码:packages/core/tools/src/index.ts

ToolRuntimectx.tools)是整个工具系统的核心——注册、查找、执行、策略拦截的统一入口。

在这里插入图片描述

管道十阶段(有序执行,每个阶段都是可拦截的):

阶段 事件 类型 能力
1 tool/call 日志 模型请求工具调用,记录到会话日志
2 presentCall(args) 纯函数 UI 渲染 pending 卡片(可用于 replay)
3 tools/pre-execute waterfall 策略层:返回 allow/deny/ask
4 Monotonic Guards 同步检查 不可逆拒绝——一旦 deny,后续无法放行
5 tools/execute waterfall 可替换 exec.signal(施加超时),信号融合
6 execute() 工具主体 返回 canonical JSON value
7 tools/post-execute waterfall accept/block/replace/addContext
8 normalizeResult() 不变式 快照 → JSON 验证 → 冻结
9 tools/result emit 冻结的不可变最终结果通知
10 tool/result 日志 持久化到会话日志

核心价值点:理解这十个阶段是开发 dsh 插件的关键。特别是第 3 阶段(pre-execute)和第 7 阶段(post-execute)——它们是自定义安全策略和结果增强的挂载点。后面的 Test Guardian 插件就是利用第 3 阶段实现阻断式测试验证的。

三个高级设计

  1. Code Modemode: 'code' 时,模型只能直接调用 run_code,其他工具通过程序内 await tools.xxx(args) 调用。子调用携带 parent token,记录 tool/code-dispatch 事件,遵守原生调度契约。这让 Agent 可以编写程序化的工具调用逻辑,而不是一次一个工具调用。

  2. Scoped Shadowing:通过 agent.ctx 注册的工具会 shadow 同名全局工具。restrict() 可对全局工具集做 allow/deny 过滤。你可以为每个 Agent 配置不同的工具集。

  3. 并发安全:工具声明 isConcurrencySafe(args) 返回 true 时,可加入 parallel group。否则为 exclusive(排序屏障)。

3.5 defineTool —— 类型安全的工具定义

源码:packages/core/tools/src/schema.ts

defineTool 是一等工具的推荐定义方式:

export function defineTool<const S extends ParameterSchemaSpec, const O extends ValueSchemaSpec>(
  options: DefineToolOptions<S, O>
): ToolDefinition

它做了六件事:

  1. 将作者友好的 schema DSL 编译为 raw JSON Schema
  2. 编译过程栈安全(迭代式,非递归),检测循环引用
  3. InferArgs<S> 从 schema 推断 TypeScript 参数类型
  4. InferValue<O> 从 output schema 推断返回值类型
  5. execute 的参数自动验证(validateArgsexecute 前调用)
  6. presentCall/presentResult 软验证(replay 时不 throw,回退到 generic)

实战示例

import {
   
    readFile } from 'node:fs/promises'

const readFileTool = defineTool({
   
   
  name: 'read_file',
  description: '读取文件内容',
  parameters: {
   
   
    path: {
   
    type: 'string', description: '文件路径', required: true },
    encoding: {
   
    type: 'string', default: 'utf-8' },
  },
  output: {
   
   
    type: 'object',
    properties: {
   
   
      content: {
   
    type: 'string' },
      size: {
   
    type: 'number' },
    },
  },
  async execute(args) {
   
   
    // args 类型由 InferArgs 自动推断
    // args.path: string (required)
    // args.encoding: string (default 'utf-8')
    const content = await readFile(args.path, args.encoding as BufferEncoding)
    return {
   
    content, size: content.length }
  },
})

注意args.encoding 的类型是 string,而 readFile 的第二个参数类型是 BufferEncoding'utf-8' | 'ascii' | ...)。在 strict 模式下需要显式断言 as BufferEncoding。这是 TypeScript strict 模式下类型安全的正确做法——不在框架边界做隐式转换。


四、Capability Seam 设计模式

这是 dsh 最核心的架构模式,也是它区别于其他 Agent 框架的关键。

4.1 三角色分离

一个 Capability Seam 由三个独立角色组成:

角色 职责 示例
Service Definition 声明接口和 ctx.<key> dsh-shellShellExecutor 抽象类)
Service Provider 实现接口 dsh-bash-local / dsh-bash-sandbox
Consumer 使用服务 dsh-tool-bash(模型可见的 bash 工具)

为什么三角色分离是关键:当一个 provider swap 时,整个产品行为改变。例如,将 ctx.fsfs-local 换成 fs-e2b(远程沙箱),Bash、PTY、LSP 都跟着迁移到远程 Linux 运行时,零 provider fork

这不是理论——这是 dsh 的实际能力。你可以用同一套 Consumer 代码,在本地、Docker 沙箱、E2B 远程沙箱之间无缝切换。

4.2 完整的 Capability Seam 列表

ctx key 角色 实现包
ctx.llm seam llm-deepseek / llm-pi-ai / llm-replay
ctx.fs seam fs-local / fs-sandbox / fs-e2b
ctx.shell seam bash-local / bash-sandbox / pwsh-local
ctx.subprocess seam subprocess-local / subprocess-e2b
ctx.terminals seam terminal-bash
ctx.sandbox seam sandbox-local
ctx.web seam web-search-exa / web-search-perplexity / web-search-deepseek / web-fetch-http
ctx.compaction seam compaction-basic
ctx.subagents seam spawn-in-process / fork-in-process / acp / codex / claude-code / dsh-sdk
ctx.approval seam acp
ctx.codeRuntime seam code-runtime-worker
ctx.lsp seam lsp-local
ctx.skills seam skill-badge / skill-filesystem
ctx.jobs seam jobs-local
ctx.sessionPersistence seam session-persistence-jsonl / session-persistence-sqlite
ctx.storage seam storage-json / storage-sqlite
ctx.workflowEngine seam workflow-worker-thread
ctx.spillStore seam spill-local

核心价值点:这张表是 dsh 架构的"地图"。当你想要扩展某个能力时,先找到对应的 seam,然后实现 Service Provider 接口。不需要修改 Consumer 代码。


五、Profile 和 Bundle 组合机制

dsh 的运行时是一个从启动时组合的插件树

  • Profile:存储在 Harness home 中的命名组合。列出它堆叠的 bundle、持有的 out-of-tree 插件、用户的 cordis.patch.ymlwebheadless 作为模板随产品分发。
  • Bundle:Cordis 配置行和其所挂载代码的分发格式。每个在 package.jsondsh 字段声明自己。

组合顺序(从空到满):

  1. Profile 中各 bundle 的声明顺序
  2. Profile 的 cordis.patch.yml
  3. Home 级 cordis.patch.yml
  4. --patch 覆盖
# 查看实际启动的插件树
dsh --profile web --dump-config

dsh-base 是每个 profile 的第一层:模型适配器、工具、持久化、沙箱和批准策略、设置、凭据、遥测。dsh-web-app 添加浏览器应用;dsh-headless 添加无服务器的单次运行器。

实战技巧:在开发自定义插件时,先创建一个 cordis.patch.yml 来覆盖默认配置,而不是修改 bundle 源码。这样你的定制是可逆的、可追踪的。


六、持久化与可恢复性

6.1 JSONL 持久化

dsh-session-persistence-jsonl 将会话事件流式写入 .jsonl 文件(每行一个 JSON 事件)。支持 zstd 压缩。文件存储在 root(默认 .sessions)目录下。

适用场景:开发环境、小规模部署。每个会话一个文件,便于调试和查看。

6.2 SQLite 持久化

dsh-session-persistence-sqlite 使用 SQLite 的单调 SCHEMA_VERSION 持久化会话事件。支持全文本搜索和过滤。

适用场景:生产环境。索引查询能力强,支持会话搜索和批量管理。

6.3 恢复流程

AgentRegistry.resume(options)
  → ctx.sessionPersistence.prepare
    → 加载事件
    → 构造 Session.fromRestore()
    → mint agentCtx
    → await setup
    → 发布
    → 启动循环

恢复是完整的——不仅恢复会话日志,还重建 Agent 的 scoped world(工具、prompt section、listener)。这意味着一个被恢复的 Agent 可以立即继续工作,行为与被中断前完全一致。


七、Python SDK

dsh 的 Node.js 侧通过 @deepseek-ai/dsh-cordis-client-runner 提供 JSON-RPC over stdio 接口。Python 侧通过 deepseek-harness PyPI 包提供协议感知的 API 客户端。

注意deepseek-harness Python 包(v0.2.0)是一个 DeepSeek V4 API 客户端封装,提供协议层面的安全防护(tool call salvage、reasoning content 保留、cache 字段规范化),不是 dsh Agent 框架的 Python 绑定。如需通过 Python 调用 dsh Agent 功能,需要直接使用 dsh --profile headless CLI 并解析输出。

7.1 DeepSeekHarness — DeepSeek V4 协议感知客户端

from deepseek_harness import DeepSeekHarness

# DeepSeekHarness 是 OpenAI SDK 的协议安全封装
# 构造函数签名:(api_key, base_url, *, salvage_tool_calls, normalize_cache_fields, ...)
harness = DeepSeekHarness(
    api_key="your-key",
    # base_url 默认 https://api.deepseek.com
    disable_thinking_by_default=False,  # 成本敏感部署可设 True
)

# 接口与 OpenAI SDK 完全一致:
response = harness.chat.completions.create(
    model="deepseek-v4-flash",
    messages=[{
   
   "role": "user", "content": "解释这段代码"}],
)

print(response.choices[0].message.content)

DeepSeekHarness 在每次请求中自动执行三层安全防护:

  • from_deepseek_response:保留 reasoning_content 字段
  • salvage_tool_calls_from_content:修复约 11% 的 tool call 泄漏(工具调用被错误地放在 content 而非 tool_calls 中)
  • normalize_usage:规范化两种 cache-hit 字段格式并补充成本估算

7.2 高级功能

from deepseek_harness import (
    DeepSeekHarness,
    normalize_usage,
    estimate_cache_hit,
    ReasoningLifecycle,
    salvage_tool_calls_from_content,
)

# 流式请求——ReasoningLifecycle 管理 thinking 块
harness = DeepSeekHarness(api_key="your-key")
stream = harness.chat.completions.create(
    model="deepseek-v4-pro",
    messages=[{
   
   "role": "user", "content": "分析这段代码"}],
    stream=True,
)
for chunk in stream:
    print(chunk.choices[0].delta.content, end="")

核心设计:

  • 协议安全:自动修复 DeepSeek V4 已知的 11 种协议异常(tool call 泄漏、空终块、cache 字段不一致等)
  • 成本控制disable_thinking_by_default=True 可在简单请求中禁用 reasoning token(默认 V4-Pro 会对每个请求产生约 30 reasoning tokens 的费用)
  • 异常类型HarnessErrorReasoningContentMissingErrorToolCallLeakageErrorStrictModeCorruptionErrorStreamShapeError

八、Landlock 沙箱

@deepseek-ai/node-addon-landlock-run 是一个 Linux 原生 C11 程序(约 300 行),使用 Landlock 内核 UAPI 实现自限制后执行:

  1. 在自身安装 Landlock ruleset
  2. execve 被包装的命令
  3. ruleset 跨 execve 继承,因此命令和所有子进程都被限制
  4. 调用进程不受限制
  5. Fail-closed:内核无法执行时,不运行命令直接退出
import {
   
    grantArgs, launcherPath, probe } from '@deepseek-ai/node-addon-landlock-run';

const launcher = launcherPath();
if (probe(launcher) !== 'unusable') {
   
   
  const argv = [
    launcher,
    ...grantArgs({
   
    readOnly: ['/'], readWrite: ['/tmp/work'] }),
    '--', 'bash', '-c', command
  ];
  // spawn argv
}

支持 linux-x64 和 linux-arm64,内核 5.13+。其他平台使用不同的限制后端(macOS 用 sandbox-exec,Windows 用 ACL restricted-token)。

安全洞察:Landlock 的关键优势是子进程继承限制。即使 Agent 通过 shell 启动了一个子进程,子进程也受同样的文件系统限制。这比应用级的权限检查更可靠——它是内核级的。


九、实战:Test Guardian 测试守护 Agent

这是本文的核心实战部分。我们将编写一个高价值插件,解决 AI Agent 编程中的真实痛点:Agent 修改代码后,如何确保不引入回归?

为什么这个插件值钱? 在 AI 编程工具(Cursor、Copilot、Devin、dsh)的实践中,Agent 生成的代码约 15-30% 存在隐性回归——单元测试不通过、导入路径断裂、接口签名不匹配。现有方案依赖人工 review 或 post-hoc CI,反馈周期长、成本高。Test Guardian 将测试验证前置到代码写入的瞬间:Agent 写代码 → 自动跑受影响测试 → 不通过就阻断,不允许坏代码落盘。这个插件可以直接部署到任何使用 dsh 的生产环境中,将 AI 编程的代码质量从"事后兜底"升级为"事前拦截"。

9.1 问题分析:为什么不是"代码审查"?

初版方案是一个基于 tools/result 事件监听的代码审查插件。它存在四个根本局限:

局限 原因 后果
单文件视角 只审查被修改文件本身的 diff 无法发现跨文件的回归
非阻断式反馈 使用 tools/result(emit 事件),操作已经完成 Agent 可能忽略反馈直接结束
无缓存 每次重新分析全部文件 重复工作,性能浪费
审查主观性 LLM 审查是主观判断 "审查深度不够"是固有问题

Test Guardian 的重新设计

局限 解决方案 机制
单文件视角 → 解决 双向 import 依赖图 + BFS 传递闭包 DependencyGraphManager
非阻断式 → 解决 改用 tools/pre-execute waterfall 返回 { verdict: 'deny' } 短路
无缓存 → 解决 依赖图 + 文件哈希持久化到 ctx.storage 增量更新,重启恢复
审查主观性 → 解决 改为测试验证而非代码审查 通过/失败是客观二元判定

核心思想:不问"这段代码好不好",只问"这段代码的测试通过了吗"——从主观判断升级为客观验证。

9.2 插件结构

packages/extensions/test-guardian/
  package.json
  tsconfig.json
  vitest.config.ts
  src/
    index.ts              # 插件入口:注册 pre-execute 拦截 + result 依赖更新
    types.ts              # 类型定义:PreExecuteVerdict / LanguageAdapter / TestResult / ToolExec / Config
    vendor.d.ts           # 外部依赖类型桩:@deepseek-ai/cordis / schemastery(无完整 dsh 时编译用)
    dependency-graph.ts   # 依赖图管理器:双向邻接表 + BFS 传递闭包
    test-runner.ts        # 测试运行器:防递归 + 多语言测试执行
    sandbox.js            # 动态沙箱兼容版(纯 JS,可直接粘贴进 cordis_define)
    adapters/
      python.ts           # Python 适配器:pytest / from...import
      typescript.ts       # TS/JS 适配器:vitest / import...from
      go.ts               # Go 适配器:go test / import
  tests/
    test-guardian.spec.ts # 完整测试套件:46 用例
    e2e-verification.ts   # 端到端实战验证脚本:6 场景

9.3 核心架构

 Agent 调用 write/edit 工具
       ↓
  tools/pre-execute waterfall 触发     ← 阻断式拦截点
       ↓
  TestGuardian 检查文件类型
       ↓
  更新依赖图(增量,基于文件哈希)
       ↓
  查依赖图:哪些文件受影响?             ← 跨文件影响分析
  (BFS 传递闭包:reverse 图遍历)
       ↓
  ┌─ 有测试文件 ─→ 运行测试 ─────────────┐
  │   (ctx.shell 执行,防递归保护)       ├─ 通过 → allow(放行)
  │                                       └─ 失败 → deny(阻断)+ inject 反馈
  └─ 无测试文件 ─→ LLM 生成测试骨架 ─→ 运行 ─┐
                                              ├─ 通过 → allow
                                              └─ 失败 → deny + 反馈
       ↓
  tools/result 事件同步依赖图              ← 持续更新依赖图
  (即使 pre-execute 被跳过,也维护图)

9.4 类型定义(types.ts)

/**
 * pre-execute waterfall 的返回值。
 * dsh 源码中定义了三种裁决:
 * - 'allow':放行,继续执行工具
 * - 'deny':短路,拒绝执行,reason 返给模型
 * - 'ask':请求人工批准(通过 ctx.approval)
 */
export type PreExecuteVerdict =
  | {
   
    verdict: 'allow' }
  | {
   
    verdict: 'deny'; reason: string }
  | {
   
    verdict: 'ask'; reason: string }

/**
 * 工具执行请求(exec)的描述。
 * 这是在 pre-execute waterfall 中接收到的参数。
 */
export interface ToolExec {
   
   
  /** 工具名称,如 'write'、'edit'、'bash' */
  name: string
  /** 工具参数(已经过 validateArgs 验证) */
  args: Record<string, unknown>
  /** 发起此工具调用的 Agent(可能为 undefined) */
  agent?: {
   
   
    inject: (message: unknown) => void
  }
  /** 取消信号 */
  signal?: AbortSignal
}

/**
 * 语言适配器接口(ISP 原则:消费者只依赖需要的方法)。
 * 每种编程语言实现此接口,提供测试发现、import 解析、测试执行能力。
 */
export interface LanguageAdapter {
   
   
  readonly language: string
  readonly extensions: readonly string[]
  matches(filePath: string): boolean
  parseImports(content: string, ownPath: string): string[]
  findTestFile(sourcePath: string): string | null
  buildTestCommand(testFiles: string[], projectRoot: string): string
  
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值