docs/book/src/architecture/decisions/ADR-003-wasm-plugin-model.md
This is a restored retroactive record. The original ADR was added under
docs/architecture/decisions/adr-003-wasm-extism-plugin-model.md and
was dropped during the mdBook migration. It records the historical
Extism-based plugin bridge that was accepted on 2026-03-15. The current
WIT and direct wasmtime model supersedes it in
ADR-009.
ZeroClaw compiled many tools and channels into a single binary. Every user paid the compile time and binary size for capabilities they might never use. Third-party developers could not extend ZeroClaw without forking the repository and writing Rust code against internal APIs.
The Intentional Architecture RFC defined a microkernel target where non-core tools and channels become loadable plugins. That required a sandboxed execution model that:
The original evaluation considered three WASM runtime options:
| Runtime | Pros | Cons |
|---|---|---|
| Extism | High-level SDK, built-in host function system, PDKs for several guest languages, active maintenance | Adds binary size behind the feature flag |
| Raw wasmtime | Maximum control, mature runtime | Requires building the ABI, memory protocol, and host function system directly |
| Wasmer | LLVM and Cranelift backends | Smaller ecosystem and less Rust-native host-function ergonomics |
ZeroClaw would use Extism 1.x as the initial WASM plugin runtime behind
the plugins-wasm feature flag.
Plugins were WASM modules exporting two JSON functions:
tool_metadata(String) -> String, returning JSON with name,
description, and parameters_schema fields.execute(String) -> String, receiving tool arguments as JSON and
returning a JSON result with success, output, and optional error
fields.The runtime provided two permission-gated host functions:
zc_http_request(String) -> String, gated on
PluginPermission::HttpClient.zc_env_read(String) -> String, gated on
PluginPermission::EnvRead.Extism's built-in HTTP support was deliberately not used because it would bypass ZeroClaw's permission enforcement.
Each plugin shipped a manifest.toml alongside its .wasm file. The
manifest declared name, version, capabilities such as tool, channel,
memory, and observer, and required permissions such as
http_client, env_read, file_read, file_write, memory_read, and
memory_write.
Plugin manifests supported optional Ed25519 signatures with three
enforcement modes: disabled, permissive, and strict.
Plugin authors depended on extism-pdk and compiled to
wasm32-wasip1. The protocol used documented JSON contracts rather than
a ZeroClaw-specific guest SDK crate.
Positive:
wasm32-wasip1 targets could produce plugins.plugins-wasm did not pay the binary-size or
compile-time cost.Negative:
extism-pdk, an external SDK.Tool trait was async,
so calls used blocking-task bridging.Known gaps:
zc_http_request could forward plugin-supplied URLs without the same
private-IP, loopback, and link-local restrictions used by native HTTP
tools.env_read granted access to any variable by name instead of a
per-plugin allowlist.Those gaps meant the permission model was initially a documented contract, not yet a hardened boundary for plugins from untrusted authors.
docs/architecture/decisions/adr-003-wasm-extism-plugin-model.mdcrates/zeroclaw-plugins/src/runtime.rs,
crates/zeroclaw-plugins/src/wasm_tool.rs,
crates/zeroclaw-plugins/src/host.rs, and
crates/zeroclaw-plugins/src/signature.rs