CHAPTER 05 · STAGE 01 · 建立项目地图

配置与组合:Profile、Bundle 和 cordis.yml

运行 dsh --profile headless 与运行 Web 版本时,二者使用许多相同核心插件,但入口、UI、协议和部分能力不同。项目没有为每种产品复制一套 AgentLoop,而是通过配置层组合插件树。

为什么启动命令背后不是一份固定程序

运行 dsh --profile headless 与运行 Web 版本时,二者使用许多相同核心插件,但入口、UI、协议和部分能力不同。项目没有为每种产品复制一套 AgentLoop,而是通过配置层组合插件树。

理解配置后,你才能回答“这个 Provider 到底在哪里加载”“为什么源码存在但运行时没有这个工具”“怎样只给某个部署换后端”等问题。

cordis.yml 描述的是插件树

配置行不只是给一个函数传参数。每一行可以挂载插件、指定 id、传 config、声明子节点、禁用条目或通过 include 引入其他组合。

阅读一个 cordis.yml 时,先忽略所有具体字段,只列出插件名和层级。第二遍再看每个插件的 config。这样能把“加载了谁”和“如何配置它”分开。

一个最简抽象如下:

- id: runtime
  name: some-runtime-plugin
  config:
    option: value
  children:
    - name: child-plugin

真实格式以仓库示例和配置目录为准。不要根据这段抽象示意复制生产配置。

Bundle:可叠加的配置与代码层

Bundle 是可安装的组合层。它声明自己插入哪些 Cordis 配置行以及这些行需要的包。

基础 Bundle 提供模型、工具、持久化、沙箱、设置和凭据等共同能力。Web Bundle 在它上面加入浏览器应用,Headless Bundle 加入一次性运行入口。

Bundle 的价值是共享“一个可修改的默认组合”,而不是把配置编译成不可覆盖的黑盒。

Profile:一次实际运行选择哪些 Bundle

Profile 是部署级组合。它列出需要叠加的 Bundle,并拥有自己的 cordis.patch.yml。用户可以在不修改 Bundle 源码的情况下替换某行配置或插入新插件。

可以把两者区分为:Bundle 回答“这类产品默认包含什么”,Profile 回答“我这台机器上的这次部署采用哪些层和覆盖”。

配置层的应用顺序

运行时从空的条目列表开始,依次应用 Profile 列出的 Bundle,然后应用 Profile 自己的 patch、Harness home 级 patch,最后应用命令行提供的 overlay。

后面的层可以按 id 替换前面的一整行 config,也可以插入新行。这就是为什么稳定 id 很重要:覆盖需要准确指向目标条目。

“后层覆盖前层”不等于所有对象做深度合并。补丁可能替换整份 config,所以修改时要查清拥有该字段的插件 schema。

组合包:用代码挂载一组固定子插件

agent-spine-demo 是理解完整主干的最佳入口。它的 apply(ctx, config) 用代码挂载 LLM 注册表、Session、System Prompt、Tools、Agent、AgentLoop 等共享组件。

代码组合包适合拥有固定服务集合和转发入口默认值。YAML include 适合复用配置片段,但不能替代拥有 bin、生命周期和代码级默认值的应用组合。

阅读:packages/examples/agent-spine-demo/README.zh.md 与 packages/examples/agent-spine-demo/src/index.ts。

为什么具体后端留在组合包外

主干只交付抽象 LLM Service,但叶节点选择 DeepSeek、Replay 或其他 Adapter。主干交付面向模型的 Bash Tool,但叶节点选择本地 Shell、沙箱 Shell 或远程环境。

如果组合包把具体后端写死,替换 Provider 就要复制整个主干。把后端留给叶节点,才能用同一 AgentLoop 组合不同运行环境。

!!js 与动态配置

Loader 可以在插件 config 和 disabled 字段中解析 !!js 表达式。表达式在声明的注入激活后,对相应 Context 求值。

这种能力适合从已有 Service 计算配置,但不应把整套部署选择藏进难以观察的表达式。环境选择通常更适合显式 overlay。

普通 metadata 保持字面量,标签是 !!js,不是 !js。错误配置应尽早失败,而不是静默跳过缺少的引用。

如何观察实际启动的插件树

不要只凭源码猜运行时组合。产品支持输出解析后的配置树:

pnpm dsh --profile web --dump-config

如果当前只关心 Headless,可以替换 profile 名。输出能帮助你确认某插件是否真正加载、哪一层提供了配置、目标行的 id 是什么。

阅读配置的五步法

  1. 找入口命令选择的 Profile。
  2. 找 Profile 列出的 Bundle。
  3. 展开 Bundle 插入的配置行。
  4. 按 id 检查后续 patch 是否替换这些行。
  5. 用 --dump-config 验证最终插件树,而不是只看源文件。

常见误解

“包在 workspace 中,所以运行时一定加载”

错误。源码存在、依赖安装和插件挂载是三件不同的事。

“Tool 包存在,所以模型一定看得到工具”

错误。工具插件必须被加载、注册成功、通过 scope 和限制策略,并在当前 System Prompt 组装中贡献 schema。

“换 Profile 就是换一份环境变量”

错误。Profile 选择的是一组 Bundle 和 patch,可能改变完整插件树。

本篇练习

  1. 阅读 examples/headless-agent/cordis.yml,列出它显式加载的叶节点能力。
  2. 阅读 agent-spine-demo 的插件树,标出哪些是抽象 Service、哪些是 Tool Consumer。
  3. 运行一次 --dump-config,搜索 agent-loop、llm 和 tool-bash。
  4. 假设要把本地文件系统换成 E2B,说明应该替换 Provider 还是修改 AgentLoop。

验收标准:能从启动命令追踪到 Profile、Bundle、最终插件行和对应 Service 注册。