Build the plugin walking skeleton: owned WASM host, packet ABI, digest-pinned catalog #26

Closed AtlantisPleb opened this 3d ago 3 comments

Outcome

The CLI can load and run a WASM plugin under the owned contract: handle_packet(bytes) -> bytes | typed refusal, digest verification before load, timeout/memory ceilings, typed refusals, and a receipt per invocation recorded as a tool.ran thread event. One pure-function plugin proves the path end to end.

Constraints

  • Owned, reduced-surface host — not a dependency on Extism (no longer a runtime to build on; adapt its BSD-3-Clause source where useful, with attribution). Host imports total: log, allowlisted HTTP, KV, read-only path mounts.
  • One guest path: an owned Rust PDK.
  • Engine abstraction so no WASM engine is load-bearing forever.

Source

OpenAgentsInc/openagents.com docs 2026-08-24-triage-and-plugin-model-assessment.md sections 4.2–4.3 and 2026-08-24-registry-network-strategy.md section 8. The pilot that follows is foreign session resume (OpenAgentsInc/openagents.com#198).

  1. AtlantisPleb opened this issue 3d ago
  2. A AtlantisPleb Author 3d ago

    The working demo landed on main as 16d3fa5826 (owner ask: see a plugin load in the coder chat sooner than later). What ships:

    • plugins/word-stats: a dependency-free Rust guest compiled to wasm32-unknown-unknown (52,611 bytes, artifact and digest checked in), speaking the packet ABI: host writes a JSON packet into guest memory via packet_alloc, calls handle_packet(ptr, len), reads back a packed (ptr,len) u64.
    • packages/openagents-cli/src/coder-plugins.ts: the host — sha256 digest verified before compile, imports must be empty (pure compute), declared exports required, non-empty mount/host capability declarations refused, one worker_threads worker per invocation terminated at the manifest timeout, refusals as typed values both directions (never throws).
    • /plugin load in both coder UIs registers a session-scoped tool from the manifest; a live thread model called word_stats and answered from its JSON. scripts/plugin-demo.mjs walks load -> verify -> invoke -> guest refusal -> tampered digest refusal -> runaway timeout. 9 new tests; typecheck clean (including a pre-existing coder-ui.test.ts fix).
    • Shape memo: docs/plugins/2026-08-24-coder-plugin-demo-shape.md.

    Demo: printf '/plugin load ../../plugins/word-stats/manifest.json\nUse the word_stats tool to analyze: the quick brown fox jumps over the lazy dog\n' | node dist/main.js coder --plain

    Remaining for this issue per the memo: the owned Rust PDK (serde + refusal enum + macro hiding pointer plumbing), the engine abstraction (worker-per-call stays the Node fallback engine; fuel and memory ceilings open), both-sides schema validation, the tool.ran receipt event (pairs with #23), WASI read-only mounts for the OpenAgentsInc/openagents.com#198 pilot, and packet-ABI versioning (abi.kind packet-v0).

  3. A AtlantisPleb Author 3d ago

    The walking skeleton landed on main as 958d114884 (pushed as bbbc962f01, WAL receipt seq 73). What ships beyond the demo: the owned Rust PDK (plugins/pdk, crate openagents-pdk — plugin_entry! macro generates the packet ABI, serde types in and out, closed Refusal enum, no unsafe in author code; word-stats ported at half the lines and byte-reproducible builds documented); the engine seam (coder-plugin-engine.ts — PluginEngine interface with the worker-per-call Node engine behind it, AbortSignal cancellation added, wasmtime slot documented); enforced read-only mounts (capabilities.mounts grants exactly openagents.read_file with realpath containment, symlink refusal, 1 MiB bound, and a file-stats proof plugin — the #198 prerequisite); and abi versioning (packet-v0 pinned, abi_unsupported refusal). 490/490 package tests plus 8 PDK tests and 12 new mount tests. Remaining on this issue: both-sides schema validation, the tool.ran receipt event on plugin runs, /plugin list and unload, and memory-ceiling enforcement (declared-only until a wasmtime engine).

  4. A AtlantisPleb Author 3d ago

    The walking skeleton is shipped and proven end to end. packages/openagents-cli/src/coder-plugins.ts and coder-plugin-engine.ts carry the owned contract: manifest-first identity, artifact digest verified against the pin before the module compiles (a mismatch is a refusal), the packet-v0 ABI (handle_packet/packet_alloc), per-call timeout and memory ceilings, read-only confined mounts with anything else refused, and typed refusals in both directions — host {code, reason}, guest {"refusal": {...}} inside the output packet. The engine sits behind a seam so no WASM engine is load-bearing forever, and invocations are receipted as tool.ran thread events.

    No Extism dependency: the host is owned and reduced-surface, with one guest path through the owned Rust PDK (plugins/pdk).

    Proven by more than a pure-function pilot — plugins/dir-stats, file-stats, word-stats, foreign-sessions, and now git-lost-work (#37) all run on it, the last reading a mounted .git tree through the read-only mount capability.

  5. closed this as completed 3d ago
Sign in with GitHub to comment on this issue.