Skip to content

DeepSeek Harness 系列(二):核心架构与运行时骨架

理解 DeepSeek Harness,不能只从“模型调用工具”这条线看。那只是运行时最外层的行为。它真正的架构主线是:用 Cordis 组织插件树,用 Profile/Bundle 决定启动组合,用事件日志记录持久事实,用服务和事件承载扩展能力。

本文按整体到局部的方式拆它的核心架构。细节不会展开到每个包的实现,但会把几个关键设计讲透:配置如何变成运行树,Agent 如何完成一个轮次,工具为什么是一条流水线,会话日志为什么是模型历史的来源,以及业务扩展应该挂在哪里。

总体架构

DeepSeek Harness 可以先看成四层。

这四层的职责不同:

主要职责
应用表层接收用户输入、展示事件流、启动或恢复 Agent。
Agent 运行时组装提示词、调用模型、执行工具、记录会话事实。
Cordis 插件上下文管理服务依赖、插件生命周期、事件分发和配置树。
宿主平台提供文件系统、进程、沙箱、凭据、网络与持久化能力。

最容易误解的是应用表层。Web UI 不是内核,Headless 也不是另一套内核。它们都是 profile 组合出来的表层,底下复用同一组基础服务。

Profile 与 Bundle:运行树从哪里来

dsh 启动时不是直接加载一个固定入口,而是先解析 profile。profile 位于 Harness home 下,里面有 package.json 和用户自己的 cordis.patch.ymlpackage.jsondsh.profile.bundles 列出要叠加的 bundle。

源码文档给出的组合顺序如下。

这里有两个设计点很关键。

第一,bundle 是分发格式。dsh-base 提供模型适配器、工具、持久化、沙箱、审批策略、设置、凭据、遥测等基础能力;dsh-web-app 在它之上加 Web 宿主、API 网关、workspace、前端和浏览器插件;dsh-headless 则加一次性任务 runner,不挂服务器。

第二,patch 是显式覆盖。按 id 定位的 patch 会替换整条配置的 config,不是深度合并。这个规则牺牲了一点配置便利性,但换来了一个很强的性质:最终树可以被打印、检查、复现,运行时没有隐藏合并魔法。

Cordis:插件运行时的底座

Cordis 在这里承担三件事。

Cordis 的价值不只是“可以装插件”。更重要的是它让插件生命周期可被运行时追踪。一个工具注册、一个事件监听、一个提示词段落、一个模型适配器,本质上都是插件安装时产生的副作用。插件卸载或热重载时,这些副作用应该一起撤销。

所以 Harness 的扩展方式不是改内核,而是在对应服务或事件边界旁边挂插件。

核心服务图

下面这张图可以当成 DeepSeek Harness 的运行时地图。

这里的依赖方向很有意思。AgentLoop 是唯一包含具体循环逻辑的包,但它依赖的是抽象服务。工具注册表不直接依赖 UI;工具只是声明展示意图,Host 和 Client 再把展示意图映射成自己的视图。会话服务不关心 Web 页面;Web 监听会话事件流来渲染。

这种关系让每个模块可以独立演进。比如你要加一个内部模型网关,应该注册新的 LLM adapter;要加一个业务工具,应该注册 ctx.tools;要加一个审计策略,应该监听工具或 Agent 事件;要做自己的 UI,应该监听 session/event 并调用 Agent API。

Agent 轮次模型

Harness 把一次交互拆成轮次和步骤。

概念含义
轮次从领取一批输入开始,到当前没有欠下工作为止。
步骤一次模型请求,以及这次响应中触发的一组工具调用。

一个轮次可以有多个步骤。典型场景是:模型请求工具,工具结果回来后,模型还要继续总结或发起下一组工具。

这张图省略了很多错误处理,但主线足够说明问题:Agent Loop 做的是编排,不负责所有策略。agent/pre-step 可以拦截进入步骤的消息;agent/request 可以包装模型请求;tools/pre-executetools/executetools/post-execute 可以包装工具执行;agent/turn-stopping 可以在自然停止前做终点检查。

默认循环尽量保持瘦,扩展能力挂事件。

工具流水线

工具执行是 Harness 最值得学习的一段设计。它不是“调用函数并返回字符串”,而是一条带策略点的流水线。

这条流水线拆开了四类关注点:

  • 工具作者声明参数、输出和执行主体。
  • 策略插件决定能否执行、是否审批、是否沙箱、是否超时。
  • 后置插件可以改写展示内容、阻止结果或追加上下文。
  • 会话日志只记录最终模型可见的权威结果。

这也是业务接入时最该遵守的边界。一个业务工具不应该把所有权限写在工具主体里,也不应该让模型自由拼接数据库语句。更好的做法是让工具只暴露“查询异常任务”“发起补偿”“读取补偿结果”这样的受约束能力,再由工具流水线承载审批、审计和脱敏。

会话日志:系统的事实来源

dsh-session 把会话定义为仅追加事件日志。模型请求的历史不是某个临时数组,而是从日志派生出来的 surface 投影。

这带来三个工程收益。

第一,可重建。模型可见的用户消息、助手消息和工具结果都在日志里,后续请求可以重新派生。

第二,可回放。UI 不需要保存另一份聊天状态;它可以监听 session/event,也可以从已有日志回放。

第三,可恢复。中断、取消、工具未完成、模型错误都能作为事件或恢复结果进入日志,而不是只留在进程内存里。

但这也带来一条硬纪律:新增模型可见输入时,不能只往请求对象里塞内容。它必须有对应的会话事件,能从日志投影出来。

LLM Runtime:模型是适配器,不是内核

dsh-llm 提供的是模型无关词汇和适配器注册表。适配器通过 provider 路由注册,Agent 请求通过 provider 和 model 选择适配器。

适配器必须把不同提供方的流式响应收束成统一的 StreamChunk:文本增量、reasoning 增量、工具调用参数增量、usage、finish 等。这样 Agent Loop 不需要理解每个模型 API 的差异。

对于企业来说,这个边界很实用。内部模型网关、审计代理、私有模型目录,都应该优先做成 LLM adapter 或 llm/stream 包装插件,而不是改 Agent Loop。

能力扩展应该放在哪里

可以用一张决策表来记。

你要做的事推荐挂载点
新增模型提供方注册 ctx.llm adapter。
新增模型可调用能力注册 ctx.tools 工具。
限制某个 Agent 能看到的工具使用 agent 作用域的 ctx.tools.restrict()
工具执行前做权限判断监听 tools/pre-execute 或注册 guard。
工具执行时加超时、重试、指标包装 tools/execute
工具结果脱敏、改写、补上下文监听 tools/post-execute
记录不可变审计结果监听 tools/result
改变模型请求内容监听 agent/pre-stepagent/request
新增持久会话状态扩展 SessionEventMap 并实现投影。
新增 UI 展示节点注册 conversation node renderer,或监听 session/event
接入外部自动化协议做协议驱动插件,通过 ctx.agents 创建或恢复 Agent。

这张表背后的原则是:尽量使用已有服务和事件,不改默认循环。只有当一个能力真的改变了“模型请求、工具执行、轮次关闭”的基本语义时,才需要进入更底层设计。

架构取舍

DeepSeek Harness 的设计不是只有优点。它的几个取舍需要一起看。

设计选择换来的收益付出的成本
Cordis 插件树能力可组合、可卸载、可替换。插件作者必须遵守生命周期和依赖纪律。
Profile/Bundle/Patch运行组合可分发、可覆盖、可检查。patch 替换整条配置,覆盖时更啰嗦。
事件溯源会话可回放、可审计、可恢复。新增模型可见状态必须补事件和投影。
工具流水线权限、沙箱、审批、观测不侵入工具主体。工具作者要区分规范值、模型渲染和 UI 展示。
默认循环保持瘦新行为主要靠插件扩展。复杂能力需要理解服务和事件图。

所以它更适合长期运行、需要扩展和审计的 Agent 产品,不一定适合一次性脚本或小型 Demo。

小结

DeepSeek Harness 的核心架构可以压缩成一句话:Profile 负责组合,Cordis 负责生命周期,Agent Loop 负责编排,Session 负责事实,Tools 和 LLM 负责能力边界。

当你带着这句话再读源码,很多包名就不再零散。dsh-base 是基础能力层,dsh-web-appdsh-headless 是表层组合,dsh-agent-loop 是默认驱动器,dsh-session 是事实来源,dsh-tools 是工具边界,dsh-llm 是模型边界。

下一篇进入实操:如何把源码跑起来,如何配置模型,如何用 Web UI 选择工作区并启动一个真实会话。

系列导航:上一篇:从 Agent 工程化现状看核心设计下一篇:从源码运行 Web UI