From de38186ff4a21929571de6ca22958935ec3bbc36 Mon Sep 17 00:00:00 2001 From: wuyang6 Date: Fri, 14 Aug 2026 11:23:05 +0800 Subject: [PATCH] Clarify DeepSeek Harness architecture --- ...eepseek-harness-architecture-evaluation.md | 179 +++++++++++++----- .../dsh-greet-request.svg | 84 ++++++++ .../dsh-protocol-boundaries.svg | 80 ++++++++ .../dsh-startup-assembly.svg | 93 +++++++++ 4 files changed, 386 insertions(+), 50 deletions(-) create mode 100644 public/articles/deepseek-harness-architecture-evaluation/dsh-greet-request.svg create mode 100644 public/articles/deepseek-harness-architecture-evaluation/dsh-protocol-boundaries.svg create mode 100644 public/articles/deepseek-harness-architecture-evaluation/dsh-startup-assembly.svg diff --git a/content/posts/deepseek-harness-architecture-evaluation.md b/content/posts/deepseek-harness-architecture-evaluation.md index 29eb93e..2ab372f 100644 --- a/content/posts/deepseek-harness-architecture-evaluation.md +++ b/content/posts/deepseek-harness-architecture-evaluation.md @@ -1,7 +1,7 @@ --- { "title": "DeepSeek Harness:全插件化 Agent 运行时与评测现状", - "summary": "从配置组合、Agent loop、工具审批、事件日志与恢复机制出发,解释 DeepSeek Harness 的架构取舍,并核对公开评测证据是否足以支持采用。", + "summary": "从一个 greet 工具的完整生命周期出发,分开解释启动装配、请求调用、事件记录和协议边界,再核对 DeepSeek Harness 的架构取舍与公开评测证据。", "date": "2026-08-14", "updated": "2026-08-14", "topic": "agent-systems", @@ -20,54 +20,30 @@ ## 调研范围 -本文面向需要选择或建设 Agent Harness 的研发与技术管理者,回答三个问题:DeepSeek Harness 实际如何组织和运行 Agent;它与常见 coding agent 系统的关键差异是什么;当前公开证据是否足以支持观察、试点或正式采用。 +本文面向理解大模型、API 和基本工具调用,但尚不了解 Cordis 或 Agent Runtime 内部结构的读者。它回答三个问题:DeepSeek Harness 实际如何组装和运行 Agent;它与常见 coding agent 系统的关键差异是什么;当前公开证据是否足以支持观察、试点或正式采用。 调研冻结在 2026 年 8 月 14 日,目标代码固定为提交 [`47f943859bef60e4160492346772ded9b24f765a`](https://github.com/deepseek-ai/deepseek-harness/tree/47f943859bef60e4160492346772ded9b24f765a)。横向对照也固定到同日抓取的 Codex、Gemini CLI 与 OpenHands 提交。本文不评价 DeepSeek 模型能力,不使用 GitHub star 数作为成熟度证据,也不把不同项目、不同模型、不同任务下的数字拼成排行榜。 -## 当前判断 +## 项目位置与必要概念 -1. **最值得关注的是扩展边界。** dsh 允许替换的不只包括工具和模型适配器,还包括会话、Agent loop、工具注册表、策略、持久化和 UI。Profile、Bundle 与 patch 可以逐层改写这些配置,比“在固定 Agent 核心外围加插件”更激进。 -2. **插件由完整主链路连接。** 一次 turn 通过 durable session event、live agent event 和 capability waterfall 串成完整流程;工具审批、执行和结果回写有明确顺序;会话日志同时服务模型上下文、恢复、分叉和 UI 投影。 -3. **工程质量证据多于效果证据。** 仓库有高覆盖率单元测试、keyless snapshot、真实 API e2e 入口、Web 性能/压力 runner 和 CI runner benchmark;但在冻结范围内没有发现公开的 Agent 任务成功率成绩。 -4. **现在的合理动作是观察或隔离试点。** Developer Preview、兼容性主动破坏、会话格式 v0、无正式 GitHub Release,以及缺少可复现的目标任务评测,都会阻断生产级采用。 +DeepSeek Harness(命令名 `dsh`)不是一个模型,也不是某个单独工具的封装。它是一个 **Agent Harness**:负责把模型、工具、会话状态、执行循环、权限策略和用户入口组装成一个可以持续运行的 Agent 应用。 -## 一次 Agent 请求如何流过系统 +先建立四个最小概念: -![DeepSeek Harness 从配置组合到会话持久化的主链路](/articles/deepseek-harness-architecture-evaluation/dsh-agent-flow.svg) +| 名称 | 白话解释 | 在 dsh 中负责什么 | +|---|---|---| +| Agent | 能接收目标、调用模型和工具、保留状态并继续工作的程序 | 一个 Agent 由模型、工具、会话和执行循环共同组成 | +| Agent Harness | 承载 Agent 的运行外壳 | 启动组件、接收请求、管理状态、执行工具并暴露 Web/协议入口 | +| Cordis | dsh 进程内部的插件装配器和生命周期管理器 | 决定有哪些插件、依赖是否满足,以及变化时如何卸载和重建 | +| Agent loop | 一次任务中的“模型—工具—模型”循环 | 组装上下文,调用模型,执行工具,再把结果交回模型直到结束 | -启动时,dsh 先从 Profile 读取有序 Bundle,再叠加 Profile、Harness home 和命令行 patch,得到实际 Cordis 插件树。官方架构文档明确写到,模型适配器、工具注册表、会话日志和 Agent loop 本身都是插件;配置行可以被更高层 patch 整体替换。[架构文档](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/docs/architecture.md) +dsh 主体是 TypeScript monorepo,运行在 Node.js ESM 环境;冻结提交的根配置要求 Node.js 22.19 以上或 24 以上。Cordis 插件在运行时是由 Node 加载的 JavaScript/TypeScript 模块,通常导出 `apply(ctx, config)`。[根 package.json](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/package.json) [第一个 Cordis 插件](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/docs/cordis-tutorial/01-first-plugin.zh.md) -运行时,输入先进入 Agent inbox。loop 在每个 step 前组装系统提示、工具 schema 与从会话日志派生的历史,然后发出 `agent/pre-step`;通过后写入 `step/start` 与用户消息,再经 `agent/request` 调用 LLM。模型产生工具调用时,执行路径依次经过 `tools/pre-execute`、审批、guard、`tools/execute`、工具本体、`tools/post-execute` 和结果固化。需要审批却没有可用 answerer 时,`ask` 会退化为拒绝,而不是静默放行。[Tools README](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/core/tools/README.md) +理解这套架构最重要的前提是把两个问题分开:**启动装配**回答“哪些组件存在、谁依赖谁、谁随谁卸载”;**请求执行**回答“用户的一条消息在已经启动的组件之间怎样移动”。Cordis 插件树主要回答前一个问题,不是用户请求的调用顺序。 -这些事实被追加到 `SessionEvent` 日志。模型历史从日志派生;resume、fork、轨迹视图和持久化也复用同一事实源。若进程在工具调用中断,恢复逻辑会区分“工具尚未开始”和“工具结果未知”,后者会提示模型先验证副作用,避免盲目重试。[持久化说明](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/session/session-persistence/README.md) +## 一次完整的 `greet("Ada")` 走查 -这条链路的价值在于,扩展不必侵入一个巨大核心:插件可以提供服务、监听或包裹 waterfall,并在卸载时撤销注册。Cordis 预印本将其归纳为时间可组合性与空间可组合性:一方面追踪可逆 effect,另一方面根据依赖变化重新协调组件。但论文仍处于 active revision,适合作为设计解释,不应当作稳定标准。[Cordis 预印本](https://github.com/cordiverse/paper) - -## Cordis 插件树到底怎样实现 - -“插件树”容易让人误以为是一棵类继承树。dsh 里实际叠加了三层机制: - -1. **配置层决定“挂载什么”。** Profile 先列出有序 Bundle;启动时再依次应用 Bundle patch、Profile 自身 patch、Harness home patch 和命令行 `--patch`。每个配置行用 `id` 作稳定身份,`name` 指向插件模块,还可带 `config`、`inject` 和 `disabled`。后层 patch 按 `id` 命中前层行时,替换整个 `config`,不做字段级深合并。[CLI 组合参考](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/apps/cli/reference/README.md) -2. **Cordis 运行时决定“何时激活”。** 根 `Context` 下的 Loader 把每行配置挂成一个插件实例,每个实例都有 Fiber 状态。插件通过 `apply(ctx, config)` 贡献能力,通过 `inject` 声明必需服务。因此 YAML 行的先后不决定加载顺序:依赖未就绪的插件保持 `PENDING`,服务出现后才激活。[Cordis 服务教程](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/docs/cordis-tutorial/03-services.zh.md) -3. **effect 层决定“怎样安全拆除”。** `ctx.tools.register(...)`、`ctx.on(...)`、服务注册和子插件都与当前插件生命周期绑定。配置删除、HMR 或服务提供方消失时,Cordis 会卸载相关 Fiber,反向撤销注册;服务恢复后,依赖它的插件再重新加载。[Cordis 生命周期教程](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/docs/cordis-tutorial/02-lifecycle-and-effects.zh.md) - -可以把一个简化后的运行时理解为: - -```text -Root Context -└─ Loader - ├─ tools provider Fiber → 提供 ctx.tools - ├─ greet-tool Fiber → inject: tools - ├─ llm provider Fiber → 提供 ctx.llm - ├─ agent-loop Fiber → 消费 agents / tools / llm - └─ surface Fiber → Web、ACP 或 SDK JSON-RPC -``` - -这棵“树”同时包含两种关系:Loader 建立的父子生命周期,以及 `inject` 建立的服务依赖图。Bundle 是生成配置行的发布层,并不会成为一个不可拆的运行时黑盒。 - -### 一个最小例子:向模型增加 `greet` 工具 - -下面是官方 Cordis 教程示例的精简版。插件不自己启动 Agent,只声明“我需要 `tools` 服务,并向它注册一个工具”: +假设现有 Profile 已经包含 LLM、工具注册表、会话、Agent loop 和 Web/ACP/SDK 入口。我们只增加一个虚构示例包 `@acme/dsh-greet-tool`,让模型能够调用 `greet`。下面代码根据官方教程的工具插件写法精简;它只演示插件机制,不是仓库已经发布的包。 ```ts import type { Context } from '@deepseek-ai/cordis' @@ -91,7 +67,7 @@ export function apply(ctx: Context) { } ``` -假设将它发布并安装为虚构示例包 `@acme/dsh-greet-tool`,可在 Profile 的 `cordis.patch.yml` 插入一行: +把它加入 Profile 的 `cordis.patch.yml`: ```yaml - insert: @@ -99,20 +75,123 @@ export function apply(ctx: Context) { name: '@acme/dsh-greet-tool' ``` -这个例子假定所选 Profile 已有 `tools`、LLM 和 agent loop 等基础行;它只演示新能力如何插入既有 Agent。完整过程是:Loader 看到 `greet-tool` 行 → 发现它需要 `tools`,Fiber 在缺失时保持 `PENDING` → `ctx.tools` 就绪后调用 `apply` 并进入 `ACTIVE` → `greet` 的 schema 进入模型工具列表 → 模型发出 `greet({"name":"Ada"})` 时经标准工具管线执行 → 返回 `Hello, Ada!`。删掉这行或 HMR 热替换包时,Loader 根据稳定 `id` 重挂变化节点;旧 Fiber 进入卸载并最终 `DISPOSED`,工具注册随 effect 自动撤销。官方完整例子还演示了如何监听 `tools/result` 事件。[进入 Harness 教程](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/docs/cordis-tutorial/07-into-the-harness.zh.md) +这个工具从配置到卸载会经历六个连续阶段。 -### “接入协议”要先区分方向 +### 第一阶段:配置先决定“准备挂载什么” -| 接入面 | 连接方式 | 解决的问题 | +dsh 不会直接把某一个 YAML 文件原样交给 Cordis。它先读取 Profile 指定的有序 Bundle,依次应用各 Bundle 的 patch,再叠加 Profile 自身 patch、Harness home patch 和本次命令行 `--patch`,最终得到一组有效配置行。`greet-tool` 是其中一行;它的 `id` 是稳定身份,`name` 指向要加载的模块。[CLI 组合参考](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/apps/cli/reference/README.md) + +如果后层 patch 命中同一个 `id`,该行的 `config` 会整体替换,不是字段级深合并。这里的配置合成只回答“本次启动要挂载什么”,尚未执行任何用户请求。 + +### 第二阶段:Loader 创建 Fiber,`inject` 决定何时启动 + +启动器创建根 `Context` 并挂载 Loader。Loader 解析 `@acme/dsh-greet-tool`,为这次插件挂载创建一个 Fiber。Fiber 不是线程或操作系统进程,而是 **一个插件实例的运行时句柄**:它记录父上下文、配置、依赖、状态、注册的 effect 和清理过程。 + +插件导出了 `inject = ['tools']`,所以 Cordis 会检查名为 `tools` 的 Service 是否已经存在。若不存在,Fiber 保持 `PENDING`,不会调用 `apply`;当 `ctx.tools` 出现后,Fiber 进入 `LOADING`,执行 `apply(ctx)`,完成后进入 `ACTIVE`。因此 YAML 中哪一行写在前面并不决定启动顺序,服务依赖才决定。[Cordis 服务教程](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/docs/cordis-tutorial/03-services.zh.md) + +`apply` 中的 `ctx.tools.register(...)` 把 `greet` 的名称、参数 schema、返回值渲染和执行函数注册到工具运行时。该注册本身属于一个 effect;Cordis 会记住对应的 disposer,以便插件卸载时自动注销工具。[进入 Harness 教程](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/docs/cordis-tutorial/07-into-the-harness.zh.md) + +### 第三阶段:用户请求进入已经启动的 Agent + +用户通过 Web、ACP 或 SDK 输入“向 Ada 问好”。入口适配器把消息交给对应 Agent 的 inbox。Agent loop 取得待处理输入,从会话日志派生历史,加入系统提示和当前可用工具 schema,然后进入一个 model step。此时 `greet` 已经是工具列表中的一项;Cordis 不再决定模型是否调用它,Cordis 只保证工具已经正确注册。 + +模型可以直接回答,也可以产生工具调用。这个例子中,模型返回 `greet({"name":"Ada"})`,于是 Agent loop 把调用交给统一 Tools Runtime。 + +### 第四阶段:工具经过统一执行管线 + +工具调用不会从模型直接跳进示例函数。标准路径依次经过 `tools/pre-execute`、审批、guard、`tools/execute`、工具本体、`tools/post-execute` 和结果固化。若某项策略返回 `ask`,但当前入口没有可用审批 answerer,系统会拒绝而不是静默放行。[Tools README](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/core/tools/README.md) + +`greet` 执行后返回 `Hello, Ada!`。Tools Runtime 将结果交回 Agent loop,Agent loop 再把它放入下一次模型请求。模型据此生成最终回答,例如“已向 Ada 问好”。这就是 Agent loop 所负责的“模型 → 工具 → 模型”循环。 + +### 第五阶段:SessionEvent 在旁路记录事实 + +用户消息、step 开始、模型消息、工具调用、工具结果和最终回答会分别追加为 `SessionEvent`。它不是调用链中的“下一台服务器”,也不负责把工具结果转发给模型;它是执行过程中形成的 durable fact。模型历史、resume、fork、轨迹视图、持久化和 UI 投影都从同一事实源派生。[持久化说明](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/session/session-persistence/README.md) + +如果进程在工具调用期间中断,恢复逻辑会检查日志:工具尚未真正开始,可以安全标记失败;工具可能已经产生副作用但没有留下结果,则标记“结果未知”,提示模型先验证外部状态,避免盲目重试。这解释了事件日志为什么与执行链同样重要,但二者仍是不同关系。 + +### 第六阶段:配置或依赖变化时自动撤销 + +Fiber 的主要状态为 `PENDING → LOADING → ACTIVE → UNLOADING`,加载失败时进入 `FAILED`。如果 `tools` Service 暂时消失,已经激活的 `greet-tool` 会卸载自己的 effect,回到等待依赖的状态;服务恢复后重新加载。如果配置行被删除,旧 Fiber 完成清理后进入 `DISPOSED`,不能再次启动。HMR 则卸载旧实例,再根据新模块挂载替代实例。[Cordis 生命周期教程](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/docs/cordis-tutorial/02-lifecycle-and-effects.zh.md) [组合与 HMR](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/docs/cordis-tutorial/06-composition-and-hmr.zh.md) + +卸载时,`greet` 的工具注册、事件监听、子插件和通过 `ctx.effect()` 管理的定时器或连接都会执行 disposer。插件不需要在每个退出分支手工寻找自己曾经注册的对象。这个“注册与撤销属于同一生命周期”是 Cordis 最核心的工程价值。 + +到这里可以用一句话复述完整过程:**配置决定挂载 `greet-tool`;`inject` 让它等待 `tools`;Fiber 激活后注册工具;Agent loop 在一次请求中让模型调用它;SessionEvent 记录事实;依赖或配置变化时 effect 自动撤销。** + +## 启动装配:Profile 如何变成运行中的插件 + +![启动装配:谁被创建、谁等待谁;这不是请求调用顺序](/articles/deepseek-harness-architecture-evaluation/dsh-startup-assembly.svg) + +这张图只回答启动问题。可以把它拆成配置层和运行时层。 + +### 配置层:Profile、Bundle 与 patch + +- **Bundle** 是可复用的默认插件组合,例如一组基础服务和工具。它贡献配置行或 patch,但运行时不会把整个 Bundle 当成不可拆的黑盒。 +- **Profile** 是一种产品或运行形态的入口。它选择有序 Bundle,并附加自己的 patch,例如组合 Web 或 headless 版本。 +- **Harness home patch** 是用户或部署环境的持久覆盖。 +- **CLI `--patch`** 是本次启动最后应用的临时覆盖。 + +合成结果是一组带 `id` 的配置行。每行通常包含 `name`、`config`、`inject` 和 `disabled` 等字段。稳定 `id` 让 Loader 判断一项变化是在更新已有节点,还是删除旧节点后增加新节点。 + +### 运行时层:Context、Loader 与 Fiber + +根 `Context` 是进程内能力和事件的访问入口;Loader 根据有效配置加载模块,并把每次挂载变成 Fiber。官方教程支持函数、带 `apply` 的对象和 `Service` 子类三种插件形态。`apply(ctx, config)` 只描述插件向当前上下文贡献什么,启动器和 Loader 负责实际挂载。[第一个 Cordis 插件](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/docs/cordis-tutorial/01-first-plugin.zh.md) + +Fiber 负责把一次插件应用变成可观察、可等待、可失败、可卸载的运行实例。它记录所需 Service 的具体实现;提供方变化时,Cordis 可以比较依赖并只重载受影响的插件,而不是重启整个进程。 + +### 依赖层:Service 与 `inject` + +Service 是插件提供给其他插件的具名能力,例如 `ctx.llm`、`ctx.tools` 和 `ctx.sessions`。消费者声明 `inject: ['tools']`,只表示“我需要工具服务”,不绑定某一个具体提供包。部署可以替换 provider,而消费插件代码无需改变。 + +`inject` 是硬依赖:缺失时 Fiber 保持 `PENDING`。可选能力则不应写入 `inject`,而是在使用处通过 `ctx.get(...)` 探测。服务名称共享一个命名空间,这也意味着大型部署必须治理名称、提供方和配置来源。 + +## 请求执行:一次消息如何经过 Agent loop + +![一次 greet 请求:消息在已启动组件之间移动,SessionEvent 是记录旁路](/articles/deepseek-harness-architecture-evaluation/dsh-greet-request.svg) + +这张图只回答运行问题。系统已经完成装配,因此请求链中不再出现 Profile、Bundle 和 Loader: + +1. Web、ACP 或 SDK 入口把用户输入交给 Agent inbox。 +2. Agent loop 取得输入,组装系统提示、历史和工具 schema。 +3. Agent loop 调用 LLM;模型可以直接回答或请求工具。 +4. 工具调用进入统一 Tools Runtime,经过策略、审批、guard 和执行阶段。 +5. 工具结果回到 Agent loop,再进入下一次模型 step。 +6. 模型生成最终消息,Agent loop 提交结果并在没有待处理工作时回到 idle。 + +dsh 同时存在两类容易混淆的“事件”。`SessionEvent` 是写入会话日志的 durable fact,承担重建、恢复和投影;`agent/pre-step`、`agent/request`、`tools/pre-execute` 等 live event 或 waterfall 是运行时扩展点,插件可以监听、修改、放行或短路当前过程。前者回答“发生过什么”,后者参与“现在怎样继续执行”。 + +## 插件树、依赖图和调用链不是同一张图 + +| 关系 | 它回答的问题 | `greet` 例子 | |---|---|---| -| Cordis 插件 | 进程内:TypeScript `apply(ctx, config)`、`inject`、Service、Event、effect | 增加或替换模型、工具、会话、策略、loop 和 UI 组件 | -| Profile / Bundle / patch | 部署时:YAML 配置行与稳定 `id` | 选哪些插件、如何覆盖默认值;它不是远程协议 | -| Web | 浏览器 → dsh:HTTP POST 一元调用;dsh → 浏览器:WebSocket 事件下行 | 产品 UI、会话展示和交互式操作;业务 Remote 由 Typert 生成严格描述 | -| ACP | 外部 Agent 客户端 ↔ dsh:Agent Client Protocol over newline-delimited JSON-RPC stdio | 标准化的 `session/new`、`session/prompt`、取消和一次性权限回答 | -| Python / TypeScript SDK | 评测器或程序 ↔ dsh:项目自有的逐行 JSON-RPC 2.0 stdio | 批量任务、自动化和 benchmark runner;方法为 `initialize`、`session/prompt`、`shutdown` 及事件/状态通知 | -| MCP client | dsh ↔ 外部工具服务器:MCP stdio 或 Streamable HTTP | 发现工具并注册为 `mcp____`;当前不桥接 Resources 和 Prompts | +| 插件父子与生命周期 | 谁由谁挂载,父节点卸载时谁一起清理 | Loader 挂载 `greet-tool` Fiber;删除配置时旧实例被清理 | +| Service 依赖 | 谁必须等待谁,提供方变化时谁需要重载 | `greet-tool` 通过 `inject` 等待 `tools` | +| 请求调用 | 一条消息在运行时经过哪些已经激活的组件 | Agent loop → LLM → Tools → `greet` → LLM | -关键区别是:**Web 是浏览器产品面;ACP 和 SDK 是“从外部驱动整个 Agent”;MCP 是“把外部工具接给 Agent”;Cordis 则是“在进程内改变 Agent 由哪些部件构成”。** 它们解决的问题不同,不应放在同一层比较。ACP 只暴露基线自动化能力,而 SDK 线协议当前没有版本协商和单 prompt 取消,都不能等同于 Web 产品面的完整功能。[ACP 协议契约](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/acp/acp/README.md) [SDK 线协议](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/sdk/protocol/README.md) [Web Connection](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/client/connection/README.md) [MCP client](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/mcp/mcp-client/README.md) +所谓“Cordis 插件树”首先是一套运行实例的所有权结构;再叠加 `inject` 后,形成服务依赖图。一次请求的调用链则发生在这些实例都准备好之后。三者可能涉及相同插件,但箭头含义不同,不能用一棵树同时表示。 + +## Web、ACP、SDK、MCP 与 Cordis 的边界 + +![DeepSeek Harness 的进程与协议边界](/articles/deepseek-harness-architecture-evaluation/dsh-protocol-boundaries.svg) + +先看进程边界,再看协议名称:Web、ACP 和 SDK 把请求从外部送入 Agent;MCP 把外部工具送入 Tools;Cordis 位于 dsh 的 Node.js 进程内部,负责组件装配,不是远程协议。 + +| 接入面 | 连接方向与载体 | 解决的问题 | +|---|---|---| +| Cordis 插件 | dsh 进程内的 ESM 模块:`apply`、Service、Event、effect | 增加或替换模型、工具、会话、策略、loop 和 UI 组件 | +| Web | 浏览器 → dsh 为 HTTP POST;dsh → 浏览器为 WebSocket 事件 | 产品 UI、会话展示和交互操作 | +| ACP | 外部 Agent Client ↔ dsh,newline-delimited JSON-RPC over stdio | 创建会话、发送 prompt、取消和一次性权限回答的基线自动化 | +| Python / TypeScript SDK | 评测器或程序 ↔ dsh,项目自有 line JSON-RPC 2.0 over stdio | 批量任务、自动化和 benchmark runner | +| MCP client | dsh ↔ 外部 MCP Server,stdio 或 Streamable HTTP | 发现外部工具并注册为 `mcp____`;当前只桥接 Tools | + +Profile、Bundle 和 patch 不在表中,因为它们是部署配置方式,不是通信协议。ACP 也不等于完整 Web 产品面:它只暴露自动化基线;SDK 线协议当前没有版本协商和单 prompt 取消;MCP 当前不桥接 Resources 和 Prompts。[ACP 协议契约](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/acp/acp/README.md) [SDK 线协议](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/sdk/protocol/README.md) [Web Connection](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/client/connection/README.md) [MCP client](https://github.com/deepseek-ai/deepseek-harness/blob/47f943859bef60e4160492346772ded9b24f765a/packages/mcp/mcp-client/README.md) + +## 架构判断 + +1. **最值得关注的是扩展边界。** dsh 允许替换的不只包括工具和模型适配器,还包括会话、Agent loop、工具注册表、策略、持久化和 UI。Profile、Bundle 与 patch 可以逐层改写配置,比“在固定 Agent 核心外围加插件”更激进。 +2. **Cordis 解决的是组合变化。** Fiber、Service、`inject` 和 effect 让插件能随配置与依赖变化安全地加载、等待、卸载和重建;它并不替代 Agent loop。 +3. **Agent loop、工具管线和事件日志形成执行主链。** loop 决定下一步,Tools 执行动作,SessionEvent 保留可恢复事实;三者职责分开。 +4. **工程质量证据多于效果证据。** 仓库有高覆盖率单元测试、keyless snapshot、真实 API e2e 入口、Web 性能/压力 runner 和 CI runner benchmark;但在冻结范围内没有发现公开的 Agent 任务成功率成绩。 +5. **现在的合理动作是观察或隔离试点。** Developer Preview、兼容性主动破坏、会话格式 v0、无正式 GitHub Release,以及缺少可复现的目标任务评测,都会阻断生产级采用。 ## 这套设计解决了什么 diff --git a/public/articles/deepseek-harness-architecture-evaluation/dsh-greet-request.svg b/public/articles/deepseek-harness-architecture-evaluation/dsh-greet-request.svg new file mode 100644 index 0000000..5a7d810 --- /dev/null +++ b/public/articles/deepseek-harness-architecture-evaluation/dsh-greet-request.svg @@ -0,0 +1,84 @@ + + DeepSeek Harness greet 请求调用图 + 用户请求进入已经启动的 Agent,模型调用 greet 工具并返回结果;SessionEvent 在下方记录事实,但不是调用链的下一跳。 + + + + + + + + 一次 greet 请求:消息在已启动的组件之间怎样移动 + 这是一张运行时调用图;Profile、Bundle 和 Fiber 装配已经完成。 + + + 用户入口 + Web / ACP / SDK + + + Agent inbox + “向 Ada 问好” + + + Agent loop + 组装历史、系统提示 + 和可用工具 schema + + + LLM + 决定调用工具: + greet({ name: "Ada" }) + + + Guarded tool pipeline + pre-execute → approval → guard + → execute → post-execute → result + 最终调用 greet handler + + + + + + + + greet 工具返回 + “Hello, Ada!” + + + + LLM 继续生成 + 把工具结果写成回答 + + + + Agent loop 收束 + 提交 assistant/message + + + + 用户收到回答 + “已向 Ada 问好” + + + + SessionEvent 日志:记录旁路,不是请求的“下一跳” + 用户消息、step/start、模型消息、工具调用、工具结果和最终回答分别追加为 durable facts。 + 历史重建、resume、fork、故障修复和 UI 投影都从这些事实派生。 + + + + 记忆锚点:Agent loop 决定“下一步做什么”;Tools 执行“具体动作”;SessionEvent 记录“发生过什么”。 + diff --git a/public/articles/deepseek-harness-architecture-evaluation/dsh-protocol-boundaries.svg b/public/articles/deepseek-harness-architecture-evaluation/dsh-protocol-boundaries.svg new file mode 100644 index 0000000..fd3f420 --- /dev/null +++ b/public/articles/deepseek-harness-architecture-evaluation/dsh-protocol-boundaries.svg @@ -0,0 +1,80 @@ + + DeepSeek Harness 协议边界图 + Web、ACP 和 SDK 从外部驱动 Agent;MCP 把外部工具接入 Tools;Cordis 位于 dsh 的 Node 进程内部,负责组件装配而不是远程通信。 + + + + + + + + 协议边界:谁从哪里接进 Harness + 先看进程边界,再看协议名称;Cordis 位于 dsh 进程内部,不是远程协议。 + + dsh 进程外:驱动端 + + 浏览器 + 产品 UI 和交互 + + ACP Client + 编辑器或另一个 Agent + + Python / TS 程序 + 自动化、批量任务、评测器 + + + dsh:TypeScript / Node.js 进程 + + + Web adapter + HTTP POST / WebSocket + + ACP adapter + ACP JSON-RPC over stdio + + SDK server + line JSON-RPC 2.0 over stdio + + + Agent loop + Web / ACP / SDK + 都从外部驱动整个 Agent + + Tools registry + 本地工具与 MCP 工具 + 以统一名称注册 + + + Cordis:装配并管理以上进程内插件 + + + + + + + + + + dsh 进程外:能力提供端 + + MCP Server + stdio 或 Streamable HTTP + 当前只桥接 Tools + + + + 记忆锚点:Web / ACP / SDK 把“请求”送进 Agent;MCP 把“工具”送进 Tools;Cordis 只负责 dsh 进程内的组装与生命周期。 + diff --git a/public/articles/deepseek-harness-architecture-evaluation/dsh-startup-assembly.svg b/public/articles/deepseek-harness-architecture-evaluation/dsh-startup-assembly.svg new file mode 100644 index 0000000..d4322e9 --- /dev/null +++ b/public/articles/deepseek-harness-architecture-evaluation/dsh-startup-assembly.svg @@ -0,0 +1,93 @@ + + DeepSeek Harness 启动装配图 + Profile、Bundle 和 patch 先形成有效配置,Cordis Loader 再创建插件 Fiber;虚线表示服务依赖,不表示请求调用顺序。 + + + + + + + 启动装配:谁被创建、谁等待谁 + 这是一张依赖和生命周期图,不是一次用户请求的调用顺序。 + + 第一步:多层配置合成最终插件清单 + + Bundle + 可复用的默认插件组合 + + Profile patch + 为某种产品形态做选择 + + Harness home patch + 用户或部署环境覆盖 + + CLI --patch + 本次启动覆盖 + + 有效配置行 + 按稳定 id 得到结果 + + + + + 后层命中同一 id 时,整段 config 替换,不做深合并 + + + 第二步:Loader 为配置行创建 Fiber;Fiber 是否启动由服务依赖决定 + + Loader 创建 Fiber + + 消费者 → 提供方 + + Root Context + Loader + 读取有效配置,为每一行挂载运行实例 Fiber + + + LLM provider Fiber + 提供 ctx.llm + ACTIVE + + + Tools provider Fiber + 提供 ctx.tools + ACTIVE + + + greet-tool Fiber + inject: tools + PENDING → ACTIVE + + + Agent loop Fiber + inject: agents, sessions, + llm, tools, systemPrompt + 依赖齐全后才能处理请求 + + + + + + + 等待 tools + + 等待 llm + + 等待 tools 等服务 + + + 记忆锚点:配置行决定“挂载什么”;inject 决定“何时能启动”;Fiber/effect 决定“变化时怎样卸载和重建”。 +