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 是什么。
阅读配置的五步法
- 找入口命令选择的 Profile。
- 找 Profile 列出的 Bundle。
- 展开 Bundle 插入的配置行。
- 按 id 检查后续 patch 是否替换这些行。
- 用
--dump-config验证最终插件树,而不是只看源文件。
常见误解
“包在 workspace 中,所以运行时一定加载”
错误。源码存在、依赖安装和插件挂载是三件不同的事。
“Tool 包存在,所以模型一定看得到工具”
错误。工具插件必须被加载、注册成功、通过 scope 和限制策略,并在当前 System Prompt 组装中贡献 schema。
“换 Profile 就是换一份环境变量”
错误。Profile 选择的是一组 Bundle 和 patch,可能改变完整插件树。
本篇练习
- 阅读
examples/headless-agent/cordis.yml,列出它显式加载的叶节点能力。 - 阅读
agent-spine-demo的插件树,标出哪些是抽象 Service、哪些是 Tool Consumer。 - 运行一次
--dump-config,搜索agent-loop、llm和tool-bash。 - 假设要把本地文件系统换成 E2B,说明应该替换 Provider 还是修改 AgentLoop。
验收标准:能从启动命令追踪到 Profile、Bundle、最终插件行和对应 Service 注册。