3.3 KiB
Agent Note: 能力 seam——接口/实现/消费方三分
Status: implemented
English | 中文
问题
harness 具有可替换的能力:当前是 bash 执行,未来会有沙箱化/远程执行器和替代模型提供方。一项能力涉及三个关注点,它们以不同速率、因不同原因变化:契约(这项能力是什么)、实现(它如何运行)、消费方接口(模型和其他插件面向什么编程)。将三者捆绑在一个包(package)中会耦合这些变化速率——把本地执行器换成沙箱化执行器时,模型看到的工具 schema 也会被搅动,尽管面向模型的契约从未改变。
这与「谁在运行时提供、谁需要一项能力」是不同的问题,后者 Cordis 已通过服务 + inject 解决(提供方注册 ctx.bash;消费方声明 inject: ['bash'],其 fiber 挂起直到服务存在)。该机制是必要的,但不决定包的边界;本 Agent Note 决定的是包的边界。
决策
一项可替换的能力由三个包构成:
- 接口——一个抽象服务加词汇类型,拥有
ctx.<key>,仅依赖其词汇依赖(例如dsh-bash:BashExecutor、BashRunResult、BashProcess)。 - 实现——一个具体子类,以插件形式加载(例如
dsh-bash-local:子进程、进程组 kill、溢出文件截断)。沙箱化/远程后端是实现同一接口的兄弟包。 - 消费方——模型和插件看到的内容(例如
dsh-tool-bash:bashschema,后台句柄注册到通用任务运行时)。消费方inject接口键,从不导入实现类型。
实现与消费方由此独立演进:沙箱化执行器替换 dsh-bash-local 时无需触碰任何工具 schema。
当各部分确实属于同一个关注点时,三分并非强制:LLM(大语言模型) seam 将接口 + 消费方合并为 dsh-llm(消费方是 agent loop(智能体循环)本身,而非可替换的 schema 表面),适配器作为实现包。不要预防性地拆分——如果一项能力只有一种可设想的实现和一个消费方,就保持为一个包,直到第二种出现。
曾考虑的替代方案
- 单一合并包:否决。因为它重新耦合了三分设计本要分离的三种变化速率(这正是拆分的意义所在)。
@cordisjs/plugin-capability:这是完全不同的维度。它是一个权限/能力安全服务(具名权限加继承,通过ctx.capability.test对会话进行检测),是延后的权限/沙箱工作(tools/pre-executedeny/ask seam)的候选方案,不是替换实现的机制。混淆这两个「能力」概念正是本 Agent Note 所指出的陷阱。
后果
每项能力需要更多包和更多样板代码(一组 package.json/tsconfig/README,加上 inject 接线)。换来的是:实现与消费方独立发布和版本管理,新后端永远不会波及面向模型的契约。该规则记录在 AGENTS.md § Conventions(「Capability seams are three packages」)和 architecture.md §「Capability seams」中;bash 三件套是参考模板。何时合并、何时拆分是一个判断问题,架构文档对此有详细说明——本 Agent Note 记录的是为什么默认选择拆分。