一个用 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 清单包含:cosmokit、schemastery、cordis、loader、include、group、timer、hmr、logger-console。每个都保留了上游 MIT 许可证,且在 vendor/README.md 里记录了18 项本地修改清单——这是开源项目对下游开发者负责的体现。
二、Profile 与 Bundle:可组合的启动配置
DeepSeek Harness 的启动配置分两层:
| Profile | ~/.config/deepseek-harness/profiles/ 下,列出它 stack 哪些 bundle,存放用户自己的 cordis.patch.yml |
| Bundle |
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 消耗和提升复杂任务完成率都有效。
对应的实现分三层:
toolOrder表控制 prompt 中工具的出现顺序 COLLAPSE_SECTION_ORDER = 99控制 code collapse 语句在 prompt 中的位置 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 是三种角色的组合:
- Service Definition
:声明接口 - Service Provider
:实现它 - Consumer
:使用它(通常是模型 facing 的工具)
一个包可能组合多种角色,但单一角色本身不构成 seam。添加一个能力,意味着要设计全部三种角色。
为什么要这样设计?
因为一个 provider 的替换会改变整个产品的行为。比如,Filesystem 和 Subprocess 的 provider 共享同一个 execution world,把它们指向远程 sandbox,Bash、PTY、LSP 会一起移动,不需要 fork provider。
ctx.fs、ctx.shell、ctx.subprocess、ctx.terminals、ctx.sandbox 都是 seam。每个 seam 背后可以有多个 provider 实现,消费者只依赖 interface,不依赖具体实现。
五、值得关注的几个实现细节
5.1 事件系统的分层
事件分三个 domain:
- Session events
:durable facts,追加到日志,通过 session/event广播 - Agent events
:携带 live Agent,用于观察或拦截飞行中的工作 - Capability events
:在 seam 上附加策略和适配器
turn/、step/、user/message、assistant/、tool/ 是 durable 的 session events。agent/pre-step、agent/request、llm/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 的工厂。
觉得有用?点个关注,持续获取优质内容。
本文首发于「野生极客保护区」公众号,转载需授权。
夜雨聆风