乐于分享
好东西不私藏

DeepSeek Harness 源码研究(八):同一套 Agent 如何变成 Web、API、SDK 与 ACP

DeepSeek Harness 源码研究(八):同一套 Agent 如何变成 Web、API、SDK 与 ACP

同一套 Agent 如何变成 Web、API、SDK 与 ACP

一个 Agent 项目从命令行长成产品,通常会经历一次危险的复制:CLI 有一套状态,Web API 再包一层,前端为了性能建立第二套业务模型,SDK 又发明第三套协议。功能越多,入口之间越难保持一致。

dsh 的路线是把 Host 业务、网络合同、Client Runtime 和 UI 投影分层,同时让它们共享 Session 事实和 Agent 生命周期。

第一层:Web 不是另一套 Agent,而是另一个 Bundle

Base bundle 已经包含模型、Session、工具、Agent Loop、持久化、政策和大部分能力。dsh-web-app 在其上添加 WebServer、Host API、客户端模块与前端应用;dsh-headless 则添加一次性 runner。

这意味着 Web 和 headless 的差异主要在宿主与组合,而不是复制主循环。用户通过 npx @deepseek-ai/dsh web 启动的,是一个特定 profile 求值出的插件树。

客户端显示聊天时,也不是从终端 ANSI 文本做解析。Host 发送 Session event、queue/status 和业务 RPC;Client Runtime 按领域把事件折叠成 Session list、Conversation Nodes、Trajectory、Tool card、Workspace 和 Settings 状态。

第二层:Typert 让业务 API 从 TypeScript 类型图生成

dsh 的 Typert API Gateway 只暴露显式标注的业务方法。服务方法使用 @Remote 或 @RemoteScope;未标注方法不会进入生成的 Client 类型和 runtime contribution,也无法通过 ctx.remote 调用。

复杂 Host 对象不能直接过线。例如业务方法接收 Agent,wire contract 会携带 agentId;Gateway 通过注册的 lookup 在 Host 解析 id,再调用 live service。Scoped method 则先把 identity 解析为某个 Agent Context,再从该 Context 获取 service。

生成管线同时产生 Host descriptor、schema 与 Client concrete methods。客户端不是用 JavaScript Proxy 猜方法,而是获得普通对象上的真实函数,如 ctx.remote.goals.create()。请求和返回值在边界验证,AbortSignal 以协议支持的取消方式传递。

这种设计的价值是把“哪些方法允许远程调用”变成显式编译面,而不是把整个内部 service 暴露成 RPC。Host 与 Client build 也不进入同一个 TypeScript Program,避免浏览器端意外吸入 Node 依赖和 Host Context merge。

流式 Session events 不强行塞进 unary Remote。它们可以复用 Connection carrier,却有独立 mux 与 schema。这是一条好边界:命令调用和事实流虽然共享网络,语义并不相同。

Client Runtime 同样采用 Cordis 插件。Conversation Node Definition 将一条事件匹配到稳定 business id,创建 state,折叠相关更新,最终按 target 构建 Chat 或 Trajectory 节点。流式 chunk 可以按 animation frame 限流物化,最终 message 和 Turn/Step closure 则立即发布。工具卡片使用工具定义的 replay-safe presentation,而不是在 React 中按 name 写巨型 switch。

第三层:多入口复用的难点,是合同而不是 UI

JS SDK 可以通过 Connection 操作同一 Host;Python SDK 还提供 bundled runtime 的平台解析和 smoke test,降低终端用户单独安装 Node 与 workspace 的门槛。ACP package 则把 Agent 能力接入支持 Agent Client Protocol 的编辑器或宿主。

对产品团队,这形成三个层次的复用:

  • 运行能力复用
    不同宿主共享 Agent、Session、Tools 和 Provider;
  • 网络合同复用
    Host business methods 通过 Typert 生成,事件通过共享 schema;
  • 展示语义复用
    Client plugins 从 Session facts 构建节点,工具自己声明展示意图。

但产品化复杂度不会消失。浏览器有断线重连、baseline 与增量帧竞态、乐观排序、跨 tab 同步、Session 分页、工具子调用树和 pending interaction。Client Runtime README 里大量篇幅都在处理“旧响应不能覆盖新状态”和“baseline 在请求期间收到增量后如何重放”。这说明真正的 Web Agent 不是给 API 加一个 React 页面。

Python bundled runtime 也扩大了发行矩阵:平台二进制、Node/PTy 原生依赖、macOS deployment target、包版本和 SDK/runtime compatibility 都需要持续验证。

对老板而言,最值得评估的是入口复用率。如果 Web、内部服务、IDE 和自动化都需要同一 Agent 能力,这种 Host/Client/SDK 分层有长期价值。如果只计划一个单端产品,完整 Typert + Client plugin graph 的收益未必覆盖成本。

同一套 Agent 能否变成多个产品,取决于三个问题:业务服务有没有明确的远程暴露面,事实流有没有稳定 schema,客户端是否从事实投影而不是复制业务真相。dsh 在这三点上给出了一套相当完整的答案。

源码核验索引

  • Web bundle:packages/bundle/web-app/cordis.patch.yml
  • Typert Gateway:docs/api-gateway.mdpackages/typert/
  • Host API Proxy:packages/host/apiproxy/README.md
  • Client Runtime:packages/client/runtime/README.md
  • Python SDK:python/sdk/python/sdk-runtime/
  • ACP:packages/acp/acp/

事实边界:本文分析架构复用,不代表所有入口功能完全等价。具体能力取决于 profile、Client assembly、协议能力声明与平台运行时。