マルチハーネスアダプター設計の内側
Apothem の HarnessAdapter プロトコルと、1 つのプロファイルを 17 のハーネスへ物質化する 3 つのアダプタークラスの技術的な深掘り。
Apothem は、1 つの共有プロファイルを 17 のハーネスネイティブな設定へと 物質化します。17 のターゲット、17 のベンダー形式、17 のインストール位置 —— そして、オペレーターと CLI の双方が頼りにする 1 つの統一されたインターフェイス。 本稿では、アダプター層がその契約をどのように束ね続けているのかを説明します。
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() スイープは、実行時のリゾルバーではなく、
適合性のパリティチェックです。テストは、発見されたアダプターの集合が
静的レジストリと等しいことを表明するため、規約は正しいが未登録のアダプターは、
黙って解決されるのではなく、カバレッジのリグレッションとして表面化します。
したがって、ハーネスを追加するとは、レジストリデータに登録すること(および
パッケージングのパリティのためにエントリーポイントのテーブルへ反映すること)を
意味します —— どちらかが漏れていれば、パリティテストがはっきりと失敗します。
アダプターごとに 1 つのサブパッケージ
各アダプターは、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 を開く読者が目にするのは、まさにその 1 つのハーネスの
ドリフト検出ロジックだけであり、かき分けるべきインストールやアンインストールの
ノイズはありません。
3 つのアダプタークラス
17 のハーネスが、みな同じやり方で設定を消費するわけではありません。Apothem は、 プロファイルがハーネスへ到達する前にどれだけの変換を必要とするかに応じて、 それらを 3 つのクラスに分類します。
クラス I —— 素の伝播
ハーネスは、Apothem がすでに書き出している同じ成果物 —— 平坦な .md ルール、
agents、hooks —— を、レンダリング工程なしで消費します。アダプターは、素の
テンプレートをハーネスの設定ツリーへコピーします。何もレンダリングされない
ため、materializer.py はありません。プロファイルの成果物がそのまま出力です。
クラス II-A —— 素にベンダーテンプレートを加えたもの
ハーネスは、Apothem の素の成果物 AND 一連のベンダー固有のテンプレートファイル
(ハーネスが必要とする設定・マニフェスト・足場ファイル)を消費します。
アダプターは、素の成果物とベンダーテンプレートを一緒にコピーします。ベンダー
テンプレートは、アダプターの隣の templates/ ディレクトリの下に住んでいます。
クラス II-B —— プロファイルからのレンダリング
ハーネスは、共有プロファイルの構造化フィールドからレンダリングされた、1 つの
ネイティブ設定ファイルを求めます。アダプターの materializer.py がプロファイル
を読み取り、ハーネスネイティブなテキストを発します —— Hermes の config.yaml、
Open-Claw の openclaw.json、OpenCode の opencode.json、あるいは Qwen Code の
settings.json。クラス II-B は、形式の調停作業が集中する場所です。
規約収束の深掘り
が、それを詳しく扱っています。
claude-code:フルサーフェスのアダプター
claude-code アダプターは、最も広い設定面 —— ルール、agents、hooks、
output-styles、設定、規約ディレクトリ —— を持ちます。これはクラス
II-A に属します。Apothem の素のテンプレートを伝播し、規約ディレクトリを平坦化
し、そして src/apothem/harnesses/claude_code/templates/ の下にベンダー設定
テンプレートを同梱します。materializer.py は携えていません —— プロファイル
からネイティブ設定ファイルをレンダリングするのではなく、成果物と規約ディレクトリ
を直接伝播します。
意図的な 1 つの境界があります。claude-code アダプターが管理するのは、CLAUDE.md 内の
センチネルで区切られたブロックだけで、そのブロックの外側の文章は保持されます。そのファイルの
残りの部分は、オペレーターが所有する領域です。Apothem は、その周囲の設定面(ルール、フック、
設定)をマテリアライズし、オペレーター自身の文章はそのブロックの外側でそのまま保たれます。
ファイル全体を書き換えるアダプターは、apothem install を実行するたびに手書きの指示を
消し去ってしまいます。このアダプターは、管理ブロックだけを書き換えます。
なぜこの統一性が重要なのか
このプロトコルは、オペレーターに 17 のツールにまたがる 1 つの心的モデルを
与えます。ターゲットが単一ファイルをレンダリングするのであれ、ディレクトリ
ツリーを平坦化するのであれ、apothem install、apothem verify、
apothem uninstall は同じように振る舞います。ベンダーの差異は、プロトコルの
背後、アダプターサブパッケージの内部 —— それらがあるべき場所 —— に住んでいます。