欢迎光临
我们一直在努力

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 等待 tools 和 llm 服务就绪后才挂载该插件。加载顺序由服务依赖表达,无需手动编排。这解决了大型插件系统中常见的\”循环依赖\”和\”加载顺序\”问题。

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-step、agent/request、llm/stream、tools/pre-execute、tools/execute、tools/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
}

五个关键设计决策:

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

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

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

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

  • Fork:ctx.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

    AgentRegistry(ctx.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
    }

    三个关键设计:

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

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

  • 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

    ReactLoopAgent 是 Agent 接口的默认实现,驱动会话穿越 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 流程深度解析:

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

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

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

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

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

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

  • Phase 状态机:

    idle → (wake) → running → (turn done) → idle
    idle → (maintenance) → maintenance → (done) → idle
    running → (abort) → running (aborted) → idle (latch wake)

  • 请求头持久化:buildRequest() 在每个 step 记录 request/header(初始/resume/变更),确保模型可见的一切可从日志重建。

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

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

    ToolRuntime(ctx.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 阶段实现阻断式测试验证的。

    三个高级设计:

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

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

  • 并发安全:工具声明 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

    它做了六件事:

  • 将作者友好的 schema DSL 编译为 raw JSON Schema
  • 编译过程栈安全(迭代式,非递归),检测循环引用
  • InferArgs<S> 从 schema 推断 TypeScript 参数类型
  • InferValue<O> 从 output schema 推断返回值类型
  • execute 的参数自动验证(validateArgs 在 execute 前调用)
  • 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-shell(ShellExecutor 抽象类)
    Service Provider 实现接口 dsh-bash-local / dsh-bash-sandbox
    Consumer 使用服务 dsh-tool-bash(模型可见的 bash 工具)

    为什么三角色分离是关键:当一个 provider swap 时,整个产品行为改变。例如,将 ctx.fs 从 fs-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.yml。web 和 headless 作为模板随产品分发。
    • Bundle:Cordis 配置行和其所挂载代码的分发格式。每个在 package.json 的 dsh 字段声明自己。

    组合顺序(从空到满):

  • Profile 中各 bundle 的声明顺序
  • Profile 的 cordis.patch.yml
  • Home 级 cordis.patch.yml
  • –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 的费用)
    • 异常类型:HarnessError、ReasoningContentMissingError、ToolCallLeakageError、StrictModeCorruptionError、StreamShapeError

    八、Landlock 沙箱

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

  • 在自身安装 Landlock ruleset
  • execve 被包装的命令
  • ruleset 跨 execve 继承,因此命令和所有子进程都被限制
  • 调用进程不受限制
  • 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

    赞(0)
    未经允许不得转载:171主机测评 » DeepSeek Harness 源码级深度剖析:全插件架构 Agent 框架从入门到实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址