WASM plugins
The feature-gated WASM plugin system: manifest, capabilities, permissions, and signature verification.
Revka can load WebAssembly plugins to extend the runtime with custom tools, channels, memory backends, or observer hooks. Each plugin is a directory containing a manifest.toml and a .wasm module. The host discovers plugins on the local disk, optionally verifies an Ed25519 signature on the manifest, and exposes the loaded set over the CLI and the gateway API.
The whole subsystem is compile-time gated behind the plugins-wasm Cargo feature. A standard Revka build does not include it: the revka plugin CLI subcommand and the GET /api/plugins endpoint only exist in a binary built with that feature. Reach for this page when you are building a custom WASM extension, packaging one for distribution, or hardening which plugins your operator will load.
Enable the feature
Section titled “Enable the feature”The plugin system has two gates, and both must be satisfied:
-
Build gate — Revka must be compiled with the feature:
Terminal window cargo build --release --features plugins-wasmplugins-wasmis part of thefullfeature set, so a--features fullbuild also includes it. -
Config gate — turn the system on in
config.toml:[plugins]enabled = true
If the feature is compiled out, the CLI subcommand and API route are absent entirely. If the feature is present but [plugins] enabled = false, the API responds with plugins_enabled: false and an empty plugin list, and no plugin tools are registered.
See Cargo feature flags & ADRs for the full feature matrix and Configuration overview for how config.toml is loaded.
Configuration
Section titled “Configuration”All plugin settings live under [plugins], with signature policy nested in [plugins.security].
[plugins]enabled = true # master switch (default: false)plugins_dir = "~/.revka/plugins" # where plugins live (default shown)auto_discover = false # auto-discover on startup (default: false)max_plugins = 50 # max number of plugins (default: 50)
[plugins.security]signature_mode = "disabled" # "disabled" | "permissive" | "strict" (default: "disabled")trusted_publisher_keys = [] # hex-encoded Ed25519 publisher public keys| Key | Type | Default | Meaning |
|---|---|---|---|
plugins.enabled | bool | false | Master switch for the plugin system. |
plugins.plugins_dir | string | ~/.revka/plugins | Directory that holds plugin sub-directories. A leading ~/ is expanded to your home directory. |
plugins.auto_discover | bool | false | Auto-discover and load plugins on startup. |
plugins.max_plugins | usize | 50 | Maximum number of plugins that may be loaded. |
plugins.security.signature_mode | string | "disabled" | How unsigned/unverified plugins are handled. See Signature modes. |
plugins.security.trusted_publisher_keys | string[] | [] | Hex-encoded Ed25519 public keys of publishers you trust. |
Plugin layout
Section titled “Plugin layout”Each plugin is its own directory under plugins_dir. The host scans that directory and treats any sub-directory containing a manifest.toml as a plugin candidate:
~/.revka/plugins/└── my-plugin/ ├── manifest.toml # required └── plugin.wasm # the compiled module (path from manifest.wasm_path)A plugin’s directory name on disk does not have to match its manifest name, but when you install via the CLI the host copies the plugin into a directory named after manifest.name.
The manifest (manifest.toml)
Section titled “The manifest (manifest.toml)”The manifest is TOML that deserializes into the host’s PluginManifest. name, version, wasm_path, and capabilities are required; the rest are optional.
name = "my-plugin"version = "0.1.0"description = "What this plugin does"author = "Acme Corp"wasm_path = "plugin.wasm" # path to the .wasm, relative to this manifestcapabilities = ["tool"] # one or more of: tool, channel, memory, observerpermissions = ["http_client", "file_read"]
# Optional — only present on signed plugins:signature = "<base64url Ed25519 signature>"publisher_key = "<hex Ed25519 public key>"| Field | Type | Required | Meaning |
|---|---|---|---|
name | string | yes | Unique plugin identifier. |
version | string | yes | Plugin version string. |
description | string | no | Human-readable description. |
author | string | no | Author name or organization. |
wasm_path | string | yes | Path to the .wasm file, relative to the manifest. |
capabilities | string[] | yes | What the plugin provides (see below). |
permissions | string[] | no | Host capabilities the plugin requests (see below). Defaults to empty. |
signature | string | no | Base64url-encoded Ed25519 signature over the canonical manifest. |
publisher_key | string | no | Hex-encoded Ed25519 public key of the signer. |
Capabilities
Section titled “Capabilities”capabilities declares what kind of extension a plugin is. The values are serialized as snake_case:
| Capability | Meaning |
|---|---|
tool | Provides one or more callable tools. |
channel | Provides a messaging channel implementation. |
memory | Provides a memory backend. |
observer | Provides an observer / metrics backend. |
A plugin may declare more than one capability. Today the host only wires tool-capable plugins into the agent’s tool registry — see Plugin tools.
Permissions
Section titled “Permissions”permissions is the set of host capabilities a plugin requests. They are advisory metadata in the manifest (the host records and reports them); the values are serialized as snake_case:
| Permission | Grants |
|---|---|
http_client | Make HTTP requests. |
file_read | Read from the filesystem (within the sandbox). |
file_write | Write to the filesystem (within the sandbox). |
env_read | Read environment variables. |
memory_read | Read agent memory. |
memory_write | Write agent memory. |
Plugin tools
Section titled “Plugin tools”When [plugins] enabled = true, the runtime constructs a plugin host over plugins_dir and registers every tool-capable plugin as a tool in the agent’s registry. The tool name is the plugin’s manifest.name, and the description comes from the manifest description.
Each registered plugin tool currently advertises a single fixed parameter schema:
{ "type": "object", "properties": { "input": { "type": "string", "description": "Input for the plugin" } }, "required": ["input"]}Signature verification
Section titled “Signature verification”Plugin manifests can be signed with Ed25519. The signature covers the canonical manifest — the TOML content with the signature and publisher_key lines stripped — so a plugin can ship its own signature inside the same file it signs. Editing any other field invalidates the signature.
signature_mode controls how the host treats unsigned or unverifiable plugins:
| Mode | Behavior |
|---|---|
disabled | Default. No signature checking. Every discovered plugin loads. |
permissive | Verify when possible. Unsigned, untrusted, or invalid plugins log a warning but still load. |
strict | Reject unsigned plugins, plugins signed by a key not in trusted_publisher_keys, and plugins whose signature fails to verify. |
Verification has two parts. First the manifest’s publisher_key must appear in trusted_publisher_keys (case-insensitive hex match) — otherwise the plugin is untrusted. Then the Ed25519 signature is checked against the canonical manifest bytes. In strict mode an untrusted publisher, an invalid signature, or a missing signature all cause the plugin to be skipped during discovery.
To run a locked-down host, sign your plugins and pin their keys:
[plugins.security]signature_mode = "strict"trusted_publisher_keys = [ "a1b2c3d4e5f6...your-publisher-public-key-hex...",]For where this fits in Revka’s broader trust posture, see the Security model.
Manage plugins from the CLI
Section titled “Manage plugins from the CLI”With a plugins-wasm build, the revka plugin subcommand manages the plugin directory:
revka plugin listPrints each installed plugin as name vX.Y.Z — description, or No plugins installed.
revka plugin install /path/to/plugin-dirReads manifest.toml from the source (a directory or a manifest path), enforces the configured signature policy, then copies the manifest and .wasm into plugins_dir under a directory named after manifest.name. Fails if the manifest or .wasm is missing, or if a plugin of the same name is already loaded.
revka plugin info my-pluginPrints the plugin’s name, version, description, capabilities, permissions, and resolved .wasm path.
revka plugin remove my-pluginRemoves the plugin from the host and deletes its directory under plugins_dir.
See the CLI overview for global flags and environment setup.
List plugins over the API
Section titled “List plugins over the API”The gateway exposes the loaded plugin set so the dashboard can show plugin status. The route only exists in a plugins-wasm build.
GET /api/plugins — Bearer token required.
GET /api/pluginsAuthorization: Bearer rk_<token>Response:
{ "plugins_enabled": true, "plugins_dir": "~/.revka/plugins", "plugins": [ { "name": "my-plugin", "version": "0.1.0", "description": "What this plugin does", "capabilities": ["tool"], "loaded": true } ]}| Field | Meaning |
|---|---|
plugins_enabled | Mirrors [plugins] enabled. When false, plugins is always empty. |
plugins_dir | The configured plugin directory (unexpanded, e.g. ~/.revka/plugins). |
plugins[].name / version / description | From the manifest. |
plugins[].capabilities | The manifest capability list. |
plugins[].loaded | true when the resolved .wasm file exists on disk. |
If the feature is enabled but the plugin directory does not exist, plugins is an empty array. The endpoint requires a paired bearer token — see Pairing & authentication to obtain one.