Status: accepted — records what Milestone 6 and 8 built; amends ADR-0004 WASM components for extensions, which stays the target.
Context
ADR-0004 WASM components for extensions chose WASM components with a WIT world generated from ext-api. When the runtime was built (Milestone 6) the component toolchain was the risky part: cargo component, a browser host for components (wasm_component_layer/jco), and a WIT world that would have frozen an ext-api still changing every milestone. A runtime was needed to prove permissions and agent tools, not to freeze the API.
Decision
Version 1 of the wasm extension interface is a JSON ABI over core wasm modules (moonkale-ext-host, ABI_VERSION = 1):
- the module exports
alloc,manifestandrun; it importsmoonkale.logandmoonkale.call; - every value crossing the boundary is JSON:
WasmManifest { abi, id, name, description, permissions, commands },RunRequest/RunReply,HostCall/HostReply; - host calls:
list_sources,query,fetch_text, each checked against the granted permissions; - a module contributes commands only (which may be agent tools); no panels, no
ui::Tree; - runtimes: wasmtime on desktop and the server; a Worker with a
SharedArrayBuffermailbox in the browser (packages/js/wasm-host); none on Android yet.
Built with plain cargo build --target wasm32-unknown-unknown — no component tooling. Example: packages/extensions/wordcount.
Consequences
- Third-party extensions are possible today, but only as commands/tools; anything with UI is a static crate compiled into the binary.
- The manifest is returned by the module at runtime; there is no
moonkale.tomlfile yet (Manifest Reference describes the target). - Permissions are only as strong as where they are checked: on the server path they come from the client (issue #4, Audit 2026-09-23); there are no fuel or memory limits.
- Moving to components later changes the packaging of a module, not what it may do: the host calls and permission names are the parts to keep.