DeepSeek Harness源码解析:插件化Agent运行时架构搭建原理
时间:2026-08-20 | 作者:多维游侠 | 阅读:0本文基于对 deepseek-ai/deepseek-harness 仓库(开发者预览版,MIT 协议)源码与架构文档的逐层阅读写成,从架构与设计两条线拆解这个框架:它为什么长这样、每一层在源码里对应什么、以及这些设计决策背后的取舍。
一、先弄清定位:Harness 不是模型,是模型的"执行躯壳"
DeepSeek Harness(命令行名 dsh)是 DeepSeek 于 2026 年 8 月开源的 agent harness(智能体执行框架)。官方用一个公式概括它的定位:
Model + Harness = Agent
模型负责"想"。它承担推理、规划、生成。
Harness 负责除此之外的一切。包括读写文件、执行命令、调用工具、管理上下文、审批权限、错误重试、结果闭环。
README 对自身的定义只有一句话:“一切皆插件”(Everything is a Plugin)。整个系统由 Cordis 插件框架驱动,其设计理念来自论文 A Programming Paradigm for Spatiotemporal Composability[3][4]。
这个定位直接决定了它的架构形态。既然 Harness 是"模型之外的所有工程",而这部分又极其发散,那么唯一可持续的组织方式,就是把发散全部推给插件,让核心收敛到最小。
先看一组仓库的量化印象(基于 master 分支快照统计):
| 指标 | 规模 |
|---|---|
| workspace 包数量 | 约 219 个(packages/ 下 40+ 个功能组) |
| TypeScript 源码 | 约 2100 个文件、46 万行 |
| 测试文件 | 约 650 个 spec/test 文件 |
| 构建/包管理 | pnpm workspace + tsdown,Node ≥ 22.19 |
| vendor 的框架层 | Cordis 全家桶以源码方式内嵌,重命名至 @deepseek-ai scope |
46 万行代码里没有一行是"特权内核"。 这是理解全文的钥匙。
二、全局架构:一棵由配置叠加出来的插件树
2.1 运行时 = 插件树
一个运行中的 dsh 进程,本质上是一棵 Cordis 插件树。
这棵树的形状不是硬编码的,而是由若干配置层(layer)按序叠加出来的:
┌─────────────────────────────────────────────┐
│ --patch 命令行 overlay(最上层,最后应用) │
├─────────────────────────────────────────────┤
│ Harness home 级 cordis.patch.yml │
├─────────────────────────────────────────────┤
│ profile 自己的 cordis.patch.yml │
├─────────────────────────────────────────────┤
│ profile 声明的 bundles(按序) │
│ └─ dsh-base(每个 profile 的第一层) │
│ └─ dsh-web-app / dsh-headless … │
└─────────────────────────────────────────────┘
这里有两个核心概念。源码里都落实为 package.json 上的一个 dsh 字段:
- Profile(档案):存放在 Harness home(
$DSH_HOME或~/.dsh)下的一个具名目录。它声明自己叠放哪些 bundle,保存用户安装的树外插件,以及用户自己的cordis.patch.yml。发行版自带web和headless两个模板。 - Bundle(组合包):一个 npm 包,通过
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }声明自己携带一份 patch 文件。它是"Cordis 配置项 + 挂载代码"的分发格式。
patch 的语义非常克制:按 id 定位某行,整体替换其 config,或插入新行。
它不合并,也不寻址深层字段。这个"整条替换"的约束是刻意的。它保证任何一行配置最多只被"一个 bundle 层 + 用户层"写过,排查配置来源时不会出现三层叠加的鬼故事。
想看自己机器实际启动的树,可以直接执行:
dsh --profile web --dump-config
它打印出的每一个条目,都可以被你写的 patch 替换。包括 agent loop 本身。
2.2 组装机制的源码落点
这套组装逻辑集中在 packages/boot/app-boot(@deepseek-ai/dsh-app-boot)。
它是所有应用入口共享的启动胶水,包括 dsh CLI、ACP demo 等。核心导出包括:
boot():创建 root context → 安装 Loader → 挂载 include 树 → 断言所有条目加载并激活成功;composeEntries():用 include 自己的applyEntryPatches算法在空条目列表上叠加各 patch 层。保证"dump 出来的配置"和"实际启动的配置"永不分叉;installFailLoud():把启动期或运行期的未处理拒绝统一变成一行带标签的 stderr +exit(1),并给持有终端的插件留出清理窗口,绝不在半成品状态上吊死。
每个profile的基础,正是dsh-base bundle的patch文件(packages/bundle/base/cordis.patch.yml)。
像模型适配器、工具注册表、持久化、沙箱与审批策略、设置、凭据、遥测等,都是以 - insert: 的形式,在此一次性插入。
其注释中明确阐述了一条重要原则:“行顺序不带有任何加载语义”。
激活顺序完全取决于服务的可用性,具体依赖的是下文的 inject。
2.3 Agent preset:给单个会话换能力
profile/bundle 解决的是"进程级"组装。
DSH 还支持会话级组装:agent preset。
apps/cli/config/agent-presets/ 下自带四个 preset:standard、code、minimal、cordis。
其中 standard 是完整编码 agent。每个 preset 本质上都是一份 agent.cordis.yml 组合文件。
preset 文件开头注释写透了一个关键机制:isolate realm(隔离域)。
preset 里的服务行必须放在带
isolaterealm 的 group 里。没有它,服务会发布到 root realm 成为进程全局——另一个 preset 发布同名服务就会冲突,host 侧的读取方还会把某个 preset 的实例错配给所有会话。
也就是说,同名服务在不同 realm 下可以有各自的实例。
会话通过 scope 父链join到对应 preset 的 realm。于是,"这个会话用全套工具、那个会话用极简工具"就成了纯配置问题。
这是 Cordis"时空可组合性"论文思想在产品里的直接投影。
三、基石:Cordis 插件框架
Cordis 是 DSH 以 vendor 方式内嵌的底层框架。上游是 cordiverse/cordis,4.0.0-rc.7,本地有硬化修改。
整个 DSH 的插件哲学,可以压缩成五个核心概念。
3.1 五个核心概念
1. 插件是实现 Service 的对象。
最简形态就是一个带 inject 和 apply(ctx) 的对象:
export const name = 'my-plugin'
export const inject = ['tools'] // 声明依赖:等 tools 就绪再启动
export function apply(ctx) {
ctx.tools.register(defineTool({ // 向共享上下文贡献一个工具
name: 'greet',
// ...
async execute(args) { return 'Hello, ' + args.name + '!' },
}))
}
2. 上下文(Context)是服务的容器。
一个服务占据一个稳定的 ctx.,如 ctx.tools、ctx.llm、ctx.sessions、ctx.systemPrompt。
其他插件通过 key 查找服务,而非 import 具体实现。这一条是"可替换性"的根源。
3. 用 inject 声明依赖,让加载顺序自己涌现。
插件声明需要什么服务,就等它就绪后再启动。整个系统没有任何手写的"启动编排脚本"。219 个包的加载顺序来自依赖关系图的拓扑序,而不是人肉维护的清单。
4. 类型化事件是插件间通信的主干道。
事件名通过 TypeScript 声明合并(declaration merging)注册。分发有且仅有四种模式,而且每种模式本身就是事件公开契约的一部分:
| 模式 | 是否 await | 语义 | 典型用途 |
|---|---|---|---|
emit | 否 | 监听器按序观察,无返回值 | 状态广播(agent/status) |
waterfall | 否 | 环绕中间件,可改写/短路 | 拦截(agent/pre-step、tools/pre-execute) |
parallel | 是 | 并行扇出 | 多方消费同一事实 |
serial | 是 | 按序执行,有返回值 | 终末检查点(agent/turn-stopping) |
5. 注册是可逆的副作用。
工具、提示词片段、适配器、监听器,全部通过 ctx.effect() / ctx.on() 安装。
插件卸载时自动撤销。这就是 README 那句"不存在需要打补丁的特权内核"的技术含义。
扩展 dsh 的方式,是把插件挂到其他插件旁边,而不是改别人的代码。
热重载(vendor 了 cordis-plugin-hmr)和用户插件的装卸,也都建立在这种可逆性之上。
3.2 Waterfall:贯穿全系统的拦截原语
四种模式里最重要的是 waterfall。它是 DSH 所有"策略注入点"的统一形态。
语义类似 Koa/洋葱模型:监听器收到 (...args, next),调用 next() 委托给下游并把下游结果包一层返回;不调 next() 直接返回则短路。
架构文档里有一条明确的设计约定:对于单决策事件,短路是设计意图。
拥有决策权的策略监听器,比如审批插件,可以直接返回结果;而只做标注或观察的监听器必须委托。
这条约定让"谁有最终决定权"在协议层面始终清晰。
3.3 Vendor 策略:把框架层变成自己拥有的代码
值得单独说的是 vendor 目录的做法。
DSH 没有通过 npm 依赖 Cordis,而是把 Cordis 及其基础库以源码形式拷入 monorepo,并重命名到 @deepseek-ai scope。
vendor/README.md 给出的理由很直接:harness 要完全拥有自己的框架层。这样才能做到可审计、可打补丁、可锁定版本。
而且这份拷贝不是死快照。
本地修改日志里记录了实质性的硬化,例如 cordis/src/fiber.ts 的生命周期加固:关闭了三个重入式销毁缺口——effect 的 owner 包装在 setup 体运行前注册、卸载开始后拒绝新建 effect、子 fiber 在 internal/plugin 发布前先拿到父级 disposer。
这类修改说明 DeepSeek 团队是在逐行审计并接管框架的并发与销毁语义,而不是简单地"用"一个第三方框架。
四、核心主干:事件溯源的会话 + 可替换的 Agent Loop
packages/core/ 是产品 API 的脊柱。一个轮次恰好流经它的六个包:
agent-loop ──驱动── session(仅追加日志,唯一真源)
│ ▲
├─ system-prompt(组装提示词 + 工具 schema)
├─ llm(流式获取模型响应)
└─ tools(分发工具调用,结果回写日志)
4.1 Session:一切皆为仅追加事件
packages/core/session 实现了一个事件溯源(Event Sourcing)模型。
Session 是一份由类型化 SessionEvent 组成的仅追加日志,是 agent 完整交互历史的唯一真源。
模型看到的历史从不单独存储,而是由 deriveMessages() 从日志投影出来。
回放、fork、恢复、transcript、遥测、持久化,全部派生自同一条事件流。
事件词汇(SessionEventMap)的核心成员如下:
| 事件 | 含义 |
|---|---|
turn/start / turn/end | 轮次边界,turn/end 携带结构化的 TurnEndReason(completed / max-tokens / aborted / error / blocked) |
step/start / step/end | 步骤边界:一次模型请求 + 它引发的工具执行 |
user/message | 进入模型的用户消息(人类输入、inject() 注入的上下文、目标续跑,靠 source 区分) |
assistant/chunk | 原始流式 chunk——token 级回放保真度 |
assistant/message | 组装完成的助手消息,带 usage 和 sourceEventSeqs 精确指回它的 chunks |
tool/call / tool/result | 工具调用与结果(结果事件用 sourceEventSeqs 指回调用事件) |
request/header / request/context | 请求配置与上下文的锚点(initial / resume / change) |
SessionEventMap 可以通过声明合并扩展。compaction 插件就合并进了 compaction/start|summary|end 三个事件类型。
这意味着事件词汇本身也是插件化的。
这套设计里有一条被运行时不变量强制执行的铁律:
模型可见即已记录。
抵达模型请求的一切,都必须能从日志重建,并由一项运行时不变量断言这一点。
packages/core/session/src/invariant.ts 与各包同名的 invariant 文件,配合 ctx.invariants 注册表,把这类架构级承诺变成了运行时会真实抛错的断言,而不是文档里的口号。
新增一种模型可见输入,正确姿势只有一种:
- 扩展
SessionEventMap - 并从日志渲染
任何"绕过日志直接塞给模型"的写法,都会被不变量当场抓获。
另外还有一个细节设计:surface(表面)与日志的分离。
产生消息的事件带 surfaceOp(append / replace)。compaction 摘要落地为一条 surfaceOp: { op: 'replace', start, end } 的 user/message,而 compaction/* 事件本身只进日志不进 surface。
原始历史永久保留在日志里,可审计、可回放。
模型看到的 surface 则是一个可替换的投影。上下文压缩因此不需要"删除"任何历史。
4.2 Agent Loop:一个三态机驱动的轮次/步骤循环
默认的循环实现是 packages/core/agent-loop 的 ReactLoopAgent(packages/core/agent-loop/src/agent.ts)。
这个文件全文不到 500 行,是整个系统最值得精读的single文件。
对外接口(Agent,定义在 packages/core/agent)极简:
followup(input):排队到下一轮次并唤醒驱动器。正常用户输入走这里;steer(input):插队到下一步骤并唤醒。中途引导走这里;inject(input):进入下一步骤的收件箱但不唤醒。插件注入上下文走这里;cancel(cause)/whenIdle()/runMaintenance(job)。
三种输入路由区分得如此细,是因为它们对应三种不同的实时性语义:
- 追问要打断当前节奏
- steering 要在下一步生效
- 注入只需最终一致性
内部状态机只有三个相位(Phase 类型):
type Phase =
| { kind: 'idle'; lastTurn: number }
| { kind: 'maintenance'; abort: AbortController; ... } // 维护作业(如压缩)独占期
| { kind: 'running'; abort: AbortController; turn: number; step: number; ... }
wakeDriver() 是唤醒的唯一入口。
其中的 latch 逻辑处理了一个棘手并发问题:唤醒信号到达时,驱动器正在维护期或已中止怎么办?
答案是锁存 wakeRequested,等驱动器收敛回 idle 时重放。
而被 dispose 的驱动器不锁存,因为拆除永远不该等待一个模型轮次。
轮次主循环(turn() 方法)的骨架,与官方时序图一一对应:
turn/start 落盘
loop:
认领 inbox(首轮认 next-turn,之后认 next-step)
ctx.systemPrompt.assemble() 组装提示词
agent/pre-step waterfall ── 插件可改写消息或直接拒绝
拒绝 → 轮次以 blocked 关闭(不消耗模型调用,但日志留痕)
step/start 落盘,user/message 逐条落盘
step():
agent/request waterfall ── 插件可改写请求配置(provider/model/effort…)
ctx.llm.prepareCall() 解析适配器与精确模型默认值
request/header 落盘(变更时)
llm/stream → assistant/chunk* 逐块落盘 → assistant/message 落盘
失败 → agent/request-error waterfall 决定重试还是保留原错误
有工具调用 → executeToolCalls()(见 4.3)
step/end 落盘
自然停止且 inbox 空 → agent/turn-stopping serial 终末检查点(插件可续命)
turn/end 落盘(finally 中,携带结构化结束原因)
几个值得圈点的源码细节:
- max-tokens 是粘性的:一旦某一步撞上输出上限,后续正常完成的步骤不得把轮次结果"降级"回 completed。轮次结局是对用户体验的诚实记录。
- 每个失败都是结构化的:
LlmError保留全部事实,其他异常统一压成errorChain文本 +UNKNOWN码。turn/end的 reason 因此永远可机器消费。 - 被拒绝/被清空的输入也留痕:首次认领被拒或被改写为空,轮次照样以"零步骤"持久化关闭。日志记录这次尝试,审计无盲区。
- 错误恢复是瀑布式的:
agent/request-error让插件链决定重试策略;上下文溢出时 compaction 插件在此接管。
4.3 工具调度器:屏障 + 有界滚动池
tool-calls.ts(约 290 行)实现了一步之内多个工具调用的调度。
它的设计目标写在文件头注释里:分派可以重叠,但策略、结果和结果上下文必须保持模型顺序(model-ordered)。
调度规则包括:
- 每个调用按
ctx.tools.executionMode()实时分类为exclusive或parallel; - exclusive 调用构成屏障(barrier),必须独占执行,前后排空;
- parallel 调用进入有界滚动池,上限
maxParallelToolCalls; - 启动前会重新分类,因为注册表可能在运行中被 插件改动;
- 结果提交用
commitReady()推进一个只跨越连续已就绪槽位的指针,严格按模型顺序落盘tool/result; - abort 语义完整覆盖:停止补货、排空已启动调用、为未启动的调用补记合成错误结果(
TOOL_ABORTED_BEFORE_DISPATCH),保证回放永远有效; - 而调度器自身故障则排空已分派调用、抛出首个错误,绝不伪造工具结果。
这段代码是"并发性能"与"可回放性"正面相撞的地方。
DSH 的选择很清楚:执行可以并行抢时间,但落到唯一真源日志里的顺序,必须是模型当初发出的顺序。
4.4 工具执行管线:五段式受保护流水线
单个工具调用穿过 packages/core/tools 的流水线时,要过五道关卡。对应 docs/tool-execution-pipeline.md 的流程图:
tools/pre-execute (waterfall) ← 钩子、权限、沙箱策略
↓ allow / ask→审批 / deny
单调守卫(monotonic guards) ← 只能 deny 或弃权,不能翻案;身份受保护
↓
tools/execute (waterfall) ← 环绕分派:超时、重试、指标
↓
工具 execute() 本体 ← fs/* 事件闸门(先读后编辑)、工具自有日志事件
↓
tools/post-execute (waterfall) ← 接受、阻断、替换、追加上下文
↓
finalizeContent + tools/result ← 内容不变式最终检查 → 冻结的权威结果
其中两处设计尤其值得注意:
- 审批是 fail-closed 的。
ctx.approval(packages/interaction/user-approval)的结果集是封闭的'allowed-once' | 'rejected' | 'cancelled' | 'una vailable'。除非明确拿到一次性许可,其余一律拒绝。应答者缺失、不负责、抛异常,全部归入una vailable,而una vailable也是拒绝。会话级策略只有ask/never两档,never确定性拒绝且不分发。 - 结果要经过无损快照。注册表会对候选结果做 JSON 无损快照。快照失败先规范化为
isError,再由冻结的finalizeContent做最后一道"仅内容"的同步检查,最后tools/result广播的是不可变的权威结果。
钩子因此可以横跨所有工具家族工作,而工具自己完全不耦合任何策略服务。
还有一个大招藏在 tools 包里:Code Mode。
它注册一个保留的 run_code 工具,把其余工具的 schema 渲染成一份 TypeScript/Python SDK 注入提示词(tools:sdk 段落)。模型通过写程序来编排工具调用,子调用携带父级 token 走同一条管线并记录 tool/code-dispatch。
等于把"工具组合"从模型的 N 次往返压缩成一次代码执行。对长链路任务的可观测性和 token 开销,都是数量级的改善。
五、设计模式:能力 Seam 三角色
如果说 Cordis 回答了"代码怎么组织",那么能力 seam 回答的是"产品能力怎么可替换"。
架构文档给出定义:一个 seam 由三种角色构成——
| 角色 | 职责 | 例子 |
|---|---|---|
| Service Definition | 声明接口 | dsh-shell 声明 ctx.shell 执行器接口 |
| Service Provider | 实现接口 | 本地进程树后端、bwrap/Landlock/Seatbelt 沙箱后端、E2B 远程后端 |
| Consumer | 消费接口(通常是面向模型的工具) | tool-bash、tool-terminal、lsp 工具 |
“添加一项能力 = 把三者一并设计”。
seam 是"换一个提供方就改变整个产品"的原因。文档给了一个典型例证:文件系统提供方与进程提供方共享同一个执行世界。
把它们指向远程沙箱后,Bash、PTY、LSP 就一并搬过去了,不需要任何提供方专用的 fork。
看两个 seam 的具体形态:
Subagent seam(packages/subagent/)采用“多提供方注册表”形式。
ctx.subagents按名称注册多个提供方。官方自带六个:spawn-in-process、fork-in-process、acp、codex、claude-code、dsh-sdk。
提供方通过静态能力描述符(SubagentCapabilities: outputSchema / depthLimit / toolFilter / persona)公布自身支持的内容。
如果请求了不支持的能力,启动前会被明确拒绝(UNSUPPORTED_CAPABILITY)。这就是所谓的“大声失败,绝不静默降级”。
Sandbox seam(packages/sandbox/)则是"单提供方"形态。
ctx.sandbox 只做一件事:把子进程 argv 包进文件效果策略。
- 模式仅三种:
read-only workspace-writedanger-full-access(第三种直接绕过 seam 裸 spawn)
后端覆盖 Linux bwrap/Landlock、macOS Seatbelt、Windows ACL 受限令牌。
并且后端要如实报告执行完整度(full / partial)。较旧的 Landlock ABI 管不住全部文件效果时,必须如实上报,让要求绝对保证的消费方自行拒绝。
安全语义上的诚实,被做成了类型系统的一部分。
同样的三角色结构贯穿全库:LLM、持久化、压缩、设置、凭据、存储、技能、工作流、网页检索……数下来有近二十个 seam,全部同构。学会一个,就会了所有。
六、值得单独说的几个子系统
6.1 LLM seam 与双适配器策略
packages/llm/llm 定义对话与流式词汇:Message / ContentBlock / StreamChunk / 适配器契约。
其中 ContentBlock 包括 text / reasoning / image / tool-call / tool-result 五种块,且块类型 map 可声明合并扩展。
循环通过 ctx.llm.prepareCall(config) 解析路由。适配器按 provider 名注册,重复注册直接抛 DUPLICATE_ADAPTER。
有意思的是官方带了两条 DeepSeek 通路:
llm-deepseek:直连官方 API,fetch + SSE,路由名刻意叫deepseek-officialllm-pi-ai:库实现,目录名deepseek
一份组合可以同时挂两条路径做对比。
适配器配置里还能看到产品事实:默认模型目录是 V4-Flash / V4-Pro,默认上下文窗口 1,000,000 token,支持 reasoningEffort: off|low|high|max。
6.2 Compaction:以锁与事件表达的上下文压缩
压缩不在循环主干里,而是一个标准 seam。
它向 SessionEventMap 合并三个仅日志事件:compaction/start、compaction/summary、compaction/end。
compaction/start 取锁,compaction/summary 记录摘要、被遮蔽的 seq 集、token 数、生成它的那次 ctx.llm.stream() 调用,compaction/end 放锁。
锁括住整个操作。中途崩溃会表现为"有 start 无 end"的可检测遗留锁,而不是一个谎称完成的 end。
对 surface 的唯一修改,是一条 surfaceOp: replace 的 user/message。
历史原样保留,模型视角完成替换。触发侧挂在 agent/pre-step 和 agent/request-error 两个瀑布上。
6.3 Extensions:agent 修改自己的运行时
packages/extensions/ 是整个仓库里最大胆的一组包。
它把 Cordis 运行时本身暴露成模型工具:cordis_inspect_*、cordis_define、cordis_run、cordis_stop、cordis_undefine。
也就是,模型可以定义并运行模型自己写的动态插件,再撤销。
host 半边的动态包跑在 node:vm 沙箱里,浏览器半边由 client runner 求值为活插件。
这是"一切皆插件"的逻辑终点:既然系统的每一部分都是插件,那么 agent 当然也可以通过写插件来扩展它自己。
Skill 还只是提示词工程,而这里已经是运行时的自我改造。
6.4 Web 架构:前后端也是插件
Web GUI 拆成 host(packages/host/)与 client(packages/client/)两半。
host/webserver 是一个刻意"无知"的 node:http 插件。
它只提供具名路由注册表和 index.html 转换钩子,不理解任何 harness 概念。
所有功能路由,包括 /api 桥接、插件 bundle、HMR 事件流,都是由别的插件注册的。
前后端之间的 RPC 走自研的 Typert。构建期从源码类型生成类型图与产物,运行期经 ctx.typert 注册表和 API gateway 调用。前后端共享同一份类型真源。
进程外集成同样齐备:
packages/sdk提供 JSON-RPC 协议与 TS 客户端packages/acp提供 ACP(Agent Client Protocol)服务器接入编辑器生态packages/python/下还有 Python SDK
所有入口都是"一个薄的自执行组合 + 共享 boot 胶水"。
七、工程文化:从仓库组织看设计品味
源码之外,这个仓库的"元工程"同样值得关注。因为它们本身就是设计哲学的一部分:
- Agent Notes 制度:
.agents/notes/implemented/下按日期归档每一份设计决策,子系统文档直接链回对应笔记。决策的"为什么"和代码的"是什么"被制度性绑在一起。 - 文档即代码的校验体系:子系统文档里的类型签名用 ```````ts type-equiv ````代码块粘贴,
verify-type-equiv提取器会与源码逐名比对,签名漂移直接 CI 失败;中英文文档通过"双语配对"机制(verify-translation-pairing+ 专用 git merge driver)保证同步。 - 生成的目录:事件的生产方/消费方映射、工具目录、配置目录、持久化事件目录全部由脚本从源码生成(
gen-doc-graphs等),文档不可能说谎。 - 不变量即代码:几乎每个包都有
invariant.ts,架构级承诺是运行时会抛错的断言。 - Fail loud 无处不在:启动审计(
assertEntriesLoaded/Activated)、installFailLoud、能力不支持的UNSUPPORTED_CAPABILITY、审批的 fail-closed、沙箱的partial诚实上报——整个系统对"静默降级"有种近乎偏执的敌意。
八、评价:Agent 时代的"安卓",以及它的代价
把 DSH 放进坐标系里看会更清楚。
对比 Claude Code / Codex 这类"精装单体"路线,DSH 的选择是另一个极端:没有核心,只有协议。
模型适配器是插件,工具是插件,持久化是插件,审批是插件,连 agent loop 这个"心脏"都只是 ctx.agentLoop 的一个可替换注册项。
这条路线的收益:
- 替换粒度无限细。换模型、换工具集、换执行环境(本地 → E2B 远程沙箱)、换循环本身,都不需要等官方发版,甚至不需要重启进程。
- 生态潜力大。插件就是 npm 包 + 一份
cordis.patch.yml,社区已有数千个deepseek-harnesstopic 的仓库在生长。 - 可审计性。事件溯源 + 不变量 + fail-loud,让它天然适合对"agent 到底干了什么"敏感的场景。
代价也同样真实:
- 抽象税。每个能力都要走"接口 → 注入 → 事件"的间接层,热路径上是一次次 waterfall 分发;46 万行代码里有相当比例在为"可替换性"本身付费。
- 认知门槛。要改行为,先得理解 Cordis 的分发模式、realm/scope、layer 叠加规则。"一切皆插件"的自由度,是以学习曲线换的。
- 预览期的不稳定性。官方明言会有破坏兼容性的变更,插件 API 还在快速演化。
九、结语
读完 DeepSeek Harness 的源码,最强烈的感受是:这不是一个"带有插件系统的 agent 框架",而是一套插件协议恰好长成了 agent 框架的样子。
它的几乎所有设计——事件溯源的会话日志、waterfall 拦截点、三角色 seam、可逆注册、fail-loud 哲学——都在服务同一个目标:
- 让"模型之外的一切"可组合
- 可替换
- 可审计
如果 Model + Harness = Agent 这个公式成立,那么 DeepSeek 开源的其实是等号右边那一半的完整工程答案。
它是否会成为"执行层标准"尚需时间验证,但就架构的彻底性和工程的纪律性而言,这份源码本身就是 2026 年 agent 工程领域最值得细读的文本之一。
免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。
相关文章
更多-
- DeepSeek限制重生与修改次数,700亿融资何时到位
- 时间:2026-08-21
-
- 腾讯与宁德时代拟参投DeepSeek首轮融资估值或达4000亿元
- 时间:2026-08-21
-
- 阿里云快速部署DeepSeek-R1的6种方法:MaaS、PaaS与IaaS方案
- 时间:2026-08-21
-
- DeepSeek融资即将完成 梁文锋腾讯宁德时代合投350亿
- 时间:2026-08-21
-
- GPT 5.5称霸AI漏洞挑战 DeepSeek获评性价比之王
- 时间:2026-08-21
-
- DeepSeek Harness快速入门:安装配置与首个Agent任务教程
- 时间:2026-08-20
-
- DeepSeek Harness必装10个高效插件盘点与推荐
- 时间:2026-08-20
-
- DeepSeek实用指南:花小钱办大事的Swift开发周报
- 时间:2026-08-20
精选合集
更多大家都在玩
大家都在看
更多-
- 蚂蚁新村小课堂今日答案10月23日 江苏溧阳的跳幡神是一种
- 时间:2026-10-23
-
- 蚂蚁新村2026年10月23日答案最新
- 时间:2026-10-23
-
- 蚂蚁庄园答案2026年10月24日
- 时间:2026-10-23
-
- 蚂蚁庄园今天答题答案2026年10月24日
- 时间:2026-10-23
-
- 蚂蚁庄园今日答案2026年10月24日
- 时间:2026-10-23
-
- 秦始皇陵地宫至今未发掘,主要原因是 蚂蚁庄园今日答案10.24
- 时间:2026-10-23
-
- 小鸡答题今天的答案是什么2026年10月24日
- 时间:2026-10-23
-
- 蚂蚁庄园每日答题答案2026年10月24日
- 时间:2026-10-23
