Skip to content

CH 08 · プラグインツリーのメンタルモデル:すべてはプラグイン

全文字数約 4070 字所要時間約 15 分前提CH 02 の概念、CH 05 の実操作難易度理解重視

この章の目標

CH 02 で「すべてはプラグイン」と述べました。この章ではそれをメンタルモデルに変えます:動いている dsh とは何か、なぜすべての部分を差し替えられるのか、そして「Provider を差し替えれば製品も差し替わる」という一文がなぜ実際に成り立つのか。

ステージを整える 1 文:動いている dsh = プラグインツリー

公式アーキテクチャドキュメントの表現はこうです:動いている dsh はプラグインツリーで、起動時に順序付けられたレイヤ群から合成される。 覚えるために 3 層に分ければ、アーキテクチャ全体が見えます:

動いている dsh = プラグインツリー(模式図)

図を上から下に読むと、ロジックは:① まず Profile を選ぶ → ② プラグインレイヤを順に重ねる → ③ 全レイヤが同じ Cordis カーネル上で動く。対応する 3 点:

  • 最下層が Cordis カーネル:プラグインがサービスを注入し、イベントをブロードキャストし、エフェクトをマウントできる共有コンテキスト。
  • 中間がプラグインツリーそのもの:さまざまなプラグインがツリー状に重なり、それぞれが 1 つの決まった仕事を担当。
  • 最上層が Profile:新しい能力を加えるわけではなく「このツリーをどう見せるか」だけを決める —— web、headless、あるいは自分で組み合わせたカスタム(次節で扱う)。

Cordis:すべてを動かすフレームワーク

dsh は Cordis というプラグインフレームワーク上で動いています(公式 README が明示的に「powered by Cordis」と書いている)。Cordis の中核は約束が 3 つだけ:

  1. プラグインは共有コンテキストにサービスを注入する。能力を足したければ、プラグインを書いて登録する。
  2. プラグインは型付きイベントをブロードキャストする。他者がそれを購読して協調する、互いに直接 import する必要はない。
  3. プラグインの登録は可逆エフェクト —— プラグインがアンロードされると、登録したものは自動的に巻き戻り、残骸は残らない。

最も重要なのは最後です —— これが「すべてはプラグイン」が成立する理由です:dsh に特権コアは存在しない、改造対象がない。 モデルアダプタもプラグイン、ツールレジストリもプラグイン、セッションログもプラグイン、Agent のメインループ自体もプラグイン。dsh を拡張する手段は「脇にプラグインをぶら下げる」ことで、「どこかの中核ファイルを編集する」ことではありません。だから変更コストがきわめて低い:変えられないなら差し替える、差し替えられないなら足す。

これと他のツールはどう違うか?一番使われている CLI Agent 2 つを並べると、違いが際立ちます:

比較対象拡張アプローチ「骨格」に触れることができるか
Claude Codeツールは拡張可能(MCP、skill)、ただしモデルとメインループは差し替え不可半開き:ツールは足せるが、モデル適応、セッション、Agent ループは溶接済み、モデルは自エコシステムにロック
Codexツールは拡張可能、ただしモデルとメインループは差し替え不可半開き:ツールは足せるが、モデル適応、セッション、Agent ループは溶接済み、モデルは自エコシステムにロック
dsh脇にプラグインをぶら下げ、設定を変えて差し替える完全に開く:セッションログ、Agent ループ、モデルアダプタすらプラグイン、特権コアなし

1 行でまとめると:Claude Code と Codex の「骨格」は溶接されている;dsh の「骨格」もまたプラグインである。 だからこそどんなモデルでも接続できる(CH 06 の Codex との比較がその実例)、セッションも自分の実装に差し替えられる —— 骨格自体も交換可能、唯一の制限は「変えたいと思うか」だけです。

--dump-config から実物のツリーを見る

理論を 10 回言うより、実物を 1 回見るほうがよい。CH 05 で dsh --profile headless --dump-config を一度走らせました。出力されるのは @deepseek-ai/dsh-* プラグインが積み重なった長いツリーで、llm(モデル)、session(セッション)、credentials(キー)、session-persistence-jsonl(セッション永続化)…… これらのプラグインがそれぞれ自分の仕事を担当し、積み重なって実行可能な dsh になります。

公式チームが中核プラグインを明確に列挙しています;それぞれがツリーの「大枝」に対応:

中核プラグイン担当
core/session追記専用セッションログ(モデルが見たものはすべて記録される)
core/system-promptシステムプロンプトの組み立て、ツールスキーマのパッケージング方法
core/toolsツールレジストリと制御された実行パイプライン
core/agentAgent インタフェースとライブレジストリ
core/agent-loopデフォルトの「リクエスト → ツール呼び出し」ループドライバ
llm/llmメッセージとストリーミング語彙 + モデルアダプタシーム

最後の行の「シーム」に注意 —— これが次のキー概念です。

Profile と Bundle:プラグインはどう「モード」に組み合わされるか

プラグイン = 料理(チャーハン、家スープ、サイド);bundle = 定食セット(ご飯、メイン、箸がパック済み);profile = メニューカード(どの料理をどんな順で並べるか);奥の厨房にある鍋が次節の Cordis カーネル。

ツリーが何でできているかは Profile が決めます —— それは手に取ったメニューカードと考えるとよい:

  • Profile は名前付きの組み合わせで、Harness ディレクトリに保存されます。どの Bundle を重ねるか、追加でどのプラグインを入れるかを列挙し、自分の cordis.patch.yml も持ちます。カードは料理を作らず「この食事に何が入っているか」だけを伝えます:公式工場は webheadless のメニューカードをすぐ使える形で出荷;CH 02 の「4 つの実行モード」はその下にある 4 つのプラグイン組み合わせプリセットに相当し、カードの追加分です。
  • Bundle は「一群のプラグイン + マウントされる設定」の配布形式で、パック済みの定食セットに相当:dsh-base は全 profile の第 1 層(基盤:モデル、ツール、永続化、サンドボックス、承認、設定、キー、テレメトリ);web-app は基本セットに「店内飲食環境」(ブラウザ界面)を追加;headless は「テイクアウト」(ワンショットコマンドライン、サーバなし)を加えます。

混同しやすい点を先に:Profile はプラグインではありません。プラグインは「仕事をするもの」(料理)、Profile は単なる「カード」(注文するだけで調理はしない)。「Minimal モード」を選んでも新しいプラグインが入るわけではなく、プラグインツリーがよりミニマルな組み合わせに差し替わるだけです —— 違うカード、同じ厨房。

プラグインの重ね方には厳格な順序があり(公式)、厨房がカードに従って準備し、後のノートは先のものを上書きできます:

各 bundle(profile に列挙された順)         ← まずは定食セット
→ profile 自身の cordis.patch.yml         ← カードに書かれたメモ
→ Harness ディレクトリレベルのもの        ← 上司の共通要件
→ コマンドライン --patch のオーバーレイ   ← 臨時メモ、後勝ち

各レイヤは上のものを上書きできます —— パッチは id で 1 行を定位し、その設定全体を置換したり、新しい行を挿入したりします。だから CH 05 の --patch は「余計な穴」ではなく、このツリーを堂々と改造する手段です:--dump-config が出力するどの 1 行も、自分のパッチで置き換えられます。

自前の Profile を作るのは、ゼロからコードを書くことではありません —— 本質は「カードを 1 枚コピーして直す」ことです:

  • web より少なくしたい? → web の設定をコピーして不要なものを削る(「店内飲食、でもスープ抜き」);
  • headless より多くしたい(例:自分が書いたツールプラグインをデフォルトでマウント)? → headless をコピーして bundle リストに 1 行足す(「テイクアウト、プラス持ち込みサイド」);
  • 一番軽いのは「カードを変えず、メモだけ足す」:既存 profile にプラグインを入れるには dsh plugin --profile <name> add <パッケージ>(「既存カードにサイドを 1 品追加」)、一時的に上書きするには --patch(「この食事メモ:スープを Sprite に変更」)—— どちらも CH 05 で登場済みです。

ロード過程を一本のタイムラインにまとめると、以上がすべて着地します:

  1. 起動時、dsh はまず選んだ Profile を読む —— それはロードマニフェストで「どのレイヤをどんな順で重ねるか」を伝えます;
  2. マニフェスト順にレイヤ単位でロード:まず第 1 層 ベース(dsh-base)、次に第 2 層 追加(web-app / headless / 自分のプラグイン)、最後に第 3 層 上書き(cordis.patch.yml + --patch)—— 後ろにロードしたものほど、先のものを上書きできます;
  3. 全レイヤは同じ Cordis カーネル上にインストールされ、互いにサービスを注入し合います。

着地点の 1 行:Profile = 起動時の注文順;プラグインの木 = その順で積まれた料理;Cordis = 料理を積む鍋。

イベントと能力シーム:プラグインはどう話すか

プラグインは互いに import し合わず、2 つのものを介して協調します:

イベントは拡張点で、3 つの領域があります:

  • Session イベント:永続的事実、ログに追記され、ブロードキャストされ、再起動を跨いで生き残る(例:「このメッセージは送信済み」)。
  • Agent イベント(agent/*):ライブ Agent を運ぶ、実行中の作業を観察または介入するために使う(例:「このステップ開始前に介入」)。
  • 能力イベント(fs/*tools/*telemetry/*):メインループを変えずにシームにポリシーとアダプタを取り付ける。

能力シームは差し替え可能な能力で、3 つの固定役割があります:

  • Service Definition:インタフェースの形状を宣言する;
  • Service Provider:実際に実装する;
  • Consumer:それを使う(通常はモデルが呼び出すツール)。

なぜ「Provider を差し替えれば製品も差し替わる」のか?ファイルシステムとサブプロセスの Provider が同じ「実行ワールド」を共有しているため —— それらをリモートサンドボックスに向ければ、Bash、PTY、LSP もみんなついてきて、能力ごとに別系統を書く必要はありません。モデルアダプタも同じ:ctx.llm に登録されているアダプタが、製品全体で使われるモデルになります。他は何も変える必要はありません。

メンタルモデルを 1 行で

3 文しか持ち帰れないなら、次の 3 文を覚えてください(冒頭の小さなレストランの比喩と一緒に):

  1. dsh にはコアがない —— すべての部分がプラグイン、それ自身も含む;脇に料理を足せばいい、抜けば自動で巻き戻る。
  2. Profile がツリーの形を決める —— web / headless / カスタムはすべて同じツリーの違う「メニューカード」;調理はせず、注文するだけ。
  3. イベントとシームがプラグイン協調のインタフェース —— 取り付ければ動く、外せば巻き戻る、だから自由に差し替えるも足すも;Provider を差し替えれば製品も差し替わる。

この章で学んだこと

  • [ ] 「動いている dsh = プラグインツリー」であり、それが順序付けられたレイヤ群から合成されることを言える
  • [ ] Cordis の 3 つの中核約束(サービス注入 / イベント / 可逆エフェクト)を言え、特権コアがない理由を言える
  • [ ] --dump-config の出力から中核プラグイン(llm / session / credentials / tools など)を識別できる
  • [ ] Profile と Bundle の関係と、プラグイン重ねのレイヤ順を説明できる
  • [ ] 「注文」の比喩でプラグイン / bundle / profile の関係を説明でき、自前 profile 構築は「カードをコピーして直す」こと、plugin add / --patch が最も軽い改造手段であることを知っている
  • [ ] 能力シームの 3 役割(Definition / Provider / Consumer)を言え、「Provider を差し替えれば製品も差し替わる」理由を説明できる

Open Source · MIT · Community Driven