ツールプラグインの作成
これはシリーズの入門ガイドです。空のクレートから、モデルが会話の中で呼び出すツールまでの完全な作業手順を示します。ここで構築するツールは redact で、メールアドレス、既知の認証情報プレフィックス、およびオペレーターが指定したパターンをテキスト内でマスクします。これは意図的に設定駆動としています。なぜなら、独自の jail 化された設定セクションを読み取ることは、あらゆる非自明なプラグインが必要とするものであり、かつ最も誤りやすいものだからです。
このページの内容はすべて契約ソースと照合されています: wit/v0/tool.wit 内の tool-plugin world、crates/zeroclaw-plugins/src/runtime.rs および wasm_tool.rs 内のホスト側呼び出しパス、および host.rs 内のマニフェスト検証です。ソースパスは検証のための ZeroClaw リポジトリへの引用であり、プラグイン自体はご自身のリポジトリ内のご自身のクレートです。ビルドに ZeroClaw のチェックアウトは不要で、必要なのは wit/ 契約ファイル(ステップ 1 で取得)と、プラグインホストが組み込まれたインストール済みの zeroclaw バイナリだけです。
リリースバイナリは目的のバイナリではありません。 インストーラーに同梱されるビルド済みバイナリにはプラグインホストが含まれておらず(
zeroclaw plugin …は認識されないサブコマンドです)、plugins-wasmはクレートのデフォルトのフィーチャーセットに含まれていません。実行バックエンドを指定してホスト側をソースからビルドしてください。すべてのバックエンドフィーチャーにはplugins-wasmアンブレラ自体が含まれているため、フラグは1つで十分です:cargo build --release --features plugins-wasm-craneliftプロトコルページにバックエンドの選択肢が記載されています。
ツール呼び出しの流れ
コードを書く前に、ランタイムの構造を理解してください。
- 起動時に、検出処理によってプラグインディレクトリが見つけられ、マニフェストの構造が検証され、署名ポリシーが適用され、その後
config_schemaが検証されます。登録前に、ホストはプラグインのオペレーター値を型付き JSON に実体化し、それらを検証します。検証を通過したものがWasmToolインスタンスになります。 - 登録時、ホストは
name、description、parameters-schemaを読み取るためにコンポーネントを一度だけインスタンス化します。これらはキャッシュされ、再度問い合わせられることはありません。そのプローブに失敗すると登録も失敗し、ホストが動作しないコンポーネントの代わりに合成メタデータを使うことはありません。 - 呼び出しごとに、
WasmTool::executeは正規状態から設定を解決して検証し、新しいストア(新しい WASI コンテキスト、新しい燃料予算、前回の呼び出しの状態なし)を作成して、コンポーネントをインスタンス化します。その 1 つの解決済みオブジェクトがフレーム全体で使用されます。ホストは、シークレットではない値だけを__configの下に注入し、スキーマでマークされたシークレットをスコープ付きのsecretsインポート経由で提供して、executeを呼び出します。
呼び出しごとに新しいストアを生成するモデルは、最も重要な設計上の制約です。ツールプラグインは、構造的にステートレスです。呼び出しの間に永続化したいものはすべて、プラグインの外部(返すテキスト内、またはオペレーター設定内)に置く必要があります。
1. クレートのセットアップ
クレートを作成し、ゲスト側の依存関係を追加します:
cargo new --lib my-plugin
cd my-plugin
cargo add wit-bindgen@0.46
cargo add serde --features derive
cargo add serde_json
次に、パッケージマニフェストに2つの手動編集を行います。
- ライブラリの
crate-typeを["cdylib", "rlib"]に設定します。cdylibはコンポーネントのビルドで生成されるものであり、rlibにより同じクレートの純粋なロジックモジュールをホスト上でネイティブにコンパイルおよびユニットテストできます。 - リリースプロファイルでは、
opt-level = "s"、lto = true、およびstrip = trueを設定します。コンポーネントのサイズはダウンロード時間とロード時間です。プラグイン境界を越えてデバッグシンボルを配布する理由はありません。
ZeroClaw リポジトリから wit/v0/ ディレクトリをクレートのルートに wit/ としてコピーします。完全なチェックアウトは不要です。対象ホストのバージョンに一致するタグから、そのディレクトリだけを取得してください:
git clone --depth 1 --filter=blob:none --sparse \
https://github.com/zeroclaw-labs/zeroclaw /tmp/zeroclaw-wit
git -C /tmp/zeroclaw-wit sparse-checkout set wit
cp -r /tmp/zeroclaw-wit/wit .
WITファイルはABIそのものです。ホストはこれらのファイルからバインディングを生成しているため、ゲスト側のバインディングも同じファイルから生成する必要があります。バージョンを固定してください。WITワールドはホストとともに進化するため、ホストがバインドするワールドより新しいワールドに対してビルドされたコンポーネントはインスタンス化に失敗します。
2. ロジックをグルーから分離する
実際の動作は wit-bindgen のインポートを持たない素の Rust モジュールに置き、コンポーネントのグルーコードは薄く保ってください。理由はテスト容易性です。コンポーネントターゲットは cargo test をネイティブに実行できないため、グルーコードに閉じ込められたロジックは wasm ホストを通じたエンドツーエンドでしか検証できません。グルーコードは間違えようがないほど薄くすべきです。
src/redact.rs には、設定用の構造体と純粋関数が含まれています:
#![allow(unused)]
fn main() {
pub const DEFAULT_REPLACEMENT: &str = [REDACTED];
/// プラグイン自身の設定セクションから解決されたリダクションポリシー。
#[derive(Debug, serde::Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct RedactConfig {
pub replacement: String,
pub redact_emails: bool,
pub patterns: Vec<String>,
}
impl Default for RedactConfig {
fn default() -> Self {
Self {
replacement: DEFAULT_REPLACEMENT.to_string(),
redact_emails: true,
patterns: Vec::new(),
}
}
}
/// 入力をマスクする。出力とマスクされたスパンの数を返す。
pub fn redact(input: &str, cfg: &RedactConfig) -> (String, usize) {
// cfg.redact_emails の場合にメールアドレス、認証情報のプレフィックス
// (sk-, ghp_, AKIA, xoxb-)、および cfg.patterns 内の各リテラルをマスクし、
// すべてのヒットを cfg.replacement で置き換えます。
// ...
}
}
ゲストはスキーマに基づいて具体化された公開 JSON オブジェクトを受け取るため、文字列の解析を繰り返すのではなく、一度だけデシリアライズします。この例のスキーマではすべてのフィールドが省略可能であり、ホストが {} を提供した場合の動作は Default が担います。空のオブジェクトは、オペレーターがプラグインを設定していない場合や、ホストが要求された config_read の許可を拒否した場合には正常です。プラグインが値なしでは動作できない場合は、config_schema で必須として指定してください。そうすれば、ゲストコードの実行開始前にホストが空のオブジェクトを拒否します。
3. ワールドを実装する
wit/v0/tool.wit はエクスポートする必要があるインターフェイスを定義します。ワールドは次のとおりです。
world tool-plugin {
import logging;
import secrets;
export plugin-info;
export tool;
}
tool インターフェースは4つの関数です:
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>;
src/lib.rs はゲストバインディングを生成し、両方のエクスポートを実装します。
#![allow(unused)]
fn main() {
pub mod redact;
#[cfg(target_family = wasm)]
mod component {
wit_bindgen::generate!({
path: wit/v0,
world: tool-plugin,
features: [plugins-wit-v0],
});
use crate::redact::{redact, RedactConfig};
use exports::zeroclaw::plugin::plugin_info::Guest as PluginInfo;
use exports::zeroclaw::plugin::tool::{Guest as Tool, ToolResult};
use zeroclaw::plugin::logging::{
log_record, LogLevel, PluginAction, PluginEvent, PluginOutcome,
};
struct RedactPlugin;
#[derive(serde::Deserialize)]
struct ExecuteArgs {
text: String,
#[serde(rename = "__config", default)]
config: RedactConfig,
}
impl PluginInfo for RedactPlugin {
fn plugin_name() -> String {
my-redact-plugin.to_string()
}
fn plugin_version() -> String {
0.1.0.to_string()
}
}
impl Tool for RedactPlugin {
fn name() -> String {
redact.to_string()
}
fn description() -> String {
テキストがログ、 \
チャネル、またはモデルに到達する前に、シークレットとPIIをマスクします。メール、クレデンシャルのプレフィックス、および \
オペレーター設定のリテラルパターンをマスクします。
.to_string()
}
fn parameters_schema() -> String {
serde_json::json!({
"type": "object",
"properties": {
"text": {
"type": "string",
"description": 墨消しするテキスト。
}
},
"required": ["text"]
})
.to_string()
}
fn execute(args: String) -> Result<ToolResult, String> {
let parsed: ExecuteArgs = match serde_json::from_str(&args) {
Ok(a) => a,
Err(e) => {
return Ok(ToolResult {
success: false,
output: String::new(),
error: Some(format!(無効な引数: {e})),
});
}
};
let (output, count) = redact(&parsed.text, &parsed.config);
log_record(
LogLevel::Info,
&PluginEvent {
function_name: my_redact_plugin::tool::execute.into(),
action: PluginAction::Complete,
outcome: Some(PluginOutcome::Success),
duration_ms: None,
attrs: Some(format!({{"redactions":{count}}})),
message: 秘匿化された入力.into(),
},
);
Ok(ToolResult { success: true, output, error: None })
}
}
export!(RedactPlugin);
}
}
ホストソースにそれぞれアンカーされた契約ポイント:
plugin-infoはすべてのワールドの必須エクスポートです。 コンポーネント自身の名前とバージョンを報告します。両方をマニフェストと同期させてください。- メタデータは一度だけ読み取られます。
runtime.rs内のcall_tool_metadataは、登録時にname、description、parameters-schemaを読み取り、キャッシュします。動的なものから計算しないでください。それらは再観測されることはありません。 - スキーマは、モデルから見たツールの全体像です。 ホストはロード時にこれを JSON として解析し(
tool parameters-schema is not valid JSONはハードな登録失敗です)、LLM にそのまま転送します。すべてのプロパティを記述してください。決して__configを宣言しないでください:そのキーはホスト予約済みで、ホストは注入前に呼び出し元が供給した値をすべて取り除きます。これはモデルがオペレーターになりすますことができないようにするためです。 success: falseとErr。success: falseのToolResultは、モデルが反応できる通常のツール応答として返されます(修正した引数で再試行、謝罪、別のツールの選択)。Err(String)はプラグインの障害として境界を越えます。ホストはそれをplugin execute returned errorとしてラップし、呼び出しは失敗します。Errは本当に壊れた状態のために予約し、不正な入力はsuccess: falseで報告してください。- インポートした
loggingインターフェース経由でログを記録し、wasi:loggingは決して使わないでください。log-recordは fire-and-forget です。ホストがすべてのエラーを吸収するため、ログ書き込みの失敗であなたの呼び出しがクラッシュすることはありません。イベントはzeroclaw_logが書き込むすべての出力先に到達し、呼び出しが実行されるホストスパンのzeroclaw.*attribution(agent_alias、session_key、プロバイダー、チャネル)を伴います。plugin-eventのattrsフィールドは帰属情報ではありません:これはログ行の自由形式のattributesペイロードです。帰属情報はエイリアスにバインドされ、ホスト側の環境トレーシングスパンから継承されます。プラグインが送信するものでは設定も上書きもできません。PluginActionとPluginOutcomeはホストの分類体系を反映した閉じた列挙型です。意図的に自由形式のバリアントはありません。最も近いものを選んでください。
4. __config ジェイル
プラグインがプロセス環境変数を読み取ることはなく、グローバル設定を参照することもありません。config_read を要求するマニフェストは config_schema も宣言する必要があり、その権限のないスキーマも同様に無効です。スキーマは Draft 2020-12 であり、そのルートは properties マップと additionalProperties = false を持つオブジェクトでなければなりません。また、トップレベルのすべてのプロパティは、string、boolean、integer、number、array、または object のいずれかに明示的に解決されなければなりません。
ホストは、このインスタンスのパッケージ、tool capability、およびバインディングから導出されたバージョン付き設定エントリキーの下に格納されたセクションを解決し、パッケージスキーマに従って実体化し、完全な型付きオブジェクトを検証してから分割します。シークレットではないプロパティのみが、予約済みの __config キーの下で execute にマージされます:
- モデルが供給する引数に既に存在する
__configは最初に削除されます。スプーフィングは構造的に不可能です。 - Operator ストレージは、引き続き暗号化された文字列マップです。文字列は直接保存し、ブール値と数値は JSON スカラー(
"true"、"4"、"0.5")としてエンコードし、配列とオブジェクトは JSON('["secret-a","secret-b"]')としてエンコードします。ゲストは、これらのストレージ文字列ではなく、実際の JSON のブール値、数値、配列、オブジェクトを受け取ります。 x-secret = trueとマークされたトップレベルの直接の文字列プロパティは、__configから除外されます。生成されたzeroclaw::plugin::secrets::get関数で明示的に読み取ってください。ネストされたマーカー、false または非ブール値のマーカー、および文字列以外のシークレットプロパティは、マニフェストの受け入れに失敗します。- ホストは
executeのディスパッチ中にのみシークレットの読み取りを有効にします。コンポーネントの初期化またはメタデータのエクスポートからの呼び出しは、設定を解決せずにunavailableを返します。1 回の実行中、公開された__configとシークレットの読み取りでは、同じ解決済みの設定ビューが使用されます。 config_readが要求されたものの実質的に許可されなかった場合、ホストは{}を解決して検証します。この例のオプションのスキーマにより、ツールは__configを省略し、#[serde(default)]によってRedactConfig::defaultが選択されます。必須スキーマの場合は、認証情報なしで実行するのではなく、安全側に失敗します。- 未知のキー、無効な JSON エンコーディング、誤った型、スキーマ制約違反がある場合、プラグインはコードが実行される前に拒否されます。現在、オペレーターは、インストール時に表示されたインスタンスキーの下に値を TOML または汎用の
zeroclaw config set経由で設定します。これらの値は、設定の秘密鍵で保存時に暗号化されます。スキーマ駆動の zerocode とゲートウェイエディターは、将来の SDK/設定サーフェス対応です。
このツールの型付きセクションには、3つのオプションキーがあります。replacement は文字列、redact_emails はブール値、patterns は文字列の配列です。
5. マニフェスト
マニフェストとは、プラグインディレクトリ内にある 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 を返します。
このプラグインでは: name と version は plugin-info が報告する内容と一致し、wasm_path はその隣に同梱するコンポーネントファイルを示し、capabilities には正確に tool のみを含め、permissions には正確に config_read のみを含めます。ツールがアウトバウンド HTTP 呼び出しを行う場合にのみ、http_client を追加します。ツールアダプターは wasi:http を実装しますが、その許可が検証された後にのみリンクします。アダプターのサポートと許可の両方がなければ、HTTP サーフェスはありません。
型付き RedactConfig に対応するマニフェストコントラクトは次のとおりです:
name = "my-redact-plugin"
version = "0.1.0"
wasm_path = "my_redact_plugin.wasm"
capabilities = ["tool"]
permissions = ["config_read"]
[config_schema]
"$schema" = "https://json-schema.org/draft/2020-12/schema"
type = "object"
additionalProperties = false
[config_schema.properties.replacement]
type = "string"
minLength = 1
[config_schema.properties.redact_emails]
type = "boolean"
[config_schema.properties.patterns]
type = "array"
items = { type = "string" }
これらのプロパティはオプションで、ゲストのデフォルトに従います。必ず存在する必要がある認証情報の場合は、その名前を [config_schema] の required に追加してください。権限付与の拒否または値の欠落があると、コンポーネントは起動できなくなります。
ネットワークを呼び出すツール
現実世界で最も一般的なツール形態は、redact のような純粋な変換ではなく、外部 API へのブリッジだと言えるでしょう。マニフェストで http_client を宣言し、スコープ付きシークレットサービスを介して認証情報を読み取り、外部リクエストを送信します。署名付きスキーマで認証情報をマークします:
[config_schema]
required = ["api_key"]
[config_schema.properties.api_key]
type = "string"
minLength = 1
x-secret = true
このガイドに対して不足しているのは、コンポーネント内で動作する HTTP クライアントです。reqwest などは、ソケットインターフェースがなく、wasi:http しか利用できないため動作しません。このホストに対して動作することが確認されているクライアントは waki です。これはブロッキング方式なので、execute の同期シグネチャにそのまま適合します。純粋なロジックのモジュールをネイティブ環境で引き続きテスト可能にするため、コンポーネントターゲット向けに条件付きで追加します:
cargo add waki --target cfg(target_family = "wasm")
公開の __config を解析した後、execute 内での呼び出しの形式:
#![allow(unused)]
fn main() {
let api_key = zeroclaw::plugin::secrets::get(api_key)
.map_err(|_| api_key は利用できません.to_string())?;
let resp = waki::Client::new()
.get(https://api.example.com/search)
.query([(q, term.as_str())])
.header(「Authorization」, format!(Bearer {api_key}))
.connect_timeout(std::time::Duration::from_secs(5))
.send()
.map_err(|e| format!(リクエストに失敗しました: {e}))?;
}
一見すると破損のように見えるが実際にはそうではない、2つのバージョンに関する事実があります。waki は、あなたの world バインディングが使用する 0.46 とは別に、独自の wit-bindgen(0.34)をベンダリングしています。この2つは共存し、それぞれが独自のバインディングを生成します。また、waki は wasi:http@0.2.4 のインポートを出力しますが、現在のツールチェーンのベースラインは @0.2.6 です。ホストは両方を問題なくリンクします。いずれも対応は不要です。
overview で説明した信頼の枠組みを思い出してください。http_client はオール・オア・ナッシングです。サンドボックスは、許可されたプラグインがデータを送信する先を制限しないため、strict 署名ポリシーを運用するオペレーターは、URL の許可リストではなく、あなたのコードを信頼していることになります。
6. ロジックをネイティブでテストする
redact.rs には wasm 依存がないため、ホスト上ではプレーンな cargo test でカバーされます:
#![allow(unused)]
fn main() {
#[test]
fn empty_config_falls_back_to_defaults() {
let cfg: RedactConfig = serde_json::from_str("{}").unwrap();
let (out, n) = redact(a@b.example にメールしてください, &cfg);
assert_eq!(n, 1);
assert!(out.contains([REDACTED]));
}
}
最低限カバーすること: ジェイルケース(空のセクション)、設定済みケース、およびマスク対象がないテキストのクリーンなパススルー。glue が転送するすべての動作は、wasm ツールチェーンが視界にない状態でここで証明可能でなければならない。
7. ビルド
WASI Preview 2 ターゲットを一度インストールしてから、コンポーネントをビルドします:
rustup target add wasm32-wasip2
cargo build --release --target wasm32-wasip2
コンポーネントは target/wasm32-wasip2/release/<crate_name>.wasm に生成されます(クレート名のハイフンはアンダースコアに変換されます)。プラグインディレクトリを構成する際は、マニフェストの wasm_path で宣言した名前に変更してください。
[!IMPORTANT] コンパイル済みの
.wasmおよび.cwasmファイルはバイナリ成果物であり、多くの場合それぞれ数メガバイトになります。Git LFS を使わずに Git のソースツリーへコミットしないでください。リビルドのたびに素のブロブとしてコミットされると、リポジトリの履歴が永久的に肥大化し、git diffやレビューツールがそれらの処理でつまずきます。これらは他のビルド出力と同様に扱ってください。target/と*.wasm/*.cwasmを.gitignoreに追加し、代わりにリリース成果物やプラグインレジストリのアーカイブを通じて配布してください。どうしても成果物をツリー内に置く必要がある場合は、最初のコミットの前に LFS でパターンを追跡してください(git lfs track "*.wasm")。
ターゲットホストがランタイム専用ビルド(JITバックエンドがコンパイルに含まれていない)の場合、ロード時に .wasm をコンパイルできません。代わりに事前コンパイル済みの .cwasm をデシリアライズします。ホストのバージョンと一致する wasmtime CLI で事前コンパイルし、.cwasm を wasm_path アーティファクトとして同梱してください。バージョンが一致しないアーティファクトは、wasmtime のデシリアライズチェックによって拒否され、サイレントに誤ロードされることはありません。
8. インストールと検証
これらのコマンドには、プラグインホストをコンパイルして組み込んだバイナリが必要です。 インストーラーが配布するビルド済みリリースバイナリは
plugins-wasm機能なしでビルドされているため、そこではzeroclaw plugin ...は認識されないサブコマンドとなり、インストール済みのプラグインは検出されません。プラグイン実行バックエンドを指定してソースからビルドしてください。例:cargo build --release --features plugins-wasm-cranelift。
各プラグインは、プラグインディレクトリ(デフォルト ~/.zeroclaw/plugins/、plugins.plugins_dir を通じて解決)の独自のサブディレクトリに配置され、マニフェストとマニフェストの wasm_path に一致する名前のコンポーネントを保持します:
~/.zeroclaw/plugins/
└── my-plugin/
├── manifest.toml
└── my-plugin.wasm
ローカルディレクトリからインストール(これはマニフェストの形状を検証し、何かをコピーする前に署名ポリシーを実行します):
zeroclaw plugin install ./my-plugin/
プラグインシステムを有効にして検出を確認する:
zeroclaw config set plugins.enabled true
zeroclaw plugin list
zeroclaw plugin info my-plugin
zeroclaw plugin list と zeroclaw plugin info は、パッケージがインストールされ、検出可能であることを確認しますが、検出は有効化を意味しません。plugins.enabled = true によってプラグインホストが有効になります。自動検出されたツールおよびスキルの機能は、plugins.auto_discover = true も設定されている場合にのみ実行時に読み込まれ、このフラグはデフォルトでは false です(フェイルクローズド):
zeroclaw config set plugins.auto_discover true
したがって、plugins.enabled = true だけでは、[channels.plugin.<alias>] の下で宣言したチャンネルのみが有効になり、プラグインのツールやスキルは有効になりません。ツールまたはスキルのパッケージが zeroclaw plugin list に表示されていても、実行時には何も提供しないことがあります。明示的なチャンネルバインディングは自動検出ではなくオペレーターが名前を指定するものなので、auto_discover は不要です。このフラグが制御するのは、自動検出されたツールとスキルだけです。
zeroclaw plugin list にないプラグインは検出時にスキップされました: 起動ログでスキップ警告を確認してください(形式が不正なマニフェスト、wasm_path ファイルの欠落、または署名ポリシーによる拒否)。
9. 実行する
エージェントにツールの使用を依頼してください:
> redact this before you log it: key sk-live-abc123, mail ops@example.com
モデルはカタログ内であなたのスキーマとともにredactを確認し、それを呼び出します。ホストは設定されたfuelとメモリ制限のもとで、新しいストア内にコンポーネントを実行します。プラグインツールは組み込みの読み取り専用の自動承認セットには含まれないため、完全な自律性ではない場合、その呼び出しは他の特権ツールと同様にオペレーター承認プロンプトを表示します。これに驚かないよう、ツールの説明の中で想定しておいてください。あなたのlog-recordイベントは、ホスト呼び出し箇所のスパン帰属とともに構造化ログに表示されます。
プラグイン概要で述べた、繰り返し強調しておくべき2つの運用上の制約:
- ツール名は組み込みと衝突してはいけません。 組み込みツールが先に登録され、ディスパッチは最初の一致を解決します(ランタイムの
find_tool)。そのため、組み込みと同じ名前のプラグインツールは決して選択されません。エラーはなく、ただ沈黙するだけです。一意な名前を選んでください。 - コンポーネントごとに1つのツール。
tool-pluginワールドは単一のtoolインターフェースをエクスポートします。ツールボックスは複数のプラグイン依存関係であり、それぞれ1つのコンポーネントです。
トラブルシューティング
| 症状 | 考えられる原因 |
|---|---|
zeroclaw plugin list にプラグインが表示されない | プラグインシステムが無効になっています。マニフェストの形式が不正であるか、wasm_path のファイルが見つからないか、署名ポリシーによって拒否されました。起動ログに具体的なスキップ警告が記録されています。 |
zeroclaw plugin list には表示されるが、ツールは読み込まれない | plugins.auto_discover は false(デフォルト)です。自動検出されたツールとスキルの機能は plugins.auto_discover = true の場合にのみ読み込まれます。plugins.enabled = true だけでは、明示的に宣言されたチャネルのみが有効になります。zeroclaw config set plugins.auto_discover true を実行してください。 |
| 登録時にツールが拒否されました | 設定の検証またはメタデータプローブに失敗しました。具体的なエラーについてはログを確認してください。プローブの失敗は通常、コンポーネントが互換性のない WIT に対してビルドされたことを意味します。 |
| モデルによって選択されることのないツール | 名前がビルトインと衝突しているか、説明/スキーマがモデルにツールの適用タイミングを伝えていない。 |
設定されたセクションがあるにもかかわらず __config が存在しません | 有効なスコープによって config_read が拒否された、エントリがインストール時に出力された完全なインスタンスキーを使用していない、検証済みオブジェクトが空である、または検証済みプロパティがすべて secret としてマークされている場合です。config_schema/権限の不一致の場合は、代わりにプラグインが拒否されます。 |
secrets.get は not-found を返します | プロパティが存在しないか、許可されたスキーマ内で x-secret = true とマークされた直接のトップレベル文字列ではありません。 |
secrets.get は unavailable を返します | execute の外部で呼び出しが実行されたか、設定の解決に失敗したか、実行が固定のホスト呼び出し予算を使い果たしました。 |
| 呼び出しに失敗するか、トラップが発生する | Fuel、実時間、またはメモリの上限に達しました。適切な plugins.limits.call_fuel、plugins.limits.call_timeout_ms、または plugins.limits.max_memory_mb の値を引き上げるか、1 回の呼び出しで実行する処理を減らしてください。 |
| ランタイム専用ホストでのロードに失敗します | JIT のないホストに .wasm をデプロイしました。代わりにバージョンが一致した .cwasm をデプロイしてください。 |
次へ
- warm-store のライフサイクル、機能フラグ、ホスト供給の受信についてはチャネルプラグインの作成を参照してください。
- プラグインの配布(このツールがマシンから離れる場合)。