Skip to main content
A plugin is a WebAssembly component you upload. It runs in a Wasmtime sandbox under WASI Preview 2, with no ambient authority: no filesystem it was not handed, no network beyond an explicit allowlist, and hard caps on memory and CPU. Write a plugin when the logic is yours and you want it to stay that way — proprietary scoring, a deterministic calculation, something touching values you would rather not hand to a model.

The sandbox

A plugin gets exactly what it is granted, and nothing else. Only nine WASI import prefixes are permitted. Every import in the component must start with one of them:
Of wasi:keyvalue, only store is implemented. A component importing atomics, batch, or watch will fail to instantiate. The filesystem is not a real one — it is backed by object storage. A plugin sees either a session scope (input, read-only; output, read-write) or, on an agent surface only, an agent-scoped home directory. MCP and workflow surfaces never receive a home mount. Outbound request bodies pass through a leak-detection scan on their way out.

The .npack format

A .npack is a gzip-compressed tar archive. Format validation checks the gzip magic bytes. Entries are recognized by pattern, not by fixed filename: Directories are skipped; anything else is ignored with a log line.
There is no manifest.json and no schema.json. Tool schemas are not declared in a file — they are derived from the component’s exported functions by reflecting over its component type. The WIT file, if present, only improves the descriptions.
The plugin name comes from noorle.yaml’s metadata name, falling back to the archive filename, falling back to the WASM file stem.

noorle.yaml

schema_version must be exactly "1.0"; anything else is rejected.
runtime is validated and stored but nothing dispatches on it. Every plugin runs on WASI Preview 2 under Wasmtime regardless of the value. Setting runtime: edge or runtime: native does not select a different engine.

Network permissions

Network access is an allowlist of hosts. A host entry may carry a wildcard (*.example.com), an optional path prefix, and an optional list of permitted methods. Omitting either of the latter means unrestricted on that dimension. An empty allowlist means no network access at all. And if noorle.yaml has no permissions block, the plugin runs under the default sandbox — every permission field unset, which is to say no external access whatsoever. CIDR permissions exist as a type but are rejected at validation with a message pointing you at host-based permissions instead.

Credentials

Declare what secrets a plugin needs and where they go. Names must match ^[a-z0-9_]+$ and be unique. Seven injection modes: bearer token, basic auth, a named header, a URL query parameter, a URL path placeholder, a JSON body field, and a form body field. Secrets are decrypted at the host boundary and injected into the outbound request there. Ciphertext never reaches guest memory, and neither does the plaintext secret as a readable value. A missing required credential fails the call before the WASM runs.

Admission validation

Every upload is checked before it can be activated:
  1. It must be a valid Component Model binary. A core WebAssembly module is rejected.
  2. Every import must match one of the nine allowed WASI prefixes.
  3. It must export at least one callable function.
  4. Above 20 MB, a warning is logged. This is advisory, not a rejection.
The archive is also guarded against decompression bombs: expansion is capped at 256 MB total across all entries, enforced incrementally rather than trusting tar headers.

Limits

Every execution runs under a platform minimum, default, and maximum: What can move a limit off its default differs by limit, so it is worth being precise: Those two account settings are the only per-account WASM overrides. The platform converts fuel to CPU time at roughly two million units per millisecond, so the 200-million default is on the order of 100 ms of CPU. That ratio is an empirical approximation the platform uses for its own accounting, not a guarantee — size a plugin with headroom rather than against the boundary. Two independent stop mechanisms run at once: a fuel budget and an epoch deadline, plus an outer wall clock. Fuel exhaustion is reported as a timeout. Outbound HTTP requests are capped at 100 per execution. This is a platform default and is not settable from noorle.yaml — a max_http_requests in your resource limits parses but is ignored.

Upload size

Activation

A plugin version is registered dormant. The version row exists and the bytes are stored, but it is not serving until it is activated — so a partially-completed registration cannot start answering calls. The WASM binary itself is content-addressed by its SHA-256. Identical bytes uploaded twice are stored once. On load, a BLAKE3 integrity check runs against the recorded hash; a mismatch is a hard failure. If the final activation step fails, the new version stays dormant and the previously active version keeps serving.

Performance

Compiled components are cached and pre-instantiated per content hash, so identical WASM shared across several capabilities compiles once. A second, byte-weighted cache holds plugin bytes per pod so a warm plugin skips the object-store fetch. The practical consequence: the first call to a newly uploaded plugin pays compilation, and subsequent calls do not.

Languages

Five toolchains are supported, each with a starter template: Rust, Python, Go, JavaScript, and TypeScript. Any language that compiles to a WASI Preview 2 component works. The requirement is the Component Model, not just a core .wasm module — for Rust that means building for the wasm32-wasip2 target. A core WebAssembly module is rejected at validation. For per-language toolchain setup, see the Build tab language guides.

Practical guidance

Declare the narrowest allowlist

No permissions block means no network at all. That is usually the right starting point — add hosts only when a call actually needs one.

Let the host hold your secrets

Declare credentials and an injection mode instead of reading a secret inside the guest. The plaintext then never enters WASM memory.

Keep plugins deterministic

Same input, same output. Retries and durable replay both assume it.

Every plugin tool is Act tier

The platform cannot see inside your component, so it classifies conservatively. Under Supervised autonomy your plugin’s calls pause until auto-approved.

Next: Authentication.