- Introduction — What Actually Happened on June 11
- The Real Problem with WASI 0.2 — The Sandwich Problem
- Three New Primitives
- Where Did wasi:io Go
- What Actually Changed, Interface by Interface
- Is the Runtime Ready — What Wasmtime 46 Actually Did
- Honestly — What's Still Not Done
- On the Claim of "Six Orders of Magnitude Faster"
- Component Model 1.0 and WASI 1.0 — No Date Yet
- So, Should You Migrate Now
- Closing
- References
Introduction — What Actually Happened on June 11
On June 11, 2026, the WASI Subgroup voted to ratify WASI 0.3.0. The headline is: "async has arrived natively in WebAssembly components."
That sentence is easy to read as hype, and the WASM camp really has repeated "it's almost here" several times over the past few years. So this post tries to separate two things — what has actually landed in the spec and the runtime, and what isn't actually done yet but is easy to read as done.
Conclusion first. This release is real. But what "real" means here differs from what most people expect. This isn't a performance release — it's a composability release. And on performance alone, there's actually a spot right now that's a step backward. I'll get to that below too.
The Real Problem with WASI 0.2 — The Sandwich Problem
Async worked in WASI 0.2 too. The wasi:io package provided the pollable resource, input-stream / output-stream, and the poll function, and with those you could express non-blocking I/O.
The problem blew up when you composed components. The example wasi.dev's 0.3 overview doc gives is clear: consider a situation where component A calls B, and B calls the host.
A → B → Host
Here, pollable is a resource bound to a single component instance. So B has no way to hand the wakeup of a pollable it received from the host straight through to A. All B can do is keep polling itself to relay the readiness signal, and in the document's words, in practice that wakeup chain just breaks. This is called the sandwich problem — WASI 0.2 could express async, but it couldn't compose it across component boundaries.
The Bytecode Alliance's announcement post puts it more bluntly. In WASI 0.2, every component needed its own event loop or async runtime, so you could run individual components on top of a host, but those event loops had no way to coordinate with each other. As a result, the moment a component used streaming or an async API, that component couldn't be composed with other components.
Why this hurts becomes clear once you think about the reason the Component Model exists at all. The Component Model's selling point is "binaries you can assemble regardless of language," but real-world code almost always does I/O, doing I/O means using async, and using async meant you couldn't assemble it. In other words, the most important feature didn't work in the most common case.
WASI 0.3's solution is to push async down into the Component Model's canonical ABI. Now the host manages a single event loop, and every component shares it. Since the runtime owns scheduling and wakeup propagation, async works correctly no matter how many components sit between the producer and the consumer.
Three New Primitives
Three things were added to the canonical ABI as first-class citizens — async func, and the stream and future types.
// Declare the function itself as async
handle: async func(request: request) -> result<response, error-code>;
// stream<T>: a typed asynchronous data channel
// future<T>: asynchronous completion of a single value
read-via-stream: func() -> tuple<stream<u8>, future<result<_, error-code>>>;
A few design decisions are worth a closer look.
Ownership. Streams and futures behave like resource types — each is an owned handle, and passing it across a component boundary transfers ownership from the caller to the callee. Unlike resources, though, they cannot be borrowed. In the exact opposite of 0.2, where input-stream was a resource bound to an instance and couldn't be handed off, streams are now values you can pass along. This is exactly where the sandwich problem gets solved.
A completion-based model. This is not readiness-based. The announcement post compares it to Linux's io_uring and Windows's IOCP/IoRing. It states explicitly that if you need a readiness-based API in the style of epoll or kqueue, you can emulate it on top of this. This choice isn't a matter of taste — it connects directly to composability, because readiness signals have to be relayed, while completions just need the runtime to wake things up directly.
Fixing the stream-state problem. In 0.2, a stream's termination error rode inline on every read call. So the caller could only find out the result by reading all the way to the end, and if you stopped reading partway through, you couldn't tell a clean finish apart from an error. 0.3 attaches one more future to the stream, so the result is settled separately, independent of how much of the stream you consumed. The shape in the code above — returning a stream and a future together as a tuple — is exactly that answer.
Where Did wasi:io Go
The wasi:io package was deleted entirely. There is no WASI 0.3 version of wasi:io. Everything it used to do has been absorbed into the Component Model's native primitives.
The mapping table the release notes give sums up half of this release.
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 the future (handled by the runtime)
subscribe() on resource → the call returns a future<...>
start-foo / finish-foo → foo: async func(...)
The last line is the one you feel most in practice. The two-step dance of 0.2's start-bind / finish-bind, start-connect / finish-connect, and the subscribe() that drove it, all disappear and collapse into a single async func.
What Actually Changed, Interface by Interface
The release notes say most of the changes are mechanical. The acrobatics 0.2 performed to fake async are no longer needed, so you now write the same thing far more concisely. Still, a few spots aren't mechanical.
wasi:http — the largest scope of change. This is the one place that didn't just mechanically convert a polling interface to async — it reorganized the worlds and the core abstractions. incoming-request, outgoing-request, incoming-response, outgoing-response, incoming-body, outgoing-body, future-trailers, future-incoming-response, response-outparam — all of these resources disappeared, consolidated into unified request / response resources where the body is a stream and the trailers are a future. And the world splits in two.
// service world: makes HTTP calls, handles incoming requests
world service {
import client;
export handler;
}
// middleware world: a superset of service
world middleware {
include service; // does everything service does
import handler; // can also forward incoming requests to another handler
}
The proxy world from the 0.2 era is replaced by service, and the middleware world expresses request-path middleware as a first-class citizen.
wasi:sockets — from 7 interfaces down to 2. network, instance-network, tcp, tcp-create-socket, udp, udp-create-socket merge into a unified types interface holding the tcp-socket and udp-socket resources, and DNS is handled by a separate ip-name-lookup. And the network resource is gone — 0.2 modeled network access as a capability resource carried along every bind/connect/lookup call, but 0.3 removes that and grants permission at the world-import level instead.
wasi:clocks — mostly renaming. wall-clock becomes system-clock, and datetime becomes instant. The reason is consistency — "wall clock" and "datetime" were WASI-only terms that appear in neither POSIX nor Rust's std::time. The seconds field of the instant record changed from u64 to s64, so it can now express pre-epoch timestamps. The downstream impact is mostly find-and-replace, but the release notes flag this specifically because the churn shows up across the whole test suite.
wasi:random — a semantic change hides behind the name. The parameter of get-random-bytes changed from len to max-len, and that isn't a simple rename. An implementation is now allowed to return fewer bytes than requested. That means callers have to loop until they've collected as many as they want. If you assume this is a mechanical conversion and move on, this is a good place to get bitten.
Is the Runtime Ready — What Wasmtime 46 Actually Did
This is the point where you have to separate "promise" from "ship."
The announcement post (June 11) said that Wasmtime 45 was running the then-latest release candidate, and that Wasmtime 46 would ship WASI 0.3.0 by turning Component Model async on by default. That's future tense.
So checking what actually happened — Wasmtime 46.0.0 was released on June 22, 2026, and its release notes have this line: Wasmtime now supports WASI 0.3.0 by default, and the component-model-async wasm feature is enabled by default (#13612). The promise arrived for real in 11 days. The runtime side is real.
One documentation inconsistency is worth pointing out. The wasi.dev roadmap states that "WASI 0.3 support is available from Wasmtime 43+," while the announcement post talks about 45/46. The overview doc explains the gap — from Wasmtime v44, wasmtime serve could serve 0.3 components if you passed the -Sp3 -W component-model-async=y flags (and a component that doesn't export the 0.3 service world automatically falls back to the 0.2 wasi:http/proxy world), and v44 also added initial support for wasi:tls@0.3.0-draft. In other words, support behind a flag landed earlier, and 46 is when it was turned on by default. Different documents cite different numbers because they don't distinguish between the two.
Conformance is verified with the shared wasi-testsuite, and WASI 0.3 coverage runs on Wasmtime and jco across Linux, macOS, and Windows.
Honestly — What's Still Not Done
This is where the actual reason for writing this post starts. Read only the announcement post and it looks like everything is done, but read it together with another post the Bytecode Alliance itself put out, and the picture changes considerably.
Synchronous calls are currently about 3.5x slower. The Road to Component Model 1.0 has this passage: the current implementation manages async task infrastructure that crosses component boundaries through host calls, and this adds roughly 3.5x overhead even to purely synchronous call paths (cited as a figure Nick Fitzgerald measured at the Plumbers Summit). Some of this has already been handled at the spec level (the recursion check was removed during P3 development), and the rest is planned for after WASI P3 ships — the direction is to refactor task state so the sync adapter allocates only a lightweight task on the stack, letting Cranelift optimize most of it away, with the goal of getting back to the performance profile from before async support was added.
It's important to read this sentence precisely. The goal isn't "get faster" — it's "get back to as fast as before." In other words, the synchronous path is currently paying the price of adding native async, and that bill hasn't been paid off yet.
Guest toolchains are still catching up. A spec being ratified and you actually being able to use it in your language are two different things. The announcement post says async support in guest binding generators is in progress across several languages, including Python, JavaScript, C#, and C. jco supports all of WASI 0.3, but a release where that's enabled by default is due "soon"; on the Rust side, async fn maps naturally via wit-bindgen. Go is a different texture — componentize-go runs stackful coroutines, parking goroutines at the ABI boundary and resuming them once a stream is ready. This is possible because the Component Model's async ABI is designed to accommodate both stackless and stackful coroutines. As the announcement post puts it, individual projects will announce 0.3 support "over the coming weeks to months." That means, as of today, most of them are still not there.
Threads and stream splicing didn't make it into 0.3.0. Both were pushed to a follow-up 0.3.x release. Cooperative threads are implemented at the Component Model level (a layer below WASI), and if a Rust component uses std::thread::spawn, it gets support without doing anything in WIT. Progress so far: wasi-libc's pthreads support is nearly done, an LLVM patch just landed, and Wasmtime has an implementation working behind a feature flag. The order is cooperative first, preemptive later. Stream splicing (stitching one stream into another without a copy in the middle) matters for streaming performance, but in the document's own words it simply came too close to the deadline to make it into P3 itself.
LLVM may be the longest lead time. The ABI work heading toward 1.0 is tied to C-ABI-level multivalue returns, and LLVM doesn't yet support multivalue at the C ABI level. A precise ABI proposal exists, but the post itself admits that landing it upstream could be the longest-running piece of this work. For reference, LLVM releases every six months, and it takes roughly nine weeks to get something into Rust all the way to stable. The Bytecode Alliance doesn't control this whole pipeline.
There's no migration documentation from 0.2 yet. A detailed 0.2 → 0.3 migration guide with real examples attached is "in progress" in the Component Model documentation.
On the Claim of "Six Orders of Magnitude Faster"
The announcement post has this line: thanks to the service chaining the middleware world enables, microservice components that talk to each other frequently can be composed directly inside the same process instead of going over the network, and so "for most microservices, the time to call another microservice will drop from milliseconds to nanoseconds — six orders of magnitude" is the claim.
This sentence should be read as follows.
- This is a projection, not a measurement. The original text is future tense ("will reduce"), and no benchmark or measurement conditions are given.
- The comparison is unfair. Comparing "a network hop" to "a function call inside the same process" is naturally going to split by orders of magnitude. This number comes from not going over the network, not from WASI 0.3 being fast. By the same logic, just merging two microservices into a single binary would also give you six orders of magnitude.
- And as we saw in the section right before this, another post from the same organization currently states that there's a 3.5x overhead on calls between synchronous components. The two statements aren't contradictory (one is the gain from removing the network, the other is call-convention overhead) — but you need to place them side by side to get a balanced picture.
It's worth reading the browser-side number by the same standard. In an experiment by Mozilla's Ryan Hunt, a WASM VDOM reconciliation loop with heavy DOM changes was able to get close to a 2x speedup by bypassing the JavaScript glue layer and calling browser APIs directly. The post itself adds the caveat that "actual gains will vary by workload." It's an upper bound for a specific workload, not a general expectation.
This is exactly the spot the WASM camp has been dogged by for a long time. The technology is genuinely good, but the claims always run half a beat ahead. The real achievement of this release isn't nanoseconds — it's that components can now be composed while using async — and that alone is big enough news.
Component Model 1.0 and WASI 1.0 — No Date Yet
This is a spot people often confuse, so to be clear: Component Model 1.0 and WASI 1.0 are separate milestones. And there's an order — WASI 1.0 follows only after the Component Model reaches 1.0 first.
An analogy Luke Wagner made at the Plumbers Summit explains this relationship well. The Component Model is an always-present microkernel — it provides base primitives that run on any host. WASI is the OS services layered on top of that (networking, storage, graphics), running like processes on top of the microkernel, and it may or may not be present depending on the device. Thinking about the browser makes this easy to grasp — browsers have very strong opinions about which I/O APIs should exist, so WASI interfaces run as polyfills in the browser. The Component Model itself, by contrast, only provides computational primitives, so it can be implemented natively alongside core WASM.
But there's a formidable gate here. The Component Model cannot officially reach 1.0 unless it's natively implemented in at least two browser engines. Here's the current state — the Mozilla team recently presented performance results at a WebAssembly CG in-person meeting, and the Chrome V8 team opened an issue to evaluate a Component Model implementation. The post's own wording is precise: this is a meaningful signal, not a promise.
So the strategy for getting to 1.0 is interesting. It uses the exact playbook from the asm.js days — asm.js embedded a no-op string, "use asm", that browser engines could detect through feature-usage telemetry, and that data built the case for optimization, eventually leading to WebAssembly itself. jco is doing the same thing now. The JavaScript glue jco generates emits a "use components" identifier (renamed from "use jco" in jco 1.16.8). The calculation is that as real-world usage of jco-transpiled components accumulates, it builds the case for a native implementation.
To sum up, there is no primary source that states a date for WASI 1.0. Not on the roadmap page, and not in the post covering the road to 1.0. The roadmap document itself nails this down in its first line — that it's a living document, and its goals and projections are tentative and subject to change. (I've seen numbers like a "late 2026 / early 2027 target" circulating online in a few places, but since they're not confirmed in any primary source, I won't carry them over here.)
The only thing that's certain is the cadence. WASI point releases go out every two months, on the first Thursday of that month, on a release-train model — meaning it ships on a fixed cadence regardless of whatever happens to be "ready to board." After 0.3.0, backward-compatible 0.3.x releases ride this train. The cargo on the schedule: cancellation integrated into language idioms, stream specialization and optimization, caller-provided buffers that widen zero-copy, and threads.
So, Should You Migrate Now
The most important fact first. Migration is not mandatory. You don't have to move to 0.3 just to use a 0.3-capable runtime. wasmtime serve runs both 0.3 and 0.2 components from the same binary, dispatching per component. The roadmap also states explicitly that implementations can support 0.2 and 0.3 side by side, or polyfill 0.2 on top of 0.3 (virtualize it). P1 modules still run, and P2 components still run too — this team has maintained stability since P1 through semantic versioning, side-by-side implementations, and WASM-to-WASM adapters, and that story continues past 1.0 as well.
When migrating now pays off
- You're actually composing components, and async or streaming is part of that chain. If you've already felt the pain of the sandwich problem, this is exactly the cure.
- You want to express HTTP middleware as a component — something that couldn't be expressed with 0.2's
proxyworld is now first-class with themiddlewareworld. - You have consumers that stop a stream partway through, and you need to distinguish a clean finish from an error.
- You can pin your runtime to Wasmtime 46+, and your guest language is Rust.
When it's better to wait
- The synchronous call path is your hot path. The fix for the 3.5x overhead is still only at the planning stage.
- Your guest language is one like Python, C#, or C, where binding async support is still in progress.
- You need threads or stream splicing — you'll have to wait for 0.3.x.
- You only run a single component on top of a host. If you're not composing, 0.2 is enough, and the problem 0.3 solves isn't your problem.
If you decide to migrate, there's one practical trap. Pin your toolchain versions consistently. The Component Model defines canonical interface names, which does make linking across compatible versions possible, but not every tool supports this version-aware linking yet. Until they do, Wasmtime and your binding generator (wit-bindgen for Rust, jco for JavaScript) need to target the same WIT version, 0.3.0. If they drift apart, it surfaces at instantiation time as a hard-to-diagnose wrong type error. This is the kind of error that eats half a day tracking down the cause, so knowing about it ahead of time is worth a lot.
The rest of the migration procedure is mostly mechanical — swap wasi:io types according to the mapping table above, choose the right world (wasi:cli/command for a CLI, wasi:http/service for an HTTP server, wasi:http/middleware for middleware), and change start-foo / finish-foo call sites to async func. Just don't let semantic changes like the max-len one in wasi:random mentioned earlier slip through your mechanical conversion.
Closing
To sum up: WASI 0.3.0 was ratified on June 11, 2026, moving async out of the wasi:io package and down into the Component Model's canonical ABI. wasi:io was deleted, and pollable / input-stream / output-stream / poll were replaced by async func and the stream/future primitives. Wasmtime 46 turned this on by default on June 22.
This release's value isn't speed — it's composability. Under 0.2, a component became impossible to compose the moment it used async — a defect that ate directly into the Component Model's reason for existing — and 0.3 fixed that by moving event-loop ownership to the host. That alone is big enough news. The nanosecond marketing wasn't even necessary.
At the same time, to be honest: the synchronous call path currently carries roughly 3.5x overhead and the fix for it is still just a plan; guest toolchains are in progress across languages; threads and splicing are on the next train; Component Model 1.0 is waiting on native implementations in two browser engines; and nobody has promised a WASI 1.0 date.
So the practical call is simple. If you're composing components and async is part of that chain, look at it now. If not, 0.2 keeps running, a polyfill exists, and the next train comes in two months. The most trustworthy thing about this project isn't a date on the roadmap — it's the cadence of the release train. And this time, what was promised actually arrived in 11 days.
References
- WASI 0.3 Launched — Bytecode Alliance announcement post (Bailey Hayes, Yosh Wuyts, 2026-06-11)
- WASI v0.3.0 release notes — detailed changes by interface
- WASI 0.3 overview — wasi.dev (the sandwich problem, migration, runtime support)
- WASI roadmap — the release train and items planned for 0.3.x
- The Road to Component Model 1.0 (Eric Gregory, 2026-06-08) — the 3.5x overhead, the browser path, ABI work
- Wasmtime 46.0.0 release notes — WASI 0.3 enabled by default (2026-06-22)
- wasi-testsuite — runtime conformance verification
- WebAssembly Beyond the Browser — an overview of WASI and the Component Model (related post)
현재 단락 (1/95)
On June 11, 2026, the WASI Subgroup voted to [ratify WASI 0.3.0](https://github.com/WebAssembly/WASI...