深入多工具适配器设计
对 Apothem 的 HarnessAdapter 协议以及把一份配置文件物化为十七个工具的三种适配器类的技术深度解析。
Apothem 将一份共享配置档案物化为十七种工具原生配置。十七个目标、 十七种厂商格式、十七个安装位置——而操作者与 CLI 共同依赖的,是一个 统一的接口。本文阐释适配器层如何把这份契约维系在一起。
HarnessAdapter 协议
每个工具适配器都实现一个结构化的 HarnessAdapter 协议。该协议是
CLI 与厂商专属细节之间的接缝。它声明了 CLI 调用的操作以及 CLI 读取的
元数据:
name— 适配器注册的标识符。output_path— 物化后的配置在磁盘上的落点。install— 渲染并写入工具的原生配置。update— 配置档案变更后,重新渲染工具的原生配置。uninstall— 移除物化后的配置,共享配置档案保持不变。is_installed— 报告该工具的配置是否存在。verify— 检测配置档案与磁盘配置之间的漂移。
由于该契约是一个 typing.Protocol,适配器靠形态而非继承去满足它。
在运行时,CLI 从一个静态注册表解析每个适配器——即
src/apothem/lib/harness_registry_data.py 中的 HARNESS_REGISTRY 表,
harness_registry.py 将其声明为在运行时具有权威性——因此普通检出无需
sys.path 上存在任何已安装的入口点元数据,即可加载每个适配器。
pyproject.toml 中的 [project.entry-points."apothem.harnesses"] 表,
以及基于文件系统的 discover_adapters() 扫查,是一项一致性对等检查,
而不是运行时解析器:一项测试断言所发现的适配器集合等于静态注册表,
因此一个约定正确但未注册的适配器会作为覆盖回归浮现出来,
而不是被静默地解析。新增一个工具因而意味着在注册表数据中注册它
(并为保持打包对等而在入口点表中予以镜像)——若漏掉其中任何一处,
对等测试都会显式失败。
每个适配器一个子包
每个适配器都驻留在 src/apothem/harnesses/<name>/ 下它自己的子包里:
__init__.py— 实现协议并委托给同级动作模块的<Name>Adapter类。install.py/uninstall.py/update.py/verify.py— 适配器类 导入并调用的逐动作实现。materializer.py— 一个可选的materialize_native_config(profile) -> str, 它把共享配置档案渲染为工具的原生配置文本。
把每个动作拆进它自己的模块,使适配器类保持轻薄:它是一个调度器,把
协议方法接到动作函数上。打开 verify.py 的读者所见,正是那一个编码
助手的漂移检测逻辑,无需在安装或卸载的噪声中费力穿行。
三种适配器类
这十七个工具并非都以相同方式消费配置。Apothem 按配置文件在抵达 工具之前需要多少转换,把它们分入三个类别。
第一类——原始传播
工具消费的正是 Apothem 已经撰写的同一批工件——扁平的 .md 规则、
agents、钩子——无需渲染步骤。适配器把原始模板复制进工具的配置树。
这里没有 materializer.py,因为没有任何东西被渲染;配置文件的工件即是
输出。
第 II-A 类——原始外加厂商模板
工具消费 Apothem 的原始工件,并且还需要一组厂商专属的模板文件
(工具所需的设置、清单或脚手架文件)。适配器把原始工件与厂商模板
一起复制。厂商模板与适配器并列,存放在一个 templates/ 目录下。
第 II-B 类——配置文件渲染
工具想要一份从共享配置档案的结构化字段渲染出来的单一原生配置文件。
适配器的 materializer.py 读取配置文件并发出工具原生的文本——一份
Hermes 的 config.yaml、一份 Open-Claw 的 openclaw.json、一份 OpenCode
的 opencode.json,或一份 Qwen Code 的 settings.json。第 II-B 类是格式
调和工作集中之处;约定收敛深度解析
对此有详细论述。
claude-code:全配置面适配器
claude-code 适配器拥有最广的配置面——规则、agents、钩子、
输出样式、设置以及约定目录。它属于第 II-A 类:它传播 Apothem 的原始
模板并摊平约定目录,且在 src/apothem/harnesses/claude_code/templates/
下随附厂商设置模板。它不带 materializer.py——它并不从配置文件渲染原生
配置文件;它直接传播工件与约定目录。
有一处刻意划定的边界:claude-code 适配器只管理 CLAUDE.md 中由哨兵界定的一个块;该块之外的散文保持不变。该文件的其余部分属于操作者自有的领地。Apothem 物化其周围的配置面——规则、钩子、设置——并将该块之外操作者的散文原样保留。若适配器改写整个文件,每次 apothem install 都会抹去手写的指令;而此适配器只改写它所管理的那个块。
为何这份统一性举足轻重
该协议给操作者一个横跨十七种工具的统一心智模型。无论目标是渲染一个
单一文件还是摊平一棵目录树,apothem install、apothem verify、
apothem uninstall 的行为始终如一。厂商差异活在适配器子包内部、协议
背后——那正是它们的归属之地。