プラグイン
ZeroClaw のプラグインシステムを使用すると、コアバイナリに手を加えることなくエージェントに機能を追加できます。このページでは技術的な決定について説明します。プラグインが何で構成されているのか、なぜ WebAssembly なのか、そしてホストが信頼できないコンポーネントをどのように隔離しているのかを解説します。その下にあるガイドでは、各種類のプラグインの構築方法を、下に進むにつれてより技術的な内容へと掘り下げながら順を追って説明します。
- ツールプラグインを書く: モデルが呼び出せる呼び出し可能なツール。ここから始めてください。空のクレートからインストール済みツールまでの完全な作業手順です。
- チャネルプラグインの作成: 機能フラグ全体を扱うメッセージングプラットフォーム統合。
- メモリプラグインの作成: エージェント属性のリコールを実装するストレージバックエンド。
- プラグインの配布: 署名、レジストリ、インストール時のセキュリティ。
Markdown のみの スキルバンドル はプラグインではありませんが、同じマニフェスト、署名、インストール機構を経由します。そのページは Skills のドキュメントにあります。
オペレーターの視点でのディスカバリー、署名ポリシー、および設定については、プラグインの仕組み を参照してください。規範的な契約リファレンスについては、プラグインプロトコル を参照してください。
WebAssembly を選ぶ理由
プラグインは、API キー、会話履歴、シェルアクセスを保持するプロセス内で任意のサードパーティコードを実行します。分離境界は助言的なものではなく、実質的なものでなければなりません。ZeroClaw は wasmtime 上の WASI Component Model を使用します。動的ライブラリ方式やサブプロセス方式では同時に満たせない4つの特性を提供するからです:
- ケイパビリティベースのサンドボックス。 WebAssembly コンポーネントにはアンビエント権限がありません。ホストがそのケイパビリティをリンカーに明示的に接続しない限り、ファイル、ソケット、環境変数を開くことはできません。ZeroClaw のホストは、ファイルシステムのプリオープンもネットワークもない WASI コンテキストで各プラグインストアを構築します(
crates/zeroclaw-plugins/src/component.rsのPluginState)。プラグインが到達できるのは、その world が宣言するホストインポートの集合と、マニフェストのパーミッションが追加するものだけであり、それ以外には何もありません。 - 実行のメータリング。 エンジンは燃料メータリングを有効にしてビルドされており、呼び出しごとに新しい燃料予算と、待機中のホスト処理を含む実時間の期限が設定されます。無限ループや無限待機を行うプラグインは失敗し、エージェントをハングさせることはできません。メモリ、テーブル、インスタンスの上限はストアリミッターによって適用されます。5つすべての上限はオペレーター設定(
plugins.limits.*)から取得され、ゼロ以外であることが検証されます。また、これらの上限なしではストアを構築できないため、どの読み込み経路でもサンドボックス化されていないプラグインが生成されることはありません。 - 型付きの言語非依存 ABI。 ホストとプラグイン間の契約は Rust API ではなく、WIT インターフェースファイルのセット(ZeroClaw リポジトリ内の
wit/v0/)です。ホストは wasmtime のbindgen!を用いてそれらのファイルからバインディングを生成し、プラグインは Rust のwit-bindgen、またはwasm32-wasip2コンポーネントにコンパイルできる任意の言語の同等のツールを用いて、ミラーイメージのゲストバインディングを生成します。レコード、バリアント、result、option 型は、型を保持したまま境界を越えます。 - 組み込みと同一の動作。 各プラグイン種別は、ファーストパーティ実装が使用する同じ Rust トレイトに適合されます。ツールプラグインは
Tool(wasm_tool.rs) に、チャネルプラグインはChannel(wasm_channel.rs) に、メモリプラグインはMemory(wasm_memory.rs) になります。エージェントループ、アトリビューション、レシート、セキュリティポリシーは違いを認識しません。
ピース
ディスク上のプラグインは、マニフェストとコンパイル済みコンポーネントを保持するディレクトリです:
~/.zeroclaw/plugins/
└── my-plugin/
├── manifest.toml # identity, capabilities, permissions, signature
└── my-plugin.wasm # wasm32-wasip2 component
マニフェストは直交する 2 つの事柄を宣言します。
- Capabilities: プラグインが 何であるか。
tool、channel、memory、observer、skillのいずれか1つ以上(crates/zeroclaw-plugins/src/lib.rsのPluginCapability列挙型)。各 WASM 機能は、コンポーネントがエクスポートしなければならない WIT world を選択します。skill機能は例外で、コードではなくインストール機構に乗る markdown の skill bundle を示すものであり、コンポーネントは不要です。 - 権限: プラグインのコードが 到達できる ホストサービス。同じファイルにある
PluginPermission列挙型。現在、config_read(ツールおよびチャネルアダプターは、それぞれスキーマから具体化され、検証済みの公開設定を受け取り、認可されたサービス呼び出しでスキーマによって指定されたシークレットを解決できる)とhttp_clientが動作に影響します。HTTP 権限は、送信wasi:httpを実装するアダプターに必要な許可です。ツールとチャネルではその機能を有効にしますが、メモリでは意図的にまだ有効にしていません。config_readはマニフェストのconfig_schemaと組み合わせる必要があり、どちらか一方だけの場合は拒否されます。ファイルシステムおよびメモリアクセスの権限はスキーマでは受け付けられますが、ホスト関数による実装はまだないため、宣言しても何も付与されません。
世界
wit/v0/ は WASM 機能ごとに 1 つの world を定義します。すべての world はホストの logging インターフェイスをインポートし、その log-record イベントはホスト呼び出し元のスパン属性を保持しながら構造化ログに記録されます。また、plugin-info(自己申告の名前とバージョン)とそのプライマリインターフェイスをエクスポートします。
| World | エクスポート | ストアのライフサイクル |
|---|---|---|
tool-plugin | tool: name, description, parameters-schema, execute | execute ごとに新しいストアを作成;インポートのスコープは secrets |
channel-plugin | channel: configure、send、poll-message、および22個の機能ゲート付きメソッド | 非同期ミューテックスで保護されたウォームストア。呼び出しごとに補充され、インポートのスコープは config、secrets、およびホストから供給される inbound |
memory-plugin | memory: store、recall、get、forget、および11個の機能ゲート付きメソッド | 非同期ミューテックスの背後にあるウォームストア。呼び出しごとに補充されます |
チャネルワールドとメモリワールドは、capability flags を使用します。これは、ホストがロード時に一度だけ読み取るビットマスクです(get-channel-capabilities / get-memory-capabilities)。設定されていないフラグについては、ホストは Rust トレイトのデフォルトを使用し、プラグインのエクスポートを呼び出すことはありません。これにより、WIT コントラクトは追加的な状態を保ちます。新しいオプションのメソッドは、新しいフラグと新しい関数の追加であり、破壊的変更となることはありません。
実行モデル
ホスト(crates/zeroclaw-plugins/src/component.rs)は、プロセス用に1つの非同期 wasmtime::Engine を保持します。読み込みはバックエンドに依存します。Cranelift JIT を使用したビルドでは、読み込み時に .wasm をコンパイルします。ランタイム専用ビルドでは、プリコンパイル済みの .cwasm をデシリアライズします。各プラグインのインスタンス化では、以下が行われます。
- サンドボックス化された WASI コンテキスト、リソーステーブル、オプションの HTTP コンテキスト、および燃料バジェットを保持する
Store。 - そのワールド、付与された権限、アダプターサポートが呼び出す import だけを正確に備えた
Linkerは、常にlogging、ツールとチャネルにはsecrets、チャネルにはconfigとinboundを使用し、マニフェストがhttp_clientを許可している場合に限り、ツールおよびチャネルのアダプターにはwasi:httpを使用します。Memory は HTTP コンテキストも HTTP リンカーも作成しません。各アダプターはインスタンス化時にコンテキストとリンカーを相互検証します(ensure_http_coherent)。
ツール呼び出しは、その構造上ステートレスです。WasmTool::execute は新しいストアを構築し、呼び出しを実行してから破棄します。チャネルとメモリバックエンドは本質的にステートフルであるため、プラグインの存続期間中、ウォームストアを 1 つ保持します。ホストは呼び出しの前に毎回燃料を補給するため、長時間稼働するプラグインは時間の経過とともに予算を使い果たすのではなく、呼び出しごとに全予算を得られます。デッドラインによる割り込みが発生すると、部分的に巻き戻されたゲスト状態を再開するのではなく、ウォームストアを破棄します。チャネルは次の呼び出しでインスタンスを再作成し、メモリは所有者が再構築するまで利用できない状態のままです。認可されたチャネル呼び出し中、config.get と secrets.get は、その許可されたインスタンスの正規構成のリビジョンを最大 1 つ実体化します。ホストは呼び出しの終了時にビューを破棄します。準拠するチャネルプラグインは、各利用時点で両方を解決しなければならず、返された構成や平文のシークレットをウォームなゲスト状態に保持してはなりません。ホストは、信頼されたゲストコードにデータを返した後、そのデータを保持しないよう強制することはできません。
境界は 32 ビットです。wasm32-wasip2 は Rust ツールチェーンが提供する唯一の WASI Preview 2 ターゲットであり、コンポーネント ABI はホストのワードサイズに関係なくオフセットを 32 ビットとして扱います。大きな値(チャネルアタッチメントのバイト列)は値渡しで受け渡されます。これが上流の制約である理由については、プロトコルページを参照してください。
現在の配線状態
エンドツーエンドで登録されているものと、ホスト完了だがまだ実行中のデーモンから到達できないものとの違いに注意してください:
| 機能 | ホストアダプター | ランタイムの接続 |
|---|---|---|
tool | WasmTool | 登録がエンドツーエンドで完了しました。検出されたツールプラグインがエージェントのツールセットに表示されます |
skill | markdown ローダー | エンドツーエンドで登録済み。スキルは plugin:<plugin>/<skill> の名前空間で読み込まれます |
channel | WasmChannel、完全かつユニットテスト網羅済み | エイリアス側での構築と実行時設定の解決が取り込まれました(#10146)。各トランスポートからチャネルの inbound キューへデータを流し込むベンダーごとのホストリスナーは、後続対応となります |
memory | WasmMemory、完全なMemoryトレイトを実装します | ランタイムはこれをまだ設定可能なバックエンドとして構築しません |
observer | sop_executeツールによってトリガーされます(zeroclaw sop run CLI コマンドではありません)。 | PluginCapability::Observer は予約済みです。WIT world やアダプターはまだ存在しません |
設定
静的なプラグインホスト設定では、他のすべてと同じスキーマミラーを使用します。インスタンスごとの値は現在、汎用のTOMLまたは zeroclaw config set を使用します。プラグインマニフェストスキーマは、まだzerocode形式やゲートウェイ形式としてレンダリングされていません。手動で編集する場合は注意してください。セクション内の構文ミス(たとえば、[[plugins.entries]] とすべき箇所で [plugins.entries] とするなど)により、現在は [plugins] セクション全体のデシリアライズに失敗し、警告なしでデフォルト値にフォールバックします。その結果、読み戻すと plugins.enabled = false になります(issue #8636で追跡中)。一般的な操作:
# システムをオンにする
zeroclaw config set plugins.enabled true
# 実行時に自動検出されたツールおよびスキルのプラグインを読み込む(デフォルト: false)
zeroclaw config set plugins.auto_discover true
# プラグインが検出される場所(デフォルト: ~/.zeroclaw/plugins)
zeroclaw config set plugins.plugins_dir /srv/zeroclaw/plugins
# 署名ポリシー: disabled | permissive | strict
zeroclaw config set plugins.security.signature_mode strict
# 呼び出しごとのサンドボックス制限
zeroclaw config set plugins.limits.call_fuel 1000000000
zeroclaw config set plugins.limits.call_timeout_ms 30000
zeroclaw config set plugins.limits.max_memory_mb 256
plugins.enabled = true はプラグインホストを有効にしますが、自動検出されたツールおよびスキルの機能が読み込まれるのは、plugins.auto_discover = true も設定されている場合だけです。そのフラグはデフォルトで false(フェイルクローズ)なので、enabled = true だけでは、[channels.plugin.<alias>] の下で宣言したチャンネルが利用できるだけで、プラグインのツールやスキルは利用できません。つまり、ツールまたはスキルのパッケージは list と info を正常に実行できても、実行時には何も提供しないことがあります。明示的なチャンネルバインディングは自動検出ではなくオペレーターが名前を指定するものなので、auto_discover は必要ありません。このフラグが制御するのは、自動検出されたツールとスキルだけです。
インスタンスごとの設定は plugins.entries 配下に、ホスト所有のパッケージ、ケイパビリティ、バインディングの識別情報から導出されたバージョン付きの zpi1_… 文字列をキーとして格納されます。インストール時には、パッケージのデフォルトのツールバインディング用のキーが表示され、初期登録されます。zeroclaw plugin info <package> はそのツールキーを再度表示します。これらの自動処理はツールに限られます。エイリアス単位のチャネル構築では、パッケージ名のバインディングを新たに作るのではなく、実際に設定されたエイリアスからキーを導出します。このランタイムパスは #10146 で実装されました。デーモンは、明示的に宣言された [channels.plugin.<alias>] インスタンスを構築し、そのエイリアスから型付き設定を解決するようになりました。チャネルインスタンスの自動的な plugin info キー表示とインストール時の初期登録は、#9584 の権限付与手続きが整うまで手動のままです。完全な識別情報を持つキーにより、異なるパッケージやケイパビリティ領域で、認証情報を共有せずに main のようなエイリアスを安全に再利用できます。正規のオペレーター値は、シークレットとしてマークされた文字列マップであり、保存時も暗号化されたままです(enc2:…)。config_read を要求するプラグインは、マップの単一の型コントラクトを config_schema で宣言します。これは、トップレベルの各プロパティが string、boolean、integer、number、array、または object のいずれかを明示的に使用する、閉じた Draft 2020-12 オブジェクトです。ツールまたはチャネルのコンシューマーは、トップレベルの文字列プロパティに x-secret = true を設定できます。ホストはオブジェクト全体を使ってその値を検証し、公開設定からその値を削除して、認可されたインスタンスの secrets.get インポートを通じてのみ利用可能にします。ツールは execute 中にシークレットを読み取れます。チャネルは configure および運用上の呼び出し中に、config.get を通じて公開設定を、secrets.get を通じてシークレットを取得します。有効な config_read 権限付与がない場合、どちらのインポートも access-denied を返します。インスタンス化、静的メタデータの検出、解決の失敗、ホスト呼び出し予算の枯渇は unavailable を返します。文字列は直接保存し、ブール値と数値は JSON スカラーのテキストとして、配列とオブジェクトは JSON テキストとして保存します。ホストは、ツールまたはチャネルのゲストコードを使用する前に、結果として得られた型付きオブジェクトを実体化して検証します。未知の値、形式が不正な値、範囲外の値はプラグインに到達することなく失敗します。メモリープラグインにはまだ設定インポートがなく、その ABI が追加されるまで config_read を要求してはなりません。
1.0 より前のプラグイン作成者は明示的に移行する必要があります。config_read を要求しながら config_schema を指定しないマニフェストは、今後検出されません。現在の値に一致する閉じたスキーマを追加し、文字列マップではなく型付きJSONをデシリアライズするようツール/チャネルのゲストを更新して、スキーマも署名の対象に含まれるため再ビルドして再署名します。ホスト統合では、所有する設定マップの代わりに、PluginConfigResolver をラップする PluginHostServices が注入されます。認可された各ツールまたはチャネルのフレームでは、スコープにバインドされた ResolvedPluginConfig を最大1つ実体化し、フレーム内のすべての設定読み取りでそのビューを使用して、フレーム終了時に破棄します。チャネルは使用時点で config.get と secrets.get を呼び出すため、同じ論理バインディング内で公開設定と認証情報のローテーションが行われた場合、次の操作では1つのリビジョンとして見えます。静的な識別情報とケイパビリティのエクスポートはロード時に一度だけ読み取られます。ボット/アカウントの識別情報やその他の静的メタデータを変更するには、チャネルのライフサイクルを再構築する必要があります。型付き設定への移行は、手順を追ったガイドであり、互換性シムなしでこの強制を提供するというリリース判断も含まれています。
これは厳格な 1.0 以前のキー形式です。パッケージ名またはバインディング名だけで命名されたレガシーエントリは参照されません。既存のツールパッケージの場合は、zeroclaw plugin info <package> を実行して完全なインスタンスキーを取得し、古いエントリの名前をそのキーに変更して、設定を保存してください。新規にツールをインストールすると、自動的に設定されます。
実効権限は、マニフェストの要求とは別にチェックされます。config_read が拒否されると、ホストは空のオブジェクトを検証します。必須プロパティがあると、起動はフェイルクローズします。空のオブジェクトが有効な場合、ツールは空の __config キーを省略し、チャネル設定/シークレットのインポートは access-denied を返します。ホストの正式なフィールド一覧とデフォルト値については、Config リファレンスを参照してください。zeroclaw config list には、現在保存されている値が表示されます。
信頼境界が実際にどこにあるか
サンドボックスは読み込まれたプラグインが実行できる操作を制限します。署名ポリシーはそもそも何が読み込まれるかを制限します。どちらもオペレーターの判断によるもので、これらは組み合わせて機能します。
plugins.enabledが false(デフォルト)の場合、プラグインコードは一切実行されません。plugins.auto_discoverが false(デフォルト)の場合、自動検出されたツールおよびスキルの機能は読み込まれません。plugins.enabled = trueだけでは、[channels.plugin.<alias>]配下で宣言したチャネルのみが有効になります。ツールとスキルを読み込むには、auto_discover = trueも必要です。- 署名
strict: マニフェストに信頼済みセットの鍵による有効な Ed25519 署名を持つコンポーネントのみが読み込まれます。 - 読み込み済みプラグイン: fuel、メモリ上限、no-preopen WASI、およびパーミッションゲート付きインポートセットによって制限されています。
サンドボックスが制約しないのは、モデルが呼び出すことを選択したツールの意味的な挙動です。http_client 権限を持ち、ツールアダプターの HTTP サーフェスを使用するツールは、モデルが渡したものを、そのコードが決めた任意の場所に送信できます。署名ポリシーが存在するのは、「どのコードを読み込むか」が最も重要な判断だからです。意識して決めてください。