Write your own plugin

The manifest, the sandbox contract, and testing.

Written By Dustin

Last updated 27 minutes ago

A plugin is a WASM module plus a manifest. The manifest declares an id, a name, a version, a publisher, an API version, a type, a runtime, the events it can run on, the capabilities it requests, its input and output schema versions, a default timeout, and its integrity metadata. The plugin specification in the TOVIO docs is the field-by-field reference.

The sandbox contract

Your guest is a wasm32-wasi module that reads its input on stdin and writes its result on stdout. That is the whole surface.

  • Deny-by-default: no network, no filesystem, no environment unless granted. Importing a host function you were not granted is a link error and fails closed with TVO-PLUGIN-006.
  • A fuel budget, a wall-clock deadline, and a memory ceiling bound every run. Overrunning is TVO-PLUGIN-007.
  • A fresh instance per execution. You cannot carry state between runs, and you should not try.
  • Key material is never provided, under any grant.

Request the minimum

Capabilities are intersected with the binding, so over-requesting does not gain access — it just gets your plugin refused by cautious operators, and quite reasonably so. Ask for what you need.

Output must validate

A result that fails schema validation is coerced to error, not ignored, and so is a result whose plugin_id or event does not match the execution it came from. Emit the declared shape, name yourself honestly, and use the status vocabulary as intended: pass, warning, fail, error, skipped, unsupported. skipped and unsupported are ordinary answers, not failures.

Propose, do not apply

A transform proposes actions; it does not silently modify tracked state. A resolver proposes a resolution, which TOVIO applies through the same path a human choice takes. A plugin that wants a path protected emits a protect-path proposed action — and the host applies it only after a human confirms, failing closed in a non-interactive context.

Test it

tovio plugin test <path> --event <event>tovio plugin run <id> --event <event> --dry-run

Both require --event. They are different harnesses on purpose: test resolves a package from a path without installing or binding it and runs it under its manifest's own capabilities, and never gates. run executes an installed plugin under its binding, so mode, required, and the granted intersection all apply; without a binding for that event it refuses. Add --dry-run to see what would block without gating, and --json for machine-readable status and findings.

Both need a binary built with the plugins-wasm feature.

Manifest problems

TVO-PLUGIN-001 covers a bad id, version, or type, an event the plugin cannot run, a self-contradictory capability request, and a manifest that is missing, unreadable, or not valid TOML.

Signing

Sign your artifact with an Ed25519 key, publish the identity as a did:key:, and operators can add it to their trust list. The signature covers the canonical manifest and the artifact hash together, so nobody can swap one without invalidating it. An unsigned package can still be installed locally, but it will be reported as a development install rather than as publisher-verified, and it cannot be installed over HTTPS or registered on a hosted Forge.