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\’):
| 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
它做了六件事:
实战示例:
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.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 字段声明自己。
组合顺序(从空到满):
# 查看实际启动的插件树
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 实现自限制后执行:
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




