乐于分享
好东西不私藏

DeepSeek Harness 源码拆解:一切皆插件的 Agent 框架是如何设计的

DeepSeek Harness 源码拆解:一切皆插件的 Agent 框架是如何设计的

一个用 Cordis 把 Agent 拆成乐高块的硬核框架

DeepSeek Harness(命令行工具 dsh)是 DeepSeek AI 开源的 Agent 运行时框架。它的核心设计哲学只有一句话:Everything is a plugin.

整个产品没有“特权核心”——模型适配器、工具注册表、会话日志、Agent 循环本身,全都是插件。任何组件都可以从配置层替换掉。

目前处于 developer preview 阶段,迭代很快,兼容性破坏变更随时发生。

# 一行跑起来 npx @deepseek-ai/dsh web  # 或从源码编译 pnpm install && pnpm run build && pnpm dsh web

默认 Web UI 跑在 http://127.0.0.1:3080


一、架构总览:Cordis 框架 + 插件树

DeepSeek Harness 构建在 Cordis 框架之上(一个 TypeScript 插件框架,设计论文叫 A Programming Paradigm for Spatiotemporal Composability)。

Cordis 的核心机制:

  • 插件
    向共享 Context 贡献服务、类型化事件、可逆副作用
  • 注册是副作用
    (effect),插件卸载时自动回滚
  • 无特权层
    ——框架本身也是插件,可被配置覆盖

DeepSeek Harness 将 Cordis 的源码 vendored 进自己仓库vendor/ 目录),而不是通过 npm 依赖。这样做的好处是:框架层完全由项目自己拥有(可审计、可打补丁、可固定版本),且所有 vendored 包都重命名到 @deepseek-ai scope,避免发布时抢占上游 npm 名称。

Vendor 清单包含:cosmokitschemasterycordisloaderincludegrouptimerhmrlogger-console。每个都保留了上游 MIT 许可证,且在 vendor/README.md 里记录了18 项本地修改清单——这是开源项目对下游开发者负责的体现。


二、Profile 与 Bundle:可组合的启动配置

DeepSeek Harness 的启动配置分两层:

概念
作用
Profile
一个命名的组合配置,存在 ~/.config/deepseek-harness/profiles/ 下,列出它 stack 哪些 bundle,存放用户自己的 cordis.patch.yml
Bundle
Cordis 配置行的分发格式,包含要挂载的代码和 patch 文件

dsh-base 是所有 profile 的第一层:模型适配器、工具、持久化、沙箱和审批策略、设置、凭证、遥测。dsh-web-app 叠加浏览器应用;dsh-headless 叠加一次性 runner(无 server)。

层叠顺序:

空 entry list   → 每个 bundle 按 profile 列表顺序   → profile 的 cordis.patch.yml   → home 级别的 cordis.patch.yml   → 任何 --patch overlay

查看你机器实际 boot 的树:

dsh --profile web --dump-config

任何打印出来的行,都可以被自己的 patch 替换。这个设计让 DeepSeek Harness 具备了极高的可定制性——你可以只改一个配置文件,就替换掉整个 Agent 循环的实现。


三、核心包拆解

3.1 session —— 会话日志是唯一事实源

core/session 维护一个 append-only 的 SessionEvent 日志。模型看到的所有上下文,都必须能从日志中重建出来。

这是一个关键约束:Model-visible means logged. 任何到达模型请求的东西,都必须能从 session log 中 reconstruct。新增一个模型可见的输入,必须扩展 SessionEventMap 并从日志中渲染。

这样做的好处是:fork、resume、transcript、telemetry、persistence 全都从同一个流衍生出来,不会有状态不一致的问题。

3.2 tools —— 工具注册与执行管道

core/tools 提供:

  • 工具注册表(scoped registry)
  • 模型展示模式(native tools vs SDK code mode)
  • 执行管道:pre → guard → around → post → result 五层钩子

最值得注意的设计是 code mode:当模型处在 code runtime 语言下(TypeScript/Python),run_code 是唯一可以直接调用的工具。其他所有工具都通过 SDK 在程序内部声明和调用。

这本质上把工具调用从“模型每步选一个工具”变成了“模型写一段程序,程序内部批量调用工具”——对减少 token 消耗和提升复杂任务完成率都有效。

对应的实现分三层:

  1. toolOrder
     表控制 prompt 中工具的出现顺序
  2. COLLAPSE_SECTION_ORDER = 99
     控制 code collapse 语句在 prompt 中的位置
  3. SDK_RENDERERS
     表将工具 schema 渲染成 TypeScript/Python 的 SDK 代码

3.3 agent-loop —— ReactLoopAgent

core/agent-loop 实现了 ReactLoopAgent,是默认的 Agent 驱动。

核心流程是一个 turn 内多 step 循环

turn/start   claim next-step input   assemble prompt sections + tool schemas   → agent/pre-step (可以 reject 或 rewrite)   → step/start   → agent/request → llm/stream → assistant/message   → tool/call* → tools/pre-execute → execute → post-execute   → step/end   → 如果 tools 还欠请求 → 继续下一个 step   → agent/turn-stopping turn/end

关键设计点:

  • turn
     是零个或多个 step 的容器,在第一个输入被 claim 时打开,在 nothing owed 时关闭
  • step
     是一次模型请求 + 它调用的所有工具
  • agent/pre-step
     是 waterfall,监听器可以改写或拒绝消息
  • 拒绝或空 claim 仍然会关闭一个 durable turn(但没花费 step),日志会记录这次尝试

FactoryOwnership 类管理 live agents 的 teardown:追踪每个 agent 的 dispose 函数,factory 卸载时一并清理。源码中对 fiber 状态的判断非常严谨:

const INACTIVE_STATES: ReadonlySet<FiberState> = new Set([   FiberState.UNLOADING,   FiberState.DISPOSED,   FiberState.FAILED, ])

确保在不可用状态下不会创建新的 agent。

3.4 llm —— 适配器注册与流式调用

llm/llm 是 LLM 服务的核心:适配器注册表 + 可拦截的流式调用 API。

核心接口:

  • LlmRuntime
    :服务类,提供 stream() 方法
  • LlmAdapter
    :抽象类,provider 后端实现
  • llm/stream
     事件:waterfall,可以短路由自己的 chunk

LLM 调用支持:

  • 重试策略(resolveRetryPolicy
  • 调用配置冻结(deepFreeze,防止 mutation)
  • 错误归一化(normalizeLlmFailure
  • provider 元数据(request id、retry-after)

LOOP-built 请求带有一个 process-local marker,到达时已经 deep-frozen——任何 mutation 都会抛错。这个设计保证了“模型看到的上下文可以从 session log 重建”这个 invariant。


四、Capability Seam:可替换的能力接口

这是 DeepSeek Harness 架构里最精巧的部分:Seam 是三种角色的组合:

  1. Service Definition
    :声明接口
  2. Service Provider
    :实现它
  3. Consumer
    :使用它(通常是模型 facing 的工具)

一个包可能组合多种角色,但单一角色本身不构成 seam。添加一个能力,意味着要设计全部三种角色。

为什么要这样设计?

因为一个 provider 的替换会改变整个产品的行为。比如,Filesystem 和 Subprocess 的 provider 共享同一个 execution world,把它们指向远程 sandbox,Bash、PTY、LSP 会一起移动,不需要 fork provider。

ctx.fsctx.shellctx.subprocessctx.terminalsctx.sandbox 都是 seam。每个 seam 背后可以有多个 provider 实现,消费者只依赖 interface,不依赖具体实现。


五、值得关注的几个实现细节

5.1 事件系统的分层

事件分三个 domain:

  • Session events
    :durable facts,追加到日志,通过 session/event 广播
  • Agent events
    :携带 live Agent,用于观察或拦截飞行中的工作
  • Capability events
    :在 seam 上附加策略和适配器

turn/step/user/messageassistant/tool/ 是 durable 的 session events。agent/pre-stepagent/requestllm/stream 是 waterfalls(必须 next() 委托)。agent/turn-stopping 是 serial 的,没有 next()

5.2 Vendor 修改清单

vendor/README.md 里列出了 18 项本地修改。其中几个值得注意:

  • HMR 的 i18n 移除
    :因为 YAML loader hook 没有 vendor,删掉了 locales
  • Cordis fiber 的生命周期硬化
    :修复了三个重入 disposal 的 gap
  • Include 的 patch 语义
    :export 了纯函数 applyEntryPatches,让 dsh --dump-config 能正确渲染 patch 后的配置树
  • Include 的序列化 mutation
    :每个 child-tree mutation 跑在队列中,避免并发 apply 导致 fiber 永不 settle

这些修改说明 DeepSeek Harness 的团队对 Cordis 框架有很深的理解,且不惮于 fork 和修改上游来满足产品需求。

5.3 Lazy Loader 的 config 解析

从 cordis#41 移植过来的特性:保留原始 fiber config,只在 declared injections 激活后通过 internal/config 解析。这样 provider replacement 可以重新解析 raw expression,pending updates 保留它,HMR transfer 也保留它。

disabled: !!js expression 在每个 mount decision 时评估,raw node 保留在 options 里,write-back 保持 !!js 形式。这是 Cordis 配置动态性的核心机制。


六、总结:为什么值得关注 DeepSeek Harness

DeepSeek Harness 不是又一个 Agent CLI 工具,它是一个 Agent 基础设施框架

它解决的核心问题是:如何让 Agent 系统的每个部分都可替换、可组合、可观测。

  • 用 Cordis 的插件树做依赖注入和生命周期管理
  • 用 append-only session log 做状态持久化和 replay
  • 用 seam 抽象能力接口,让 provider 替换无感知
  • 用 bundle + profile + patch 做可组合的配置

代码质量上乘:严格的类型约束、完整的测试覆盖、清晰的文档分层(AGENTS.md 定义了七层文档 tier)、invariant 检查贯穿所有核心包。

目前还是 developer preview,但架构方向已经非常清晰。对于想自己造 Agent 框架的开发者,这是一个值得深入阅读的参考实现。

金句总结:DeepSeek Harness 用 Cordis 插件系统把 Agent 拆成了可替换的乐高块,用 session log 锁死了可观测性,用 seam 让能力替换无感——它不是在造一个 Agent,而是在造一个造 Agent 的工厂。


觉得有用?点个关注,持续获取优质内容。

本文首发于「野生极客保护区」公众号,转载需授权。