WASI 0.3
WASI 0.3 brings native async to WebAssembly components, moving asynchronous functionality that previously lived in the wasi:io package down into the Component Model itself. WASI 0.3.0 was released on June 11, 2026. This page provides an overview of what changed in WASI 0.3 and why.
Why native async?
In WASI 0.2, asynchronous operations were modeled with the wasi:io package, which provided pollable resources, input-stream and output-stream types, and a poll function. This system worked well when a component communicated directly with a host or a single peer, but broke down when components were composed in a chain.
Consider a scenario where component A calls component B, which in turn calls the host:
A → B → Host
In WASI 0.2, component B could not forward wake-ups from the host's pollable to component A, because pollable is a resource scoped to a single component instance. B would have to actively poll just to relay readiness signals. In practice, the wake-up chain gets dropped. This is sometimes called the sandwich problem: WASI 0.2 could express async, but could not compose it across component boundaries.
WASI 0.3 solves this by pushing async into the Component Model's Canonical ABI. The runtime owns the scheduling and wake-up propagation, so async works correctly regardless of how many components sit between a producer and a consumer.
New primitives
WASI 0.3 introduces three primitives built into the Component Model:
async func
Functions can now be declared as asynchronous in WIT. The runtime manages scheduling and suspension.
// A WASI 0.3 HTTP handler
handle: async func(request: request) -> result<response, error-code>;
Bindings generators emit language-native async constructs: async fn in Rust, Promise in JavaScript, coroutines in Python.
stream<T>
A typed, asynchronous data channel. Unlike input-stream and output-stream in WASI 0.2, a stream<T> is a value that can be passed across component boundaries.
// Reading from stdin in WASI 0.3
read-via-stream: func() -> tuple<stream<u8>, future<result<_, error-code>>>;
future<T>
A single-value async completion. Where WASI 0.2 used pollable resources, WASI 0.3 uses future<T> values.
// Writing to stdout in WASI 0.3
write-via-stream: func(data: stream<u8>) -> future<result<_, error-code>>;
Common patterns
The stream-plus-future pattern
A recurring pattern in WASI 0.3 pairs a stream<T> with a future that signals completion or error:
read-via-stream: func() -> tuple<stream<u8>, future<result<_, error-code>>>;
The stream delivers data incrementally. Once the stream closes, the future resolves to indicate whether the operation completed successfully or encountered an error. This pattern appears in stdin, filesystem reads, TCP receives, and directory listings.
The write direction flip
In WASI 0.2, write operations gave you an output-stream that you wrote into. In WASI 0.3, the direction is reversed: you pass in a stream<u8> and receive a future<result> that resolves when the write completes. This applies to stdout, stderr, filesystem writes, and TCP sends.
// WASI 0.2: get a stream, write to it
get-stdout: func() -> output-stream;
// WASI 0.3: pass data in, get a completion future
write-via-stream: func(data: stream<u8>) -> future<result<_, error-code>>;
Worlds
WASI 0.3 defines the following worlds across its core proposals:
wasi:cli/commandtargets command-line programs that export anasync func run(), with access to filesystem, environment variables, stdin/stdout/stderr, exit, and clocks/random/sockets imports.wasi:http/servicetargets HTTP servers that export an asynchandler, with imports from clocks, random, cli stdio, and the HTTP client interface. Theserviceworld replaceswasi:http/proxyfrom WASI 0.2.wasi:http/middlewareextendsserviceby also importing the handler interface, providing first-class support for request-path middleware components.
Each core proposal additionally exposes an imports world (wasi:cli/imports, wasi:sockets/imports, wasi:filesystem/imports, wasi:clocks/imports, wasi:random/imports) aggregating that package's interfaces for use by other worlds.
What changed in each interface
wasi:io (removed)
The entire wasi:io package has been removed. Its functionality is replaced by the Component Model's native stream<T>, future<T>, and async func primitives. There is no WASI 0.3 version of wasi:io.
WASI 0.2 (wasi:io) | WASI 0.3 (Component Model) |
|---|---|
resource pollable | future<T> |
resource input-stream | stream<u8> |
resource output-stream | stream<u8> (write direction) |
poll(list<pollable>) | await on a future |
subscribe() on resource | return a future from the call |
start-foo / finish-foo pattern | foo: async func(...) |
wasi:http
HTTP saw the most dramatic restructuring in WASI 0.3.
Resources dramatically simplified. The incoming-request, outgoing-request, incoming-response, outgoing-response, incoming-body, outgoing-body, future-trailers, future-incoming-response, and response-outparam resources are all gone, replaced by unified request and response resources with stream<u8> bodies and future trailers.
The handler is now an async func:
// WASI 0.3 handler
handle: async func(request: request) -> result<response, error-code>;
// WASI 0.3 client
send: async func(request: request) -> result<response, error-code>;
New worlds. The proxy world has been replaced by service. A new middleware world both imports and exports the handler, providing first-class support for request-path middleware components.
New header error variant. The header-error variant gains size-exceeded for headers that exceed the runtime's configured size limit.
wasi:sockets
Interfaces consolidated from 7 to 2. The network, instance-network, tcp, tcp-create-socket, udp, and udp-create-socket interfaces are consolidated into a unified types interface containing both tcp-socket and udp-socket resources. A separate ip-name-lookup interface handles DNS resolution.
The network resource is removed. Network access is now granted at the world level.
Async operations are simplified. All start-connect/finish-connect and start-listen/finish-listen pairs are replaced with single async func calls:
connect: async func(remote-address: ip-socket-address) -> result<_, error-code>;
TCP listen returns a stream of sockets:
listen: func() -> result<stream<tcp-socket>, error-code>;
New error variant. The error-code enum gains connection-broken (POSIX EPIPE), distinguishing writes that failed because the peer closed the connection from other reset or abort conditions.
wasi:cli
stdin, stdout, and stderr use stream<u8> with the stream-plus-future pattern. The run function is now async:
run: async func() -> result;
A new shared types interface defines the error-code enum used across CLI interfaces.
The wasi:cli/exit interface now exposes both exit(status: result) and exit-with-code(status-code: u8). The exit-with-code function was promoted from Phase 2 into the stable 0.3 interface in June 2026.
wasi:filesystem
Nearly all descriptor methods are now async func. Streaming reads and writes use the stream-plus-future pattern, and directory listing returns stream<directory-entry>:
read-directory: func() -> tuple<stream<directory-entry>, future<result<_, error-code>>>;
The error-code enum gains a catch-all other(option<string>) variant for errors not captured by the existing variants.
wasi:clocks
Interfaces and types have been renamed to align with POSIX and Rust std::time conventions:
| WASI 0.2 | WASI 0.3 |
|---|---|
wall-clock | system-clock |
datetime | instant |
The instant record now uses s64 for seconds (instead of u64), supporting pre-epoch timestamps.
The subscribe-instant and subscribe-duration functions (which returned pollable) are replaced with async func equivalents:
wait-until: async func(when: mark);
wait-for: async func(how-long: duration);
wasi:random
The len parameter is renamed to max-len on get-random-bytes and get-insecure-random-bytes. The rename reflects a semantic change: implementations may return fewer bytes than requested, so callers should loop until they have collected the desired amount.
Runtime and tooling support
WASI 0.3 support is available in:
- Wasmtime Wasmtime 45 runs the latest release candidate today, and Wasmtime 46 will ship WASI 0.3.0 with Component Model Async enabled by default.
- jco for JavaScript environments
Wasmtime v44 added initial wasi:tls@0.3.0-draft support. From v44 onward, wasmtime serve can serve 0.3 components when invoked with -Sp3 -W component-model-async=y, automatically falling back to the WASI 0.2 wasi:http/proxy world for components that don't export the 0.3 service world.
Runtimes verify WASI 0.3 conformance against the shared wasi-testsuite. WASI 0.3 coverage is now running on Wasmtime and jco across Linux, macOS, and Windows.
Migrating from WASI 0.2
Migration to WASI 0.3 is not required in order to use a 0.3-capable runtime. As noted above, wasmtime serve runs both 0.3 and 0.2 components from the same binary, dispatching per component. The roadmap also notes that implementations may polyfill 0.2 in terms of 0.3, mapping 0.2 imports onto native 0.3 primitives at the host boundary.
To migrate:
- Pinning your toolchain to a consistent WASI 0.3 release. The Component Model defines canonical interface names so that components can link across compatible versions, but not all tools support this version-aware linking yet. Until they do, Wasmtime and the bindings generator (
wit-bindgenfor Rust,jcofor JavaScript, and so on) should target the same WIT version,0.3.0. Mismatches surface as confusingwrong typeerrors at instantiation. - Replacing
wasi:iotypes with their Component Model equivalents (see thewasi:io(removed) section above for the mapping). - Switching to the appropriate world:
wasi:cli/commandfor CLI programs,wasi:http/servicefor HTTP servers,wasi:http/middlewarefor middleware components. - Updating
start-foo/finish-foocall sites to use the correspondingasync funcdeclarations.
A detailed 0.2-to-0.3 migration guide with working examples is forthcoming in the Component Model documentation.