タイプ: リファレンス ステータス: 承認済み 最終レビュー日: 2026-07-17 関連先:
- FND-001
- ADR-003
- crates/zeroclaw-plugins
プラグインプロトコル
この文書は、ZeroClawのプラグインホストとWASMプラグインコンポーネント間のプロトコルを定義します。
プラグインとは
プラグインは、コアバイナリには同梱されていない機能を追加するために ZeroClaw が実行時に読み込む、自己完結型の WebAssembly コンポーネントです。~/.zeroclaw/plugins/ 配下の専用ディレクトリに配置され、名前と提供内容を宣言するマニフェストと一緒に置かれます。ZeroClaw は起動時にそれを検出し、検証し、エクスポートされた関数を実行中のエージェントに接続して、組み込み機能と同じように動作させます。ツールプラグインはモデルに対して単なる呼び出し可能なツールとして表示され(WasmTool はネイティブツールと同じ Tool トレイトを実装します)、チャネルプラグインはメッセージングチャネルとして、メモリプラグインはストレージバックエンドとして動作します。
プラグインは、PluginCapability(crates/zeroclaw-plugins/src/lib.rs)で定義された機能のうち1つ以上を提供できます。呼び出し可能なツール、メッセージングチャネル、メモリバックエンド、オブザーバビリティバックエンド、またはマークダウンスキルのバンドルです。スキルのケースは特別で、WASMを一切同梱せず、マークダウンの skills/ ディレクトリのみを提供するため、コンパイル済みコンポーネントを省略する唯一の機能です。
なぜ作るのか
- フォークせずに拡張。 ZeroClaw のソースツリーを変更したりリリースを待ったりすることなく、ツールやチャネルを追加できます。プラグインはあなたのもので、インストールディレクトリから読み込まれます。
- ネイティブな動作。 読み込まれたプラグインは二級のアドオンではありません。ブリッジは組み込みが使用するのと同じランタイムトレイトを実装しているため、プラグインツールはモデルに提供され、帰属され、ファーストパーティのものとまったく同じように呼び出されます。
- 言語の選択。 契約は WIT と WASI Component Model であり、Rust API ではありません。
wasm32-wasip2コンポーネントにコンパイルできる任意の言語で world を実装できます。以下の実践ガイドは、現時点で最もサポートが充実したパスであるため Rust ですが、境界そのものは言語非依存です。 - デフォルトでサンドボックス化。 ホストは各プラグインをファイルシステムのプレオープンなし・アンビエントネットワークなしのWASIコンテキストにロードします。プラグインはホストに静かにアクセスすることはできず、そのワールドに組み込まれたホスト関数だけが与えられ、それ以上は何もありません。アウトバウンドHTTPはオープン可能な唯一のネットワーク面ですが、マニフェストが
http_clientを付与し、かつそのケイパビリティアダプターがテスト済みのHTTP境界を明示的に有効化した場合に限ります。ツールアダプターとチャネルアダプターはそれを行いますが、メモリアダプターはまだ行いません。 - 検証可能なプロビナンス。 マニフェストはEd25519で署名でき、オペレーターはプラグインがロードされる前に信頼できるパブリッシャーからの署名を要求できます。
プラグインでできないこと(現時点)
これらは現在のホストの実際の制限であり、スタイル上の好みではありません。存在しない機能を前提に設計する前に、これらを把握しておいてください。
logging、型付き設定、インスタンススコープのシークレット、http_client、およびホストから供給されるインバウンドが接続されています。 マニフェストで宣言できる権限のうち、config_readはプラグイン独自のスキーマ検証済みパブリック設定を公開します。ツールまたはチャネルのスキーマでは、パブリック設定から除外するシークレットを指定し、認可されたサービス呼び出しで解決できます。外向きのwasi:httpにはhttp_clientの許可が必要ですが、ケイパビリティアダプターはそのホストサーフェスもオプトインする必要があります。ツールおよびチャネルのアダプターはオプトインしていますが、メモリは、そのネットワーク境界がコンポーネントレベルでカバーされるまで、意図的に HTTP を使用しないままです。ファイルシステムおよびメモリアクセスの権限は、マニフェストスキーマでは引き続き受け付けられますが、無効です。それらのホスト関数はまだリンカーに登録されていません。以下の「権限」と「ホストインポート」を参照してください。- アンビエントなホストネットワークやファイルシステムはありません。 WASI コンテキストには preopen もアンビエントネットワークもないため、プラグインは raw ソケットを開いたり、アンビエント WASI を通じてホストのファイルを読み取ったりすることはできません。
http_clientグラントを持つツールプラグインやチャネルプラグインは、それらのアダプターがオプトインしているため、アウトバウンドのwasi:httpを取得できますが、リッスンすることはできません。インバウンドトラフィックを受信する必要があるチャネルプラグインは、自身でリスナーを開くことはありません。ホストがリスナーを実行し、inboundインポートを通じてメッセージを送り込み、プラグインはそれをpoll-messageエクスポートからドレインします。 - 32ビット境界。 ターゲットは
wasm32-wasip2です。ゲストメモリは32ビットアドレス空間であり、コンポーネントABIはホストのワードサイズに関係なくオフセットを32ビットとしてlowerします。大きな値(たとえばチャネル添付の生バイト)は値渡しで境界を越えます。これがアップストリームのツールチェーンの制約であり、このリポジトリで切り替え可能なフラグではない理由については、32ビットアドレス空間のセクションを参照してください。 - ツールプラグインごとに1つのツール。
tool-pluginワールドは、1つの名前とスキーマを持つ単一のtoolインターフェイスをエクスポートします。複数のツールを公開する必要があるプラグインは、複数のコンポーネント、または別のワールドを提供します。 - 実験的で未凍結のコントラクト。
wit/v0にはまだ.frozenマーカーがないため、最初の安定リリース前にインターフェイスが変更される可能性があります。バージョンをピン留めし、WIT のバンプ時には再コンパイルが必要になることを想定してください。
アーキテクチャ
ZeroClaw プラグインは、wit/v0/ 配下の WIT インターフェースによって定義され、wasmtime を直接使用して(crates/zeroclaw-plugins)ホストされる WebAssembly コンポーネントです。プラグインは WASI Preview 2 コンポーネント(wasm32-wasip2)にコンパイルされ、そのコンポーネントはプラグインワールド(tool-plugin、channel-plugin、memory-plugin)のいずれかをエクスポートし、wit/v0/ でそのワールドによって宣言されたホストインターフェースをインポートします。
ホストは crates/zeroclaw-plugins/src/component.rs にあります。非同期対応の wasmtime::Engine を 1 つ保持し、wit/v0 から wasmtime::component::bindgen! を使用してワールドバインディングを生成し、サンドボックス化された WASI p2 サーフェスを各ワールドのリンカーに接続します。ストアごとのホスト状態(PluginState)には、preopen もネットワークもなしで構築された WasiCtx に加えて、WASI が必要とする ResourceTable、ホストが発行したスコープ、および型付きの稼働中サービスハンドルが保持されます。すべてのワールドは logging をインポートします。tool は secrets を、channel は config、secrets、inbound をインポートします。付与された http_client 権限により、さらに wasi:http がアタッチされ、リンクされます。このサーフェスに対する正規の契約は、ワールド宣言と認可されたスコープです(Host imports を参照)。
3つのworldブリッジは、各WIT worldをランタイムのネイティブなトレイトにマッピングします:
| World | ブリッジモジュール | ランタイムサーフェス |
|---|---|---|
tool-plugin | runtime.rs, wasm_tool.rs | zeroclaw_api::tool::Tool |
channel-plugin | wasm_channel.rs | チャネルトレイト |
memory-plugin | wasm_memory.rs | メモリバックエンドトレイト |
ツールプラグインは呼び出しごとに新しいストアを使用します(ステートレス)。チャネルプラグインとメモリプラグインは、プラグインのライフタイム全体にわたって、非同期ミューテックスで保護されたウォームストアを保持します。
ツールプラグインは検出から登録まで一通り行われます。ランタイムは channel_plugin_details() のツール相当を走査し、それぞれに対して WasmTool を構築します。チャネルホストアダプター(WasmChannel、その wasi:http ゲーティング、利用箇所での設定サービス、ホストから供給される inbound キュー)は完成しており、単体テストでカバーされています。また、PluginHost::channel_plugin_details() は wasm ベースのチャネルプラグインを登録対象として公開します。ランタイムは明示的に宣言された [channels.plugin.<alias>] バインディングを解決し、その WasmChannel を構築して、設定されたエイリアスから登録できるようになりました。このエイリアスを考慮した構築とランタイム設定の解決は #10146 で実装されました。残るフォローアップは、ベンダーごとのホストリスナーが各トランスポートからデータを取り込み、チャネルの inbound キューに流し込む処理です。メモリブリッジ(WasmMemory)は一段階遅れた同じ状況にあります。アダプターは memory-plugin ワールドに対して完全な Memory トレイトを実装していますが、ホストはまだ channel_plugin_details() に対応するメモリ側を公開しておらず、ランタイムも設定可能なバックエンドとして WasmMemory をまだ構築していません。
プラグイン構造
プラグインは以下を含むディレクトリです:
my-plugin/
manifest.toml # Plugin metadata and permissions
plugin.wasm # Compiled WASM module (optional for skill-only plugins)
プラグインは~/.zeroclaw/plugins/から検出されます(設定ファイルのplugins.plugins_dirで設定可能)。
レジストリの検索とインストール
ローカルプラグインのインストールパスは、インストール済みプラグインの信頼できる唯一の情報源であり続けます。レジストリは、コマンド実行時にプラグインアーカイブを検出してダウンロードするために使用される JSON インデックスに過ぎません:
zeroclaw plugin search calendar
zeroclaw plugin install team-calendar
zeroclaw plugin install team-calendar@0.2.0
zeroclaw plugin search calendar --registry https://example.invalid/registry.json
zeroclaw plugin install team-calendar --registry https://example.invalid/registry.json
zeroclaw plugin search はレジストリのメタデータを取得し、クエリをプラグイン名と説明に対して照合します。プラグインコードのインストール、有効化、実行は行いません。
zeroclaw plugin install <name> はレジストリから名前を解決し、選択された zip アーカイブをダウンロードし、オプションの SHA-256 ダイジェストを検証し、アーカイブを安全に展開してから、展開されたプラグインディレクトリを既存の PluginHost::install パスに渡します。ローカルパスからのインストールは変更されていません:
バージョンが固定されていない場合、ZeroClaw はレジストリインデックス内で最後に一致するエントリを選択するため、レジストリの公開者は重複する名前の順序を意図的に指定する必要があります。
zeroclaw plugin install ./my-plugin
zeroclaw plugin install ./my-plugin/manifest.toml
デフォルトのレジストリ URL は次のとおりです。
https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw-plugins/main/registry.json
プライベートまたはステージング用のレジストリの場合は、コマンドごとに --registry <url> を使用するか、ZEROCLAW_PLUGIN_REGISTRY_URL を設定してください。
レジストリのエントリはこの形式を使用します:
{
プラグイン: [
{
"name": team-calendar,
version: "0.8.5",
"description": チームカレンダーで会議をスケジュールする,
author: サンプル チーム,
機能: [tool],
「url」: https://example.invalid/team-calendar-0.2.0.zip,
sha256: sha256:<zipの16進ダイジェスト>
}
]
}
アーカイブには、ルートレベルの manifest.toml、または manifest.toml を含む 1 つのネストされたプラグインディレクトリのいずれかが含まれている必要があります。トラバーサルパス、絶対パス、Windows ドライブプレフィックス付きパス、または複数のマニフェストを含むアーカイブは、インストール前に拒否されます。ダウンロードはストリーミング中に上限が設けられているため、Content-Length のないサーバーが ZeroClaw に過大なアーカイブをバッファリングさせることはできません。展開にも上限が設けられているため、圧縮されたアーカイブが一時インストール領域で無制限に展開されることはありません。
検索は認証なしの探索です。インストールがセキュリティ境界となります。レジストリからのインストールは、設定されたプラグイン署名ポリシーと信頼された発行者キーを使用します。これは PluginHost::install を通じたローカルプラグインのインストールと同じです。
スキルのみのプラグインレイアウト(Markdownバンドル)
skill のみを機能として提供するプラグインは、skills/ ディレクトリ配下に agentskills.io 形式でスキルを同梱し、wasm_path を省略します:
my-toolkit/
manifest.toml # declares the skill capability, no wasm_path
README.md # optional bundle-level overview
skills/
design-review/
SKILL.md
scripts/
references/
code-review/
SKILL.md
data-analysis/
SKILL.md
references/
各 SKILL.md には、name フィールドと description フィールドを含む YAML フロントマターを記述する必要があります。いずれかが欠落しているスキルを含むバンドルは、最初の呼び出し時ではなく検出時にランタイムによって拒否されます。スキルは、ユーザーが作成したスキルとの衝突やバンドル間の衝突を回避するため、plugin:<plugin-name>/<skill-name> 形式(例: plugin:my-toolkit/design-review)のプラグイン名前空間付き ID で登録されます。
マニフェスト形式
マニフェストとは、プラグインディレクトリ内にある manifest.toml という名前のファイルです。そのフィールドは crates/zeroclaw-plugins/src/lib.rs 内の PluginManifest の serde サーフェスであり、これが信頼できる情報源となります。
| フィールド | 必須 | 意味 |
|---|---|---|
name | yes | 一意の正規パッケージスラッグであり、派生する各インスタンス設定キーのパッケージ部分です。これはそれ自体がオペレーター設定キーではありません。1~128文字の小文字 ASCII 文字を使用し、先頭と末尾は [a-z0-9] とし、その間には [a-z0-9._-] のみを使用します。検出時に無効な名前や重複した名前は拒否されます。 |
version | yes | バージョン文字列(例:0.1.0)。 |
description | いいえ | zeroclaw plugin list によって表示される人間が読める説明。 |
author | いいえ | 作者名または組織。 |
wasm_path | WASM 機能について | コンポーネントのファイル名(プラグインディレクトリからの相対パス)。唯一の capability が skill の場合を除き必須。指定されたファイルが存在しない場合、検出はプラグインをスキップします。 |
capabilities | はい、空ではありません | プラグインの種類: tool、channel、memory、observer、skill のいずれか(PluginCapability、snake_case でシリアライズ)。 |
permissions | いいえ | コードがアクセスできるホストサービス: http_client、config_read、file_read、file_write、memory_read、memory_write(PluginPermission)。現在強制されるのは最初の2つのみで、残りは受け付けられますが機能しません。config_read の宣言には config_schema が必要で、現在これを提供するのはツール/チャネルアダプターのみです。 |
config_schema | config_read と完全に一致して | このプラグインのプライベート設定用の Draft 2020-12 JSON Schema を作成します。これは正規マニフェストのバイト列に含まれるため、マニフェストへの署名によって保護されます。ルートは properties マップを持ち、additionalProperties = false であるオブジェクトでなければなりません。トップレベルのすべてのプロパティには、直接またはローカル JSON Pointer を介して、サポート対象の型を 1 つ明示しなければなりません。型は string、boolean、integer、number、array、または object です。ツールおよびチャネルの利用側は、トップレベルの文字列プロパティに直接 x-secret = true を設定して、そのプロパティを公開設定から削除し、スコープ付きの secrets.get ホストインポートを介して公開できます。ツールは __config 配下で公開設定を受け取り、execute 中にシークレットを読み取れます。チャネルは、configure および運用呼び出し中に、config.get を通じて現在の公開オブジェクトを読み取り、secrets.get を通じてシークレットを読み取ります。両方のインポートは、インスタンス化および静的メタデータ検出中は利用できません。ネストされたシークレットマーカー、false のシークレットマーカー、または boolean でないシークレットマーカー、および文字列以外のシークレットプロパティは拒否されます。config_read のないスキーマ、またはスキーマのない config_read は拒否されます。 |
signature | いいえ | 正規化されたマニフェストバイト列に対する Base64url Ed25519 署名。配布用に署名する際に設定されます。 |
publisher_key | いいえ | 署名者の16進数エンコードされたEd25519公開鍵。 |
コードが実際に使用する権限のみを宣言してください。宣言されていない権限はコンポーネントが到達できないホストサーフェスであり、不要に宣言された権限は自ら求めた攻撃対象領域であり、プラグインをレビューする人にとっての監査負担です。
オペレーター値は plugins.entries 内では文字列のまま保持され、永続化時には、ホスト所有のパッケージ、ケイパビリティ、バインディングの識別情報から導出されたバージョン付きの zpi1_… 文字列をキーとして暗号化されます(インストール時に出力され、デフォルトのツールバインディングのフルインスタンスキーにシードされます)。文字列はそのまま保存され、ブール値と数値には JSON スカラーのテキストが使用され、配列とオブジェクトには JSON テキストが使用されます。ゲストコードが実行される前に、ホストはそれらの文字列をパッケージスキーマの型に実体化し、ツールアダプターとチャンネルアダプターについてオブジェクト全体を検証します。シークレットではないツールプロパティで __config が構成され、チャンネルは config.get を介してシークレットではないオブジェクトを取得します。x-secret = true とマークされたプロパティは両方の公開サーフェスから省略され、認可されたサービスフレーム内で secrets.get("property") を介してのみ利用できます。1 回の呼び出し中のチャンネルの公開読み取りとシークレット読み取りは単一の正規リビジョンを共有し、ホストは呼び出しの終了時にその実体化されたビューを破棄します。準拠するチャンネルプラグインは、利用する各時点でその両方を 必ず 解決し、ウォームなゲスト状態に設定や資格情報の値を保持してはなりません。ゲストに平文を返すと、ホストは悪意のあるコードに対して値を保持しないことを強制できなくなります。config_read が要求されたものの実効的に許可されなかった場合、ホストは空のオブジェクトを検証します。そのため、必須プロパティを持つスキーマは、必須設定なしで起動するのではなくフェイルクローズします。空のオブジェクトが有効な場合、ツールは空の __config を省略し、チャンネルの設定/シークレットインポートは access-denied を返します。認可されたフレーム外の呼び出し、解決失敗、ホスト呼び出し予算の枯渇は unavailable を返します。
機能
capabilities は crates/zeroclaw-plugins/src/lib.rs で定義された PluginCapability 値の空でないリストです(snake_case としてシリアライズされます)。各値は、プラグインがエクスポートする WIT ワールド(tool、channel、memory)を選択するか、可観測性バックエンド(observer)を指定するか、Markdown のみのスキルバンドル(skill)としてマークします。正規のセットについては enum を参照してください。これが信頼できる情報源であり、このページでは再掲しません。
マニフェストは少なくとも1つのケイパビリティを宣言する必要があります。wasm_path は、唯一のケイパビリティが skill であるプラグインを除くすべてのケイパビリティに必須です。そのプラグインは WASM ペイロードを持たず、有効な skills/ バンドルを省略すると検出時に拒否されます(host.rs の validate_manifest_shape)。
パーミッション
permissions は PluginPermission 値のリストであり、こちらも crates/zeroclaw-plugins/src/lib.rs で定義されています。正規のセットについては enum を参照してください。
宣言されているものと強制適用されるものの差異に注意してください。現在のコンポーネントホストでは、config_read と http_client が動作に影響します。config_read を要求するには config_schema が必要であり、その権限なしにそのスキーマを宣言することも拒否されます。ツールまたはチャネルコンポーネントを使用する前に、ホストはその実効権限付与を解決し、プラグインのオペレーター値を型付き JSON として実体化して、完全なオブジェクトを検証します。runtime.rs は、検証済みの非シークレット値をツール呼び出しに注入する前に、呼び出し元から提供された __config をすべて削除します。x-secret: true とマークされたトップレベルに直接ある文字列プロパティは公開設定から除外され、ホストスコープの secrets インポート経由で読み取られます。ツールは execute 中にそのサービスを受け取ります。チャネルは configure および運用時の呼び出し中、公開設定を config.get 経由で、シークレットを secrets.get 経由で受け取りますが、インスタンス化と静的メタデータの検出は引き続き利用できません。http_client は必要な権限付与ですが、権限を完全に決定するものではありません。ケイパビリティアダプターは HTTP コンテキストも構築し、wasi:http をリンクする必要があります。ツールおよびチャネルのアダプターは、権限付与の検証後にオプトインします。メモリアダプターは意図的にオプトインしないため、メモリスコープに http_client を付与するだけでは、ネットワークへのアクセス範囲は広がりません。残りのバリアント(file_read、file_write、memory_read、memory_write)はマニフェストスキーマでは受け入れられますが、まだホストインポートに接続されていません。これらを宣言しても、それだけでは何も付与されません。これらを制御するホスト関数のために名前を予約しています(下記のホストインポートを参照)。
WIT インターフェース
プラグインコントラクトは wit/v0/ にある WIT ファイルの集合であり、パッケージは zeroclaw:plugin@0.1.0 です。すべての項目はパッケージが安定化するまで @unstable(feature = plugins-wit-v0) でゲートされています。互換性ルールについては wit/VERSIONING.md を参照してください。以下のインターフェースは概要把握のために要約したものです。正確なシグネチャについては .wit ファイルが正式なものとなります。
ワールド
wit/v0/ では、component.rs の bindgen! によってバインドされた 3 つのワールドを定義しています。各ワールドは logging(ホスト)をインポートし、plugin-info と主要インターフェースをエクスポートします。tool-plugin は tool、channel-plugin は channel、memory-plugin は memory をエクスポートします。Tool はさらに secrets を、channel は config、secrets、inbound をインポートします。各ワールドで必須(デフォルトなし)のエクスポートは、それぞれの .wit ファイルにあるワールドのドキュメントコメントに一覧表示されています。
tool インターフェース
wit/v0/tool.wit は単一ツールのインターフェースを定義します。ホストは読み込み時に name、description、parameters-schema を一度だけ呼び出し、その後呼び出しごとに execute をディスパッチします:
record tool-result {
success: bool,
output: string,
error: option<string>,
}
name: func() -> string;
description: func() -> string;
parameters-schema: func() -> json-string;
execute: func(args: json-string) -> result<tool-result, string>;
parameters-schema はツール呼び出しのために LLM に提示される JSON Schema 文字列を返します。execute はそのスキーマに一致する JSON エンコードされた引数を受け取り、tool-result またはエラー文字列を返します。json-string は wit/v0/types.wit の string 型エイリアスです。呼び出し側は有効な JSON を生成し、受信側はそれを解析します。
channelおよびmemoryインターフェース
wit/v0/channel.wit と wit/v0/memory.wit は、ケイパビリティでゲートされたサーフェスを定義します。ホストはロード時に get-channel-capabilities / get-memory-capabilities を一度呼び出し、設定されていない各フラグについては、プラグインを呼び出す代わりに Rust トレイトのデフォルトを使用します。プラグインは依然としてすべての関数をエクスポートしなければなりません(文書化されたデフォルト値を返すスタブで十分です)。ホストは単に、フラグが存在しない関数を決して呼び出さないだけです。設定されていない各フラグが解決されるデフォルトは、*-capabilities フラグの隣の WIT にインラインで文書化されており、これがフラグセットとそのデフォルトの両方に対する信頼できる情報源です。
機能フラグ
オプションのメソッドは flags channel-capabilities および flags memory-capabilities を通じてアドバタイズされます。フラグはビットマスクであるため、新しいオプションのメソッドは新しい @since 関数とペアにして、破壊的変更なしに vN/ パッケージに追加できます。フラグ、関数、フィールド、またはバリアントケースの削除や名前変更は破壊的変更であり、新しい vN+1/ ディレクトリが必要です。
ホストのインポート
ホスト関数はプラグインによってインポートされ、ランタイムによって提供されます。各ワールドのリンカーは、component.rs で add_wasi とともにリンクされる component_logging.rs のホスト実装を介して、logging を組み込みます。Tool と channel は、インスタンススコープの secrets サービスをリンクします。channel は、型付きの公開オブジェクト用に config を、また poll-message から取り出すホスト供給のメッセージキュー用に inbound をインポートします。Tool と channel のアダプターは、許可済みスコープが http_client を付与した後にのみ、送信用の wasi:http をリンクします(PluginStoreSpec::with_granted_http と component.rs の add_wasi_http)。Memory にはコンテキストもリンカーの公開面もありません。ファイルシステムとメモリアクセスの権限は無効なままです。これらを制御するホスト関数は、まだリンカーに組み込まれていません。プラグインのアンビエント権限は、WASI コンテキスト(プレオープンなし、アンビエントネットワークなし)に加えて、その権限付与とアダプターのオプトインを組み合わせて有効化されるホストインポートだけです。
ZeroClaw 所有のインポートは、ホストによってディスパッチされるサービスフレームごとに、固定の安全予算を共有します。基準となる上限は、crates/zeroclaw-plugins/src/component.rs にある MAX_HOST_CALLS_PER_FRAME です。上限に達すると、ロギングは何もしなくなり、受信ポーリングは空の結果を返し、公開設定またはシークレットの読み取りは unavailable を返します。新しいフレームで予算がリセットされます。この上限は固定されたホストポリシーであり、オペレーター設定を重複させたものではありません。
inbound
wit/v0/inbound.wit は channel-plugin ワールドによってインポートされます。チャネルプラグインは独自のリスナーを持たずに実行されるため、ホストがリスナー(Webhook サーバー、ベンダートンネル、ポーリングクライアント)を実行し、受信した各メッセージをキューに入れます。プラグインは inbound-poll を呼び出すことで poll-message エクスポートからキューをドレインし、バッチでドレインするために inbound-pending を使用できます。
inbound-poll: func() -> option<host-inbound-message>;
inbound-pending: func() -> u32;
ホスト側はチャネルごとに InboundQueue を保持します。WasmChannel::inbound はクローンをリスナータスクに渡すため、エンキューされたトラフィックはプラグインのドレインから参照できます。
logging
wit/v0/logging.wit は3つのワールドすべてにインポートされます。プラグインは log-record を呼び出して、構造化されたイベントをホストに返します:
log-record: func(level: log-level, event: plugin-event);
呼び出しは fire-and-forget 方式です。何も返さず、ホスト(component_logging.rs)がすべてのエラーを吸収するため、ログ書き込みの失敗によってプラグインの実行がクラッシュすることはありません。配信は非同期です。import はレコードを容量上限のあるホスト側キューに渡し、専用スレッドがそこから取り出して処理する間もブロックせずに戻ります。そのため、処理の遅い、またはハングしたログコンシューマーによって、ゲストエクスポートが plugins.limits.call_timeout_ms を超えてブロックされることはありません。処理を後回しにしてもイベントの意味は変わりません。各レコードはゲストの呼び出し箇所でその時点で有効なホストスパンを捕捉し、そのスコープ内で書き込まれるため、エージェント/チャネル/ツールの帰属情報と終端ラベルは、インラインで出力した場合と一致します。この上限は実際のメモリ上限です。イベントフィールドは長さに上限のない文字列であり、ゲストの max_memory_mb 上限の対象外であるホストメモリにコピーされるためです。ゲストが制御するバイト数が 64 KiB を超えるレコードは切り詰めずに破棄され、キューに入ったレコードは合計 8 MiB の固定バイト予算を使用し、その予算はレコードが書き込まれた後にのみ解放されます。キューが満杯である場合、レコードが上限を超える場合、または予算を使い果たした場合は、いずれも最新のレコードが破棄されます。ドレインスレッドは各書き込み後とアイドル状態からの起床時に累積ドロップ数を報告するため、拒否されたレコードの後に受け入れられたレコードが一つも続かない場合でも、損失を観測できます。plugin-action と plugin-outcome は zeroclaw-log にある閉じた Action / EventOutcome 分類体系を反映します。エスケープハッチとなるバリアントは意図的に用意していません。wasi:logging を直接呼び出さないでください。直接呼び出すとプラグインイベントのフォーマットが一貫しなくなり、zeroclaw_log のすべての出力先に届かなくなります。
config
wit/v0/config.wit は channel world によってインポートされます。現在のスキーマ検証済みで、秘密情報を含まないオブジェクトを JSON として返します。
get: func() -> result<json-string, config-error>;
このオブジェクトは config_schema で宣言された型を保持します。x-secret: true とマークされたプロパティは省略されます。このサービスは configure 中および運用チャネルのエクスポート中に利用できます。許可されたインスタンスに有効な config_read 権限がない場合は access-denied を返します。コンポーネントの初期化中または静的メタデータの検出中の呼び出し、リゾルバーまたは検証の失敗、ホスト呼び出し予算の枯渇時には、内部の詳細を公開せずに unavailable を返します。
config.get は読み込み時のスナップショットではなく、使用時点でのアクセスです。準拠するチャネルプラグインは、設定を使用する各操作で 必ず これを呼び出し、その戻り値のオブジェクトをウォームなゲスト状態に保持してはなりません。これはプラグインの準拠要件です。JSON を信頼されたゲストコードに返した後、ホストは悪意のあるコンポーネントによるそのコピーを防止できません。
secrets
wit/v0/secrets.wit は tool world と channel world によってインポートされます。ゲストが提供するのはトップレベルのプロパティ名のみです:
get: func(name: string) -> result<string, secret-error>;
ホストは、承認された PluginInstanceScope からパッケージ、ケイパビリティ、バインディング、および実効的な許可を導出します。これらはいずれもゲストからの入力ではありません。マニフェストスキーマで x-secret: true とマークされた、トップレベル直下の文字列プロパティだけが読み取り可能です。ホストが execute をディスパッチしている間、ツールはそれらを読み取れます。チャネルは configure 中、および send、poll、health、ケイパビリティでゲートされたアクションなどの運用呼び出し中に、それらを読み取れます。コンポーネントの初期化と静的メタデータのエクスポートは、設定を解決せずに unavailable を返します。1つのチャネルサービスフレーム内では、すべての config.get と secrets.get が同一の解決済み正規設定リビジョンを使用し、そのフレームはすべての終了経路で破棄されます。したがって、準拠するプラグインは、次の操作で同一バインディングの公開値とシークレット値のローテーションを同時に観測します。access-denied、not-found、unavailable は、リゾルバーやスキーマの詳細を意図的に一切明らかにしません。読み取りに成功すると、プレーンテキストが信頼されたゲストに返されます。このサービスは公開値の注入とインスタンス間の選択を防ぎますが、値をプラグインコードから隠し続けるエグレスプロキシではありません。準拠するチャネルプラグインは、必ず使用箇所ごとにシークレットを解決し、ウォーム状態で別のコピーを保持してはなりません。ホストは、プレーンテキストを返した後に保持しないことを強制できません。
プラグインごとの設定(__config と config.get)
権限: config_read
プラグインはプロセス環境変数を読み取りません。マニフェストでは config_read と Draft 2020-12 の config_schema を組み合わせる必要があり、どちらか一方しかないマニフェストは無効です。スキーマのルートは、properties マップを持ち、additionalProperties = false であるオブジェクトでなければなりません。各トップレベルプロパティでは、直接、またはパッケージローカルの JSON Pointer を介して、string、boolean、integer、number、array、object のいずれかを宣言しなければなりません。ツールおよびチャネルの利用側は、直接のトップレベル文字列プロパティに x-secret: true を設定できます。入れ子になったマーカー、false のマーカー、または boolean 以外のマーカー、および文字列以外のシークレットプロパティは拒否されます。未知のキー、形式不正なエンコーディング、または制約違反があると、値がゲストコードに渡される前にインスタンスが拒否されます。
オペレーターの正規の plugins.entries.<instance-key>.config 値は、メモリ内ではシークレットとしてマークされた文字列マップのままとなり、永続化時には暗号化されます。ホストは、パッケージ、ケイパビリティ、バインディングの完全な識別情報からバージョン付きの zpi1_… エントリキーを導出します。これにより、異なるパッケージやケイパビリティの環境で、main のようなエイリアスを安全に再利用できます。承認済みパッケージのマニフェストによってスキーマが選択されます。string 値はそのまま保存されます。boolean、integer、number の値には "true"、"4"、"0.5" のような JSON スカラー文字列が使用されます。array と object の値には '["urgent","ops"]' や '{"region":"us-east"}' のような JSON テキストが使用されます。ホストは、使用するたびに完全なデータを型付き JSON として実体化して検証し、その後、すべてのプロパティを正確に 1 回だけ振り分けます。ツールの場合、非シークレット値は予約済みの __config キーの下に注入されます。
{
"prompt": 夕焼け,
__config: {
"retry_limit": 4,
"有効": true,
"ラベル": ["緊急", "ops"]
}
}
スキーマでシークレットとして指定されている場合、省略された api_key は secrets.get("api_key") によって明示的に読み取られます。公開セクションを注入する前に、runtime.rs は呼び出し元から提供された __config をすべて削除するため、セクションを偽装することはできません。1 つの execute フレーム内でのツールへの公開設定の注入とシークレットの読み取りは、解決済みのライブ設定リビジョンを共有します。フレームは成功、エラー、トラップ、パニック、またはキャンセル時に破棄されます。チャネルの configure エクスポートには config パラメーターがありません。公開オブジェクトには config.get を、シークレットプロパティには secrets.get を呼び出します。設定を使用する後続の各オペレーション用エクスポートも同様です。1 回の呼び出し内で両方のインポートが、1 つの解決済みリビジョンを共有します。ホストは成功、エラー、トラップ、パニック、またはキャンセル時に、そのマテリアライズドビューを破棄します。したがって、準拠するゲストが使用時点で両方を解決する場合、同じ論理バインディング内での公開設定と認証情報の変更は、次の操作で同時に利用可能になります。
マニフェストが config_read を要求していても、ホストがそれを実質的に許可していない場合、解決処理では空のオブジェクトに置き換え、ゲストコードの実行前にそのオブジェクトを検証します。必須フィールドを持つスキーマは、構築時にフェイルクローズします。空のオブジェクトが有効な場合、ツールは空の __config を省略します。一方、チャネルの config.get と secrets.get は access-denied を返します。プラグインから見えるのは常に自身のセクションだけです。
チャネルの静的メタデータエクスポートはどちらの構成サービスも呼び出せず、ロード時に一度だけ読み込まれます。そのため、ボット/アカウントのアイデンティティ、または構成から導出される機能、セルフハンドル、メンション、複数メッセージの遅延を変更するには、チャネルのライフサイクルを再構築する必要があります。一方、同じ論理バインディングに対する通常のパブリック構成の変更や認証情報のローテーションでは、その必要はありません。現在の構成コンシューマーは Tool とチャネルです。メモリワールドにはまだ構成のインポート機能がないため、その ABI とランタイム配線が導入されるまで、メモリプラグインは config_read を要求してはなりません。
WASI コンポーネントホスト
ホスト(crates/zeroclaw-plugins/src/component.rs)は、単一の非同期 wasmtime::Engine に対してコンポーネントをコンパイルおよびインスタンス化します。.wasm ファイルの読み込み方法は、ビルドの実行バックエンドによって異なります。
plugins-wasm-cranelift: JIT バックエンドが存在するため、load_componentは読み込み時にComponent::from_file経由で.wasmコンポーネントをコンパイルします。- JIT バックエンドなし (
plugins-wasm-pulleyまたはランタイムのみ): バイナリにコンパイラがないため、load_componentはComponent::deserialize_file経由でファイルを直接デシリアライズし、一致する wasmtime によって生成された事前コンパイル済みの.cwasmとして扱います。不一致のアーティファクトはデシリアライズのバージョンチェックによって拒否されます。
両方のバックエンド機能は plugins-wasmtime を取り込みます。ロードパスはビルドに cranelift コンパイラが含まれているかどうかによって決まり、pulley によってではありません。
呼び出しごとの実行制限
すべてのゲストエクスポートは、ホストがストアに適用する呼び出しごとのリソース制限の下で実行されます。エンジンはフューエルメータリングを有効にし、暴走したコンポーネントや悪意のあるコンポーネントがホストをハングさせるのではなくトラップするよう、各呼び出しに新しいフューエル予算を割り当てます。ホストは、wasi:http などの非同期ホストインポートの待機時間も含め、エクスポート全体のフューチャーに対してウォールクロックのデッドラインも適用します。フューエルによる定期的な yield により、中断されないゲスト計算がそのタイマーの処理を妨げることはありません。また、ゲストから到達可能なホストインポートがエグゼキューターをブロックすることもありません(ログレコードは有界キューに渡され、専用のホストスレッドによって書き込まれるため)。そのため、ホスト処理の実行中もデッドラインを監視できます。StoreLimits の上限は、線形メモリ、テーブル要素、インスタンス数を制限します。ツールワールドには実行ごとに新しいストアが用意されます。ウォームチャネルおよびメモリーストアには各呼び出しの前にフューエルが再補給されるため、長時間稼働するプラグインは存続期間中に予算を使い果たすのではなく、新しい予算を得られます。
5 つの上限はオペレーターが調整可能で、すべての値がゼロ以外であることを検証されます。plugins.limits.call_fuel(デフォルトは 1,000,000,000 命令単位)、plugins.limits.call_timeout_ms(デフォルトは 30,000 ミリ秒)、plugins.limits.max_memory_mb(デフォルトは 256)、plugins.limits.max_table_elements(デフォルトは 100,000)、plugins.limits.max_instances(デフォルトは 64)です。ストアは明示的な制限を指定した場合にのみ構築できるため、どのロード経路でもサンドボックス化されていないプラグインを構築することはできません。ゲストの wasi:http リクエストオプションによって呼び出しを早く終了させることはできますが、ホストのデッドラインを延長することはできません。中断されたウォームストアが再開されることはありません。チャネルは次回の呼び出し時に、ホストが所有する入力からストアを再作成します。一方、メモリインスタンスは、その所有者が再構築するまで利用できません。正式なフィールドとデフォルト値は Config リファレンスに記載されています。
32ビットアドレス空間 (wasip2 は wasm32)
プラグインのターゲットは wasm32-wasip2 で、ホストエンジンは wasm_memory64 なしで燃料計測を有効化して(Config::consume_fuel(true))ビルドされています。プラグインの境界は固定された 32 ビット形式であり、それには明確に述べておくべき影響があります:
- ゲストのアドレス空間は32ビットです。 プラグインはwasm32の線形メモリ内で実行されます。大きな値は値渡しで境界を越えます。チャネルプラグインの
media-attachmentは完全なバイト列をlist<u8>として保持し、wit/v0/channel.witではこれが数メガバイトになる可能性があり、リソースハンドルモデルは将来のリビジョンに残すと既に記されています。その32ビット空間内で、ホストはplugins.limits.max_memory_mb(デフォルト256)による明示的なストアごとのメモリ上限を適用するため、ゲストはwasm32アドレス空間とそのZeroClaw設定の上限のうち小さい方で制限されます。 - コンポーネントABIは、ホストのワードサイズに関係なくオフセットを32ビットとして下げます。 64ビットホスト上でも、カノニカルABIにおけるリストと文字列のオフセットは
i32です。memory64はゲストの線形メモリアドレッシングを広げますが、コンポーネントモデルのカノニカルABIを広げるものではないため、有効にしてもWITレベルのフィールドが64ビットになることはありません。 - バインド対象となる64ビットのwasip2ターゲットは存在しません。
wasm32-wasip2は現在のrustcおよびLLVMにおける唯一のWASI Preview 2ターゲットです。プラグインを64ビットのp2コンポーネントにコンパイルできないため、エンジンがmemory64を有効にしてもホストがロードするものはありません。
これはアップストリームのツールチェーンによる制約であり、このリポジトリのフラグで解除できるホスト側の制限ではありません。64ビットのp2ターゲットとより広いコンポーネントABIがアップストリームに導入されれば、bindgen!のシームはそれらに対して再生成され、フィールド幅はwit/VERSIONING.mdのウィンドウ配下のWITで見直されます。それまでは、プラグイン境界は構造上32ビットであるものとして扱ってください。
署名
プラグインマニフェストには Ed25519 署名を含めることができます(crates/zeroclaw-plugins/src/signature.rs)。署名は、正規化されたマニフェストのバイト列(ルートにある正確な signature および publisher_key エントリだけを削除した、解析済みの TOML)に対して base64url エンコードされます。パブリッシャーの公開鍵は 16 進数でエンコードされます。これらの名前を持つネストされたスキーマプロパティは署名の対象に残ります。ホストは plugins.security.signature_mode で指定された 3 つのモードのいずれかを適用します:
| モード | 未署名のプラグイン | 信頼されていない、または無効な署名 |
|---|---|---|
strict | 却下されました | 却下されました |
permissive | 警告付きで読み込まれました | 警告付きで読み込まれました |
disabled | loaded(読み込み済み) | 未チェック |
検証は検出時とインストール時の両方で実行されます。検出時は、ポリシーに違反したプラグインをホスト全体を中断せずにスキップします。インストール時はエラーを返します。
Rustでプラグインを書く
プラグインは、コンポーネントモデルをターゲットとする cdylib クレートです。ホストが使用するのと同じ wit/v0 パッケージからゲストバインディングを生成し、エクスポートされた world を実装して、wasm32-wasip2 にコンパイルします。空のクレートからインストール済みプラグインまでの完全な手順解説については、プラグインガイド を参照してください。以下の注記では、ビルドとインストールの仕組みについて説明します。
ビルド
sh
# WASI Preview 2 ターゲットをインストールする(一度だけ)
rustup target add wasm32-wasip2
# コンポーネントをビルドする
cargo build --target wasm32-wasip2 --release
出力コンポーネントは target/wasm32-wasip2/release/<crate_name>.wasm にあります。これを manifest.toml と同じ場所にコピーしてください。JITバックエンドを持たないランタイム専用のホストビルドでは、対応するwasmtimeでコンポーネントを.cwasmにプリコンパイルし、それを代わりに配布してください。そのようなホストは、読み込み時にコンパイルするのではなくデシリアライズを行うためです。
ホスト側のツールプラグインテストは公開済みアーティファクトに依存しません。crates/zeroclaw-plugins/tests/fixtures/tool-fixture はテスト時にソースからビルドされるリポジトリ内のコンポーネントであり、reference_plugin.rs と reference_plugin_e2e.rs は、デーモンが実行するものと同じ PluginHost、config_schema、および設定解決パスを通じてこれを実行します。フィクスチャをビルドできない場合、これらのテストは失敗します。
インストール
sh
# プラグインディレクトリにコピー
zeroclaw plugin install /path/to/my-plugin/
# または手動で
cp -r my-plugin/ ~/.zeroclaw/plugins/my-plugin/
設定
オペレーター値は現在、汎用の文字列マップストレージを介して入力します。TOML の [[plugins.entries]] を編集するか、ツールのインストールによってデフォルトバインディングのエントリが初期投入された後に zeroclaw config set を使用します。zeroclaw plugin info <package> は、移行と後続の編集に使用する同じツールキーを出力します。これらの自動出力および初期投入の手段はツール専用です。チャンネルキーは設定済みのエイリアスに依存しますが、install と info はその管理対象ではありません。設定済みのエイリアスからチャンネルの型付き設定を解決するエイリアス対応の構築は #10146 で導入されました。チャンネルキーの自動表示とインストール時の初期投入は、#9584 の権限付与セレモニーまでは手動のままなので、チャンネルのみのパッケージでは、install と info だけでこの移行を完了することは依然としてできません。スキーマ駆動フォームとインラインのフィールドヘルプはまだ実装されていません。現在利用できる手段は次のとおりです:
- CLI は
list、search、install、remove、info、migrateを使用してプラグインのライフサイクルを管理します。zeroclaw config setは個々の未加工のプラグイン値を書き込みますが、プラグインのスキーマは解釈しません。 - zerocode は ZeroClaw の静的なプラグインホスト設定を編集できますが、
config_schemaからプラグインごとのフィールドを生成する機能にはまだ対応していません。 - Web ゲートウェイはプラグインに対して読み取り専用です。
GET /api/pluginsは、読み込まれているプラグインとシステムが有効かどうかを報告します。 - ホストはパッケージを受け入れる際に
config_schemaを検証し、ゲストで使用する前にもオペレーターの値を再度検証して実体化します。 - マニフェストスキーマは、プラグイン作成者にとって、ゲスト境界における唯一の型および検証の契約です。サポートされるすべてのキーと制約をそこで定義し、ホストのランタイム設定構造体でその契約を重複して定義しないでください。ゲストコードは、ホストで検証済みの JSON をネイティブの型付き構造体にデシリアライズする必要があります。
静的な設定スキーマは、動的なプラグインごとのエディターではなく、汎用的な保存およびシークレット指定用のパスを提供します。crates/zeroclaw-config/src/schema.rs にあるプラグイン設定型には、#[prefix = "plugins"]、#[prefix = "plugins.entries"]、および #[prefix = "plugins.security"] が付与されており、Configurable derive によって、プレフィックス付きの各フィールドが汎用的な設定パスに変換されます。シークレットフィールド(プラグインエントリの config マップには #[secret] が付与されています)は、隣接する .secret_key の下で保存時に暗号化されます。ホスト設定の正規フィールド、デフォルト値、および signature_mode の値は Config リファレンス に記載されています。そのスキーマが信頼できる唯一の情報源であり、各プラグインマニフェストがプライベート設定の構造に関する信頼できる唯一の情報源です。
ビルド機能
プラグインホストはコンパイル時のオプトインです。ワークスペースの Cargo.toml にあるバイナリレベルの機能によって、プラグインをそもそも組み込むかどうか、およびどの実行バックエンドを同梱するかを選択します。
plugins-wasmは、プラグインホストとそのランタイム統合をバイナリに取り込む包括的な機能です。以下のすべてのバックエンド機能はこれを暗黙的に有効にするため、任意の実行バックエンド(例:--features plugins-wasm-cranelift)を有効にすると、常にプラグインホストとその CLI インターフェイスも含まれます。バックエンドのみのビルドが、pluginサブコマンドを含まないバイナリを黙って生成することはありません。この包括機能だけを有効にした場合はplugins-wasm-runtime-onlyと同等です。JIT はないため、事前コンパイル済みの.cwasmコンポーネントのみがロードされます。plugins-wasm-runtime-onlyは最小かつ起動が最速です。JIT がないため、コンポーネントはプリコンパイルされた.cwasmからデシリアライズされます。plugins-wasm-craneliftは Cranelift JIT を追加するため、.wasmコンポーネントは読み込み時にコンパイルされます。plugins-wasm-pulleyは最も移植性が高く、Cranelift が対応していないターゲットでのコンパイルをサポートします。
これらは wasmtime を接続する zeroclaw-plugins クレートのフィーチャ(plugins-wasmtime、plugins-wasm-cranelift、plugins-wasm-pulley)に委譲します。ロードパスは、WASI Component Host で説明されているように、Cranelift コンパイラがビルドに含まれているかどうかによって決まります。正式な説明については、ワークスペースの Cargo.toml にあるフィーチャのコメントを参照してください。