Skill: inseam-loaded-plugin
Author, build, and validate a loaded (WASM component) inseam plugin against the transform seam contract. Use when creating or modifying a community/AI-authored plugin under plugins/.
Authoring a loaded inseam plugin
Section titled “Authoring a loaded inseam plugin”You are writing a loaded transform plugin: a WASM component that the
inseam kernel mounts through the plugin-host bridge. It registers into the
same transforms seam linked transforms use — tier is provenance, not
shape. Read design/plugins.md and design/kernel.md if you need the
architecture; this skill is the mechanical contract.
Loaded plugins are the preferred method for AI-written plugins. The
sandbox — manifest-attenuated capabilities, fuel limits, per-call
instantiation, admission checks — is what makes code written minutes ago
safe to mount, so default here rather than to linked plugins or core
edits; reach for those only when the transform seam genuinely can’t
express the change (docs/plugins/linked.md).
The inseam CLI is self-documenting (inseam --help,
inseam plugin check --help) — use it for flags and commands. This skill
covers only what the CLI can’t tell you: the authoring contract,
conventions, and pitfalls. The contract itself is a language-neutral WIT
world: this skill’s procedure targets Rust, but a component built with
componentize-py, ComponentizeJS/jco, or TinyGo satisfies the same
contract and the same inseam plugin check — the contract rules below
apply regardless of language.
The contract (source of truth)
Section titled “The contract (source of truth)”- WIT world:
crates/inseam-wasm-host/wit/transform.wit— READ IT FIRST. Your component exports thetransforminterface (claims,apply) and may import thehostinterface (log, llm-complete, llm-describe-image, source-bytes). - Bridge behavior:
crates/inseam-wasm-host/src/lib.rs(doc comments at the top). Key rules you must design around:- Capabilities are manifest-gated. Calling a host function your
manifest didn’t request returns
Err— degrade gracefully (emit nothing), never panic. - Effective claims = manifest claims ∩ exported claims. Keep the two lists consistent or your plugin will never run.
applymust be infallible in spirit: returnOk(empty output)when you cannot do useful work (capability withheld, unreadable input). AnErris logged and treated as empty — it never gates the source.- Fragments are a flattened tree:
parentmust index an EARLIER fragment in your output list;Nonehangs the fragment off the claimed fragment. Never emittext/x-inseam-*mimetypes (the bridge drops them). - Relations:
contains,derived-from,transcribes,links-to,mentions. A transcript-like output of media usestranscribes. - Per-call instantiation: no state survives between applications. Don’t cache; don’t count; the host meters your LLM budget mechanically.
- Capabilities are manifest-gated. Calling a host function your
manifest didn’t request returns
Project layout
Section titled “Project layout”Create the plugin as a standalone crate (NOT a member of the root
workspace) at plugins/<name>/:
plugins/<name>/ Cargo.toml src/lib.rs <name>.manifest.toml # reviewed by owners; enforced by the bridge <name>.checks.toml # your golden checks — WRITE THESE FIRST (below) fixtures/ # byte fixtures the checks reference README.md # REQUIRED: what each fixture is and why README.md # one paragraph: what it does, what it needsCargo.toml template:
[package]name = "<name>"version = "0.1.0"edition = "2021"
# Standalone: the root workspace must not adopt this crate.[workspace]
[lib]crate-type = ["cdylib"]
[dependencies]wit-bindgen = "0.60"
[profile.release]opt-level = "s"lto = truestrip = truesrc/lib.rs skeleton:
wit_bindgen::generate!({ path: "../../crates/inseam-wasm-host/wit", world: "transform-plugin",});
use exports::inseam::plugin::transform::{ClaimSpec, Envelope, Fragment, Guest, Output};use inseam::plugin::host;
struct Plugin;
impl Guest for Plugin { fn claims() -> ClaimSpec { ClaimSpec { mimetypes: vec!["image/png".into(), "image/jpeg".into()], roots_only: true, } }
fn apply( _env: Envelope, mimetype: String, _is_root: bool, _text: Option<String>, ) -> Result<Output, String> { // ... use host::source_bytes(), host::llm_describe_image(...), // host::log(...) as granted; on Err from a host call, return // Ok(Output { fragments: vec![] }) — degrade, don't gate. let _ = mimetype; Ok(Output { fragments: vec![] }) }}
export!(Plugin);<name>.manifest.toml schema (must sit next to the built .wasm):
name = "<name>"version = "0.1.0"seam = "transform"claims = ["image/png", "image/jpeg"] # or ["image/*"]; must overlap claims()roots_only = truekind = "enrichment" # or "structural"
[capabilities]llm = true # request ONLY what you usesource_bytes = truellm_call_budget = 25 # per index runRequest the minimum capabilities: every extra grant is attack surface an owner has to approve, and capability widening between versions triggers an explicit approval gate.
Golden checks — write these FIRST (<name>.checks.toml)
Section titled “Golden checks — write these FIRST (<name>.checks.toml)”Before writing apply, declare what the plugin promises: example inputs
and the output shapes they must produce. These are your enforced tests —
run by inseam plugin check during authoring, by registry CI at publish,
and by every installing node at admission. They are data, not code
(docs/plugins/validation.md has the full schema):
[[check]]name = "does the thing on the happy path"mimetype = "image/png" # what the application arrives asbytes_file = "fixtures/sample.png" # handed to source-bytes (relative path)llm_returns = "CANNED REPLY" # what the granted LLM returns verbatim
[check.expect]fragment_contains = "CANNED" # the reply must land in a fragmentrelation = "transcribes"mimetype = "text/plain"
[[check]]name = "emits nothing when the llm is withheld" # ALWAYS include a degrade checkmimetype = "image/png"bytes_file = "fixtures/sample.png"# no llm_returns => the llm refuses
[check.expect]min_fragments = 0max_fragments = 0Keep fixtures tiny and well-formed (they are downloaded by every install),
and document every fixture in fixtures/README.md — what it is, why it
exists. The LLM is always canned during checks, so fixtures prove
plumbing and shape, never model quality.
cd plugins/<name>cargo build --release --target wasm32-wasip2cp target/wasm32-wasip2/release/<name>.wasm ./<name>.wasm(wasm32-wasip2 produces a component directly; the target is installed via
rustup target add wasm32-wasip2 if missing. If the crate name has hyphens
the artifact uses underscores — rename the copy to match the manifest.)
Validate (do not skip)
Section titled “Validate (do not skip)”cargo build --release --target wasm32-wasip2— must be warning-free.- From the repo root:
This is the conformance harness — the exact gate registry CI and every node’s install-time admission run: static manifest checks, a real bridge mount, a hostile-input contract battery, then your golden checks. It must end with
Terminal window inseam plugin check plugins/<name>/<name>.wasm# (or, without the installed binary:)cargo run -p inseam-wasm-host --example inspect -- plugins/<name>/<name>.wasmPASS, and the effective-claims line must match your intent — an empty list means manifest andclaims()disagree. - If any phase fails, fix and repeat. Do not hand off a plugin whose check run fails — admission on the user’s node runs the same harness and will refuse the mount.
Reading check failures:
- The first invocation cold-compiles wasmtime and can take a few minutes — that is a build, not a hang.
contractfailures mean a code path traps (panics) instead of degrading; hunt theunwrap/expect/indexing in that path.- A failure like
component imports instance wasi:...(an unsatisfiedwasi:*import) is a host-linker gap in the bridge, not a plugin authoring error; report it againstinseam-wasm-hostinstead of reworking the plugin.
Mounting (how users will run it)
Section titled “Mounting (how users will run it)”Users add an entry to their node’s composition.toml:
[[entry]]id = "<name>"plugin = "wasm:plugins/<name>/<name>.wasm"[entry.config]# cooldown_days = 7 # release cooldown for newly observed versions# allow_new = true # explicit consent to skip the cooldownDocument that snippet in the plugin’s README.
- Keep the component dependency-light: every dependency compiles into the artifact and into the audit burden.
- Prompts sent through
llm-complete/llm-describe-imageshould say exactly what to return and forbid preamble — the output lands verbatim in a search index. - Bound your output: cap fragment counts and text sizes yourself; the host prunes, but a tight plugin doesn’t rely on it.