メモリプラグインの作成
メモリプラグインはストレージバックエンドです。エージェントが記憶した内容を永続化し、リコールクエリに答えます。これは最もデータモデルが重いプラグインの種類です。ツールが1つの関数を持ち、チャネルがメッセージ形状を持つところ、メモリバックエンドはマルチエージェントの属性付け、名前空間、セッション、カテゴリ、重要度の重み付けを持ち、ランタイムのメモリセマンティクス(スコープ付きリコール、GDPRエクスポート、置換)は、行モデルを正しく実装することに依存しています。
このガイドは、ツールプラグインの基礎と、チャネルガイドで説明したウォームストアのライフサイクルを理解していることを前提としています。内容は wit/v0/memory.wit と、crates/zeroclaw-plugins/src/wasm_memory.rs 内のホストアダプターに照らして検証されています。
接続状況。
WasmMemoryはmemory-pluginワールド向けにランタイムの完全なMemoryトレイトを実装しており、ケイパビリティによる制御と単体テストによるカバーが行われています。ランタイムはまだこれを構成可能なバックエンドとして構築しておらず、ホストにはchannel_plugin_details()に対応するメモリ用のものがありません。チャネルと同様、コントラクトを基準に実装してください。固定されるのは WIT ワールドとアダプターのセマンティクスです。メモリワールドにはまだ設定エクスポートもないため、config_readを要求せず、型付き設定 ABI とリゾルバーがWasmMemoryに組み込まれた後にのみ追加してください。
データモデル
1つのレコード型が両方向に境界をまたぎます。memory-entry(memory.wit)。ストレージを設計する前にそのフィールドを把握してください。オプションのメソッドはすべてそれらに対するビューだからです:
| フィールド | 意味 |
|---|---|
id | 行の識別子。 |
key | ルックアップキー。一意ではない: 複数の行がキーを共有する場合があり、エージェントごとに1つです。ストレージは key ではなく (key, agent-id) でキー付けする必要があります。 |
content | 記憶されたテキスト。 |
category | core(長期的な事実)、daily(セッションログ)、conversation(コンテキスト)、または custom(string)。 |
timestamp | RFC 3339 の作成時刻。時間範囲の再現境界は両端を含みます。 |
session-id | オプションの会話スコープ。 |
namespace | エージェントまたはコンテキスト間の分離境界。 |
score | 取得関連度 0.0〜1.0。非ベクトル再現の場合は none。 |
importance | 任意の優先順位付けの重み 0.0-1.0. |
superseded-by | このエントリを置き換えたエントリの ID(ある場合)。 |
agent-alias / agent-id | 表示名と生のストレージ識別子。スコープの等価性チェックには agent-id を、表示には agent-alias を使用します。 |
(key, agent-id) の複合キーは、最もよくある間違いです。ベースの get の契約では、次のように明示されています。複数の行が同じキーを共有する場合、任意の一致する行が返され、エージェントスコープのルックアップは get-for-agent を経由します。同様に、forget は帰属に関係なくキーに対応するすべての行を削除しますが、forget-for-agent は (key, agent-id) の行を正確に削除し、兄弟の行はそのまま残します。
必須エクスポート
12個の関数にはデフォルトがなく、動作する必要があります(memory.wit、required-methods セクション):
| エクスポート | 契約ノート |
|---|---|
name | バックエンド名。 |
get-memory-capabilities | オプションのメソッドのビットマスク。ロード時に一度だけ読み取る。 |
store-entry | (key, content, category, session-id) を保存します。store は wit-bindgen で予約されているため、store-entry という名前になっています。 |
recall | クエリ + 上限 + オプションのセッションおよび RFC 3339 の時間範囲(両端を含む)。空のクエリまたは単なる * のクエリは時間のみのリコールを意味し、最新のエントリを返します。 |
get | キー別; マルチエージェントのキー衝突時は任意の行。 |
list-entries | オプションのカテゴリとセッションのフィルター。wit 予約の list にちなんで命名されています。 |
forget | キーのすべての行を削除する。何かが削除された場合は true。 |
forget-for-agent | (key, agent-id) の行のみを削除します。 |
count | エントリの合計。 |
health-check | 到達可能性。 |
store-with-agent / recall-for-agents | 属性認識ペア。以下を参照してください。 |
recall-for-agents は agent-filter バリアントを受け取ります。all(エージェントフィルターなし)または some(list<string>)(リストされたエージェント ID に限定)のいずれかです。ランタイムは Rust の &[&str] スライスを、空スライスは all を意味するようにマッピングするため、some([]) はすべてではなく何にも一致しないものとして扱ってください。
機能フラグ: 11 個のオプションメソッド
チャネルと同じ仕組みです。ホストは get-memory-capabilities を一度だけ読み取り、設定されていない各フラグについては、あなたを呼び出す代わりに Rust トレイトのデフォルトを使用します。デフォルトは memory.wit 内のフラグの隣にインラインで記載されており、ホスト側のフォールバックは wasm_memory.rs で確認できます(各ゲート付きメソッドはフラグをチェックし、フラグがない場合はフォールバックパスを取ります)。
| フラグ | 未設定時のホストフォールバック |
|---|---|
get-for-agent | ホストは get + agent-id 等価フィルタを構成する |
purge-namespace, purge-session, purge-session-for-agent, purge-agent | ホストは “not supported” を返します |
reindex | ホストは 0 を返します |
store-procedural | ホスト no-ops |
ensure-agent-uuid | ホストはエイリアスを変更せずにエコーします |
recall-namespaced | ホストが recall を呼び出し、名前空間でポストフィルタする |
export-entries | ホストは list-entries を呼び出し、ポストフィルタリングします |
store-with-metadata | ホストは store-entry に委譲し、namespace と importance を破棄します |
最後の行をもう一度読んでください。store-with-metadata を実装しない場合、ランタイムが要求した名前空間と重要度はフォールバックによってサイレントに破棄されます。名前空間付きのデータを保存するバックエンドは、store-with-metadata、recall-namespaced、purge-namespace を一式として実装すべきです。そうしないと、名前空間の分離は静かにポストフィルタリングと不可逆な書き込みへと劣化します。
purge ファミリーはデータ削除のためのインターフェイスです。purge-agent は(ID ではなく)agent-alias を受け取ります。export-entries は GDPR 第 20 条のデータポータビリティに対応するために存在し、作成時刻の昇順でエントリを返し、埋め込みは除外する必要があります。バックエンドが実際のユーザーデータを扱う場合は、purge および export のフラグを実装してください。「サポートされていない」という回答が許容されるのは、使い捨てのバックエンドの場合のみです。
スケッチ: ストレージの形
コンポーネントのパターンはチャネルと同じ(ウォームインスタンス、thread_local 状態)なので、異なるのはデータレイヤーだけです。最小限で誠実なバックエンドは、正しくキー付けされたインメモリマップです。
#![allow(unused)]
fn main() {
use std::collections::HashMap;
struct Row {
id: String,
content: String,
category: Category,
timestamp: String,
session_id: Option<String>,
namespace: String,
importance: Option<f64>,
superseded_by: Option<String>,
agent_alias: Option<String>,
}
/// (key, agent_id) -> Row。agent_id の None は未帰属の行を表します。
type Table = HashMap<(String, Option<String>), Row>;
}
すべての必須メソッドは、その後、単純な走査になります:
store-entryは名前空間"default"で(key, None)に挿入します。store-with-agentは、呼び出し元の名前空間と重要度を使用して(key, agent-id)に挿入します。recallはcontentに対して部分文字列/ランクでフィルタリングし、セッションフィルタを適用し、timestampに対して RFC 3339 の境界値(両端を含む)を適用し、ソートしてlimitで切り詰めます。空のクエリまたは*クエリは、最新のものから順に処理してください。recall-for-agentsは、キーの2番目のコンポーネントに対するエージェントフィルターのウォークを追加します。
実際のバックエンドでは、コントラクトの形状を変更することなく、マップを組み込みストアに置き換えられます。メモリアダプタは、そのスコープが http_client を持つ場合でも、意図的にまだ wasi:http をリンクしていません。リモートバックエンドには、コンポーネントでテストされた別個のメモリとネットワークの境界が必要です。
ホストが周囲で行うこと
wasm_memory.rs におけるアダプターの動作を知ることで、いくつかのコントラクトのエッジが説明できます:
- ウォームストア、呼び出しごとに補給。 チャンネルと同じ: プラグインのライフタイムに1つのインスタンス、各呼び出しごとに新しい燃料、呼び出しは mutex の背後でシリアライズされます。バックエンドが同時呼び出しを見ることはありません。
- ロード時にキャッシュされるケイパビリティ。 ホストは
from_wasm中にフラグを一度だけ読み取り、その後は二度と読み取りません。動的なケイパビリティ検出はありません。デーモンを再起動することで再読み取りされます。 - トラップのラップ。 すべての呼び出しサイトは、名前付きコンテキスト(
memory.recall-namespaced trappedなど)でトラップをラップします。1回の呼び出しでのトラップはプラグインを破壊しませんが、繰り返しのトラップはバックエンドを無用にします。予期される失敗に対してはパニックする代わりにerr(string)を返してください。 - ポストフィルタのフォールバックはホスト側です。
recall-namespacedフラグが未設定の場合、名前空間フィルターはrecallが返した後にホスト上で実行されます。limitの処理がこれと相互作用します。ホストは呼び出し元の limit をrecallに渡すため、ポストフィルタリングによって結果が埋まりきらないことがあります。名前空間付きバリアントをネイティブに実装するもう一つの理由です。
マニフェスト、ビルド、インストール
マニフェストとは、プラグインディレクトリ内にある 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 に memory を含めます。まだ config_read を要求しないでください: 受け入れにはスキーマが必要ですが、現在のメモリワールドには、ホストが結果のオブジェクトを渡すためのエクスポートがありません。http_client にも依存しないでください: 権限付与だけではメモリアダプターの範囲を広げられず、現在はネットワークサーフェスを公開していません。
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 のデシリアライズチェックによって拒否され、サイレントに誤ロードされることはありません。
これらのコマンドには、プラグインホストをコンパイルして組み込んだバイナリが必要です。 インストーラーが配布するビルド済みリリースバイナリは
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 ファイルの欠落、または署名ポリシーによる拒否)。