WASI 0.3 makes asynchronous functions, streams, and futures native to WebAssembly Components, which removes the need for each component to emulate async with pollables. A safe agent-tool runner still needs a pinned runtime, an allowlisted WIT world, no capabilities by default, host-enforced memory and compute limits, bounded streams, idempotent side effects, and cancellation that reaches host imports.
Pin the July 2026 support state
The WASI Subgroup ratified WASI 0.3.0 on June 11, 2026. The official WASI 0.3 announcement says the specification is stable and toolchain support is landing. The 0.3.0 release notes map the previous wasi:io pollable and start/finish patterns to native future<T>, stream<T>, and async func primitives.
Runtime support is not universal. Wasmtime 46.0.0, released June 22, enabled WASI 0.3.0 and Component Model async by default. Pin at least the reviewed 46.0.1 patch in this article’s time frame: Wasmtime 46.0.1 fixed a WASI hard-link/rename permission-check issue. Recheck security advisories and supported patches before deployment.
Guest-language and JavaScript tooling remains uneven. The Component Model’s language-support page is a living document. Prove your exact compiler, bindings generator, adapters, runtime, and WIT packages together; “supports WebAssembly” does not imply WASI 0.3 support.
Start with a small async world
Define the domain operation rather than granting a shell:
package example:async-agent-tool@0.3.0;
interface runner {
record run-request {
operation-id: string,
input: list<u8>,
max-output-bytes: u64,
}
record run-result {
output: list<u8>,
media-type: string,
}
variant run-error {
invalid-input(string),
denied(string),
deadline,
output-limit,
cancelled,
failed(string),
}
run: async func(request: run-request) -> result<run-result, run-error>;
}
world sandboxed-tool {
export runner;
}
Native async func allows the host runtime to schedule the task without the WASI 0.2 start-*, finish-*, and subscribe dance. For large or incremental output, design a stream<T> interface and a final future<result<...>>; review direction and ownership against the 0.3 spec instead of modeling a stream as repeated unbounded strings.
The WIT request’s max-output-bytes is advisory until the host enforces it. The guest cannot be trusted to police its own limits.
Let the host own one event loop
WASI 0.3 moves scheduling to the runtime. The announcement explains that futures and streams are owned handles transferred across component boundaries and that the async model is completion-based. This permits composed components to await one another without separate, uncoordinated guest event loops.
Your runner should map each root tool invocation to a host task with:
- authenticated tenant and actor context outside guest memory;
- immutable component digest and world version;
- a cancellation token;
- wall-clock deadline;
- compute and host-call budgets;
- bounded input/output channels;
- audit and trace correlation;
- an idempotency record for effects.
Do not let the model choose the component digest, capability set, deadline, or budget directly. It may request a registered tool name; trusted policy resolves that name to an approved execution plan.
Grant imports as capabilities
Begin with no WASI filesystem, network, environment, clock, or random imports. Add the narrowest interface required:
| Tool need | Safer grant |
|---|---|
| Read one artifact | opaque host artifact.read(id) import |
| Write scratch data | isolated size-limited temporary directory |
| Call one API | typed host operation with fixed destination |
| Sign content | opaque sign(payload) import, not key bytes |
| Emit progress | bounded structured stream |
| Use time | monotonic deadline or coarse clock only |
WASI’s capability approach means filesystem access is limited to granted preopens rather than the host’s ambient namespace. Wasmtime’s WASI capability documentation explains the preopened-directory model. Still run the host process with least OS privilege because the runtime and host imports are part of the trusted computing base.
Network access deserves special treatment. Prefer domain imports over sockets. If raw network is unavoidable, enforce address and DNS policy, redirect checks, private-range blocking, response size, method, port, rate, and deadline at the host boundary. A prompt-based allowlist is not enforcement.
Enforce three independent limits
Compute
Use runtime fuel for deterministic guest-instruction budgeting where appropriate, and epoch or host deadlines for wall-clock interruption. Wasmtime’s interruption guide distinguishes deterministic fuel from epoch interruption. Host calls also consume budget; a guest that makes cheap instructions but expensive network calls can still exhaust the system.
Memory and object count
Limit linear memory, tables, instances, and per-tenant concurrent stores. Wasmtime’s Store documentation for 46.0.1 warns that a Store is intended to be short-lived and does not release instances until the Store drops. Do not accumulate an unbounded number of components in one long-lived Store.
Bytes and time in flight
Cap input, output, logs, error strings, temporary storage, open resources, stream buffering, and total duration. Backpressure must stop the producer rather than accumulating chunks in host memory. Cancel downstream imports when the consumer disconnects, but persist any already committed side effect.
Apply admission control before allocating a runtime Store. Our agent backpressure guide covers per-resource bulkheads and deadlines.
Define cancellation and side-effect semantics
Cancellation can occur while guest code computes, awaits a host import, writes a stream, or waits after an external service committed. Model these states explicitly:
accepted -> running -> dispatching-effect -> committed
\-> cancelled-before-effect
\-> outcome-unknown -> reconciling
Fuel or a trap stops guest execution; it does not roll back an HTTP request or filesystem write already accepted by the host. Require an idempotency key for every effectful host import. On timeout after dispatch, query the destination by that key before retrying. Return outcome-unknown to orchestration rather than mislabeling it cancelled.
Do not reuse an instance after a trap unless the runtime and component contract explicitly make that safe. Drop the Store, release capabilities, close streams, delete scratch data under a reviewed retention policy, and settle reservations.
Handle streams as a protocol
For progress or large outputs, define:
- event variants and schema version;
- monotonically increasing sequence;
- maximum event and total bytes;
- producer behavior when the consumer is slow;
- terminal success/error record;
- cancellation propagation;
- reconnect or replay policy;
- UTF-8 and binary framing rules.
Native WASI streams solve cross-component scheduling and ownership. They do not choose these application semantics. Never stream raw secrets, chain-of-thought, unrestricted guest logs, or unvalidated terminal escape sequences to a browser.
Build a compatibility and abuse suite
Freeze the component, WIT package, guest toolchain, Wasmtime patch, host build, and capability policy. Test:
- native async invocation and concurrent independent tasks;
- stream backpressure with a deliberately slow consumer;
- cancellation during guest compute and each host import;
- fuel, epoch, memory, output, scratch, and host-call exhaustion;
- denied filesystem, environment, and network access;
- malformed canonical-ABI values and maximum lengths;
- trap cleanup and Store disposal;
- idempotent effect retry and unknown-outcome reconciliation;
- component composition with an unexpected transitive import;
- runtime security-patch upgrade and rollback.
Fuzz host imports, WIT lifting/lowering boundaries, and domain parsers. Run multi-tenant isolation tests and inspect logs, crash dumps, and telemetry for leaked guest input.
Roll out with an immutable compatibility matrix
Promote only tested tuples of component digest, WIT version, guest toolchain, runtime patch, and policy. Canary read-only tools, then effectful ones behind approvals. A runtime upgrade is a security and compatibility change; deploy it separately from a new guest whenever possible.
If a vulnerability appears, deny new scheduling for affected tuples, preserve audit evidence, reconcile in-flight effects, and switch to a previously tested runtime/component pair. Never downgrade past a relevant security fix merely to regain compatibility.