Files
deepseek-harness/docs/rfc/implemented/architecture/2026-06-20-extract-example-app-packages.zh.md
ZiyaZhang 8ea5cdd894 docs(i18n): re-translate RFC batch with the prompt-v4 pipeline
146 篇 RFC 译文按 v4 基线(#348)重出:v4 模板+术语表、金标
few-shot、三段协议、切换行后处理;全量机械核对零异常(一处
task id 术语违规已修)。三篇超长 RFC(code-mode 已入,web-seam/
agent-scope/sandbox/cds-core 仍在长文档通道产出)随后补。
2026-07-22 03:07:36 -07:00

7.4 KiB
Raw Blame History

RFC:将示例应用提取为独立包

English | 中文

Status: implemented

问题

示例目录本应是精简的——只包含演示的可变接线,而非演示的基础设施。在此次变更之前,它是臃肿的。每个示例都携带一份手写的 start.ts 启动引导、一段基础设施前导(timer,以及 stdio 演示所需的 logger + hmr(热模块替换))、三个共享 YAML 片段的嵌套引用(base.yml / base-core.yml / acp-agent/acp-tail.yml),还有各示例自身的 agent-loop/persistence/system-prompt 配置。真正的应用——每个 agent(智能体)都需要的服务主干——散落在叶子配置和那些 include 中。

叶子配置还拥有一个耦合的前门。ACP(Agent Client Protocol)要求 stdout 纯净,并通过 session/new 创建 agent;stdio 则需要一个控制台 logger 和一个预创建的 main。防止错误组合的唯一屏障是文档中的文字警告,而三个 start.ts 文件重复着 Loader 引导和生命周期代码。

决策

每个示例现在主要是对一个应用包(package)的调用,沿着既有的接口 / 实现 / 消费方 seam 拆分接线:应用包拥有组合,叶子 cordis.yml 只拥有可替换的选择(哪个 LLM(大语言模型)适配器、哪个 bash 执行器、模型、提示词、持久化根目录)。

  • @deepseek-ai/dsh-agent-spine-demo(packages/examples/agent-spine-demo)组合了不含 provider、不含执行器、不含 UI 的主干,并转发 agent loop(智能体循环)的 agent 列表配置。它对具体 loop 的依赖是有意为之,因为该包组合的是主干而非扩展主干;替换 loop 意味着提供另一个 bundle。
  • @deepseek-ai/dsh-stdio-demo(packages/examples/stdio-demo)和 @deepseek-ai/dsh-acp-demo(packages/examples/acp-demo)各自内置了前门。Stdio 包含 ui-stdio、控制台 logger 和 main;ACP 包含 bridge 和 JSONL 持久化,但不含 stdout logger 或预创建的 agent。叶子可以添加插件,但安全的组合现在是默认产物。
  • start.ts 已移除。 每个应用包暴露一个 bin(dsh-stdio-demo / dsh-acp-demo);demo:* 脚本调用它(例如 dsh-stdio-demo ./cordis.yml)。Loader 引导尾部、.env 加载和快速失败守卫位于共享的 @deepseek-ai/dsh-app-boot 包(在逐文件覆盖率门禁下有单元测试——见共享应用 bin 的启动胶水);每个 bin 是一个精简的自执行组合,基于这些辅助函数加上其应用特有的生命周期逻辑(ACP bin:快照模式选择与 stdin-dispose)。bin.ts 文件本身仍被排除在覆盖率之外(自执行 CLI(命令行界面)入口,与旧的 start.ts 性质相同),由 keyless 的 Loader 路径测试驱动。
  • 每个叶子 cordis.yml 精简为后端 + 配置:LLM 适配器(带 apiKey/models 的 llm-deepseek,或 llm-replay)、bash 执行器(bash-local)、stdio 演示的 hmr(见下方修正),以及一个承载应用配置的 app 条目(模型、系统提示词、持久化根目录——以应用包自身的 Config 形式暴露,由它将各值路由到应用接线的目标位置:stdio 路由到预创建的 agent,acp 路由到 bridge 插件)。
  • echo-agent 折叠到 dsh-stdio-demo 上,将 LLM 后端替换为本地的 mock-llm,并在叶子层添加本地的 echo-tool(加上 bash-local,由主干的 tool-bash 注入)——这是「替换后端、保留应用」的干净示范。mock-llm.ts / echo-tool.ts 作为示例本地的教学插件保留。
  • base.yml、base-core.yml 和 acp-agent/acp-tail.yml 已退役——它们共享的主干现在位于 dsh-agent-spine-demo 中。

bash-local 和 LLM 适配器仍然是叶子选择:bundle 提供 tool-bash(消费方 schema),叶子选择执行器实现,因此沙箱执行器或回放适配器无需触碰应用即可替换。

实现修正:hmr 保留为叶子条目

提案最初将 hmr 列入 stdio 应用内置的前门集群。对照代码验证后发现,将 hmr 内置到 dsh-stdio-demo 包中会在两个方面与 Cordis 冲突,因此改为作为叶子 cordis.yml 条目交付:

  1. @cordisjs/plugin-hmr 是一个仅限 Loader、仅限子进程的开发插件——其构造函数在没有 node --expose-internals 和活跃的 loader 服务时会抛出异常,因此只能在真实的 demo:*/bin 子进程中运行,不能在进程内的单元/覆盖率测试层运行。
  2. 进程内测试层(vitest)甚至无法导入 vendor 的 hmr 模块(其 class-decorator @Inject 形式在 Vite 的 transform 下会失败),因此一个 apply 静态导入了它的包永远无法满足其主函数的逐文件 100% 覆盖率门禁。

关键在于,hmr 不是像控制台 logger 那样的 stdout 纯净隐患:ACP 配置中误加 hmr 不会破坏 JSON-RPC 帧,因此将它留在叶子层不会损失耦合论证所关注的安全性。logger(真正的耦合点)保持内置:stdio 应用包含它,ACP 应用省略它。

曾考虑的替代方案

为什么不继续用共享 YAML include 来管理接线?

旧的 base*.yml/acp-tail.yml include 已经去重了配置,但 YAML include 无法封装前门耦合——它只能在注释中描述,并信任每个叶子遵守。它也无法拥有 bin,因此启动胶水一直在三个 start.ts 文件中重复。包将「ACP 应用绝不向 stdout 输出日志」从文字警告变成了产物的属性:叶子中不存在可以写错的 logger 条目。

验证

  • 示例目录只包含配置、README 和测试:start.ts、基础设施前导和共享 YAML include 已移除。
  • demo:echo、demo:repl 和 demo:acp 调用应用包的 bin。
  • 每个新包都有 README 和逐文件 100% 覆盖率;每个应用包还有一个 keyless 的真实 Loader 路径 bin 冒烟测试,用于捕获事后分析 0001 中描述的导出形状故障。
  • ACP 回放 transcript(文本记录)保持不变,因为插件集合和加载顺序未改变。

后果

  • 裸插件树的教学性。 echo-agent 内联的 cordis.yml 曾一次展示所有插件;主干现在隐藏在 bundle 之后,查看完整树意味着打开 dsh-agent-spine-demo。应用包的 README 承担了这份教学职责。
  • 多了一层间接。「这个演示加载了什么?」从扫描单个 YAML 变成了阅读一个包。

相关

  • 取代 Make the shared example base providerless:一旦主干移入 dsh-agent-spine-demo 且 base*.yml 文件被删除,将 base.yml 重命名为无 provider 核心便不再有意义。
  • 基于 capability-seams 的接口/实现/消费方拆分——后端和展示层保持为叶子选择;主干是共享 bundle。
  • 与 Reorganize packages into a modular hierarchy 互补:新的 app/core 包按该层级结构归入既有分组(core 放可复用的主干 bundle,ui 放应用特有的前门)。