From 63dc70cedb7b7bd99d7c3c05a76a8071e7e9a63d Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Fri, 12 Jun 2026 14:21:37 -0400 Subject: [PATCH 01/23] docs(design): add Async, Streams, and Futures concepts page Add a new design/async.md introducing the Canonical ABI primitives (`async func`, `stream`, `future`) that the Component Model added for the WASI P3 release. The page covers the sandwich problem (why native async matters for composition), each primitive with a worked WIT example, the stream-plus-future and write-direction-flip patterns, and a tooling-support pointer. Hook the page into Component Model Concepts as a new subsection after Packages, and add the SUMMARY.md entry under that umbrella. Signed-off-by: Eric Gregory --- component-model/src/SUMMARY.md | 1 + component-model/src/design/async.md | 87 +++++++++++++++++++ .../src/design/component-model-concepts.md | 4 + 3 files changed, 92 insertions(+) create mode 100644 component-model/src/design/async.md diff --git a/component-model/src/SUMMARY.md b/component-model/src/SUMMARY.md index f0f1e37e..2ac9186a 100644 --- a/component-model/src/SUMMARY.md +++ b/component-model/src/SUMMARY.md @@ -11,6 +11,7 @@ - [Interfaces](./design/interfaces.md) - [Worlds](./design/worlds.md) - [Packages](./design/packages.md) + - [Async, Streams, and Futures](./design/async.md) - [WIT By Example](./design/wit-example.md) - [WIT Reference](./design/wit.md) diff --git a/component-model/src/design/async.md b/component-model/src/design/async.md new file mode 100644 index 00000000..085184bb --- /dev/null +++ b/component-model/src/design/async.md @@ -0,0 +1,87 @@ +# Async, Streams, and Futures + +WASI P3 is built on three new Canonical ABI primitives in the Component Model: `async func`, `stream`, and `future`. Together, they let interfaces express asynchronous operations that compose across component boundaries. + +## Why native async? + +WASI P2 modeled asynchronous I/O through the `wasi:io` package, which exposed three resources: + +- `pollable`: an opaque handle representing readiness +- `input-stream`: a byte source +- `output-stream`: a byte sink + +These work fine when a component talks directly to its host. They break down when components are composed. Consider a three-layer chain: + +``` +A → B → Host +``` + +Component A makes a call into Component B that requires waiting on the Host. B calls the Host, which returns a `pollable` representing the eventual completion of the work. That `pollable` is a Component Model resource scoped to B's instance — A cannot hold it, poll it, or name it. B would have to actively check the `pollable` and forward the wake-up to A, but there is no Canonical ABI hook for "when this `pollable` becomes ready, run this code in B." In practice the wake-up gets dropped, and B has to actively poll its own `pollable` just to relay readiness back to A. + +This is sometimes called the **sandwich problem**: WASI P2 could express async, but could not compose it across component boundaries. + +The Component Model solves this by pushing async down into the Canonical ABI. The runtime owns scheduling and wake-up propagation, so async works correctly regardless of how many components sit between a producer and a consumer. + +## The three primitives + +### `async func` + +A function declared as asynchronous in WIT: + +```wit +handle: async func(request: request) -> result; +``` + +Bindings generators emit language-native async constructs: `async fn` in Rust, `Promise` in JavaScript, coroutines in Python. + +### `stream` + +A typed, asynchronous data channel. Unlike `input-stream` and `output-stream` in WASI P2, a `stream` is a *value* that can be passed across component boundaries the same way a `u32` can. + +```wit +read-via-stream: func() -> tuple, future>>; +``` + +### `future` + +A single-value async completion. Where WASI P2 used `pollable` resources, WASI P3 uses `future` values. + +```wit +write-via-stream: func(data: stream) -> future>; +``` + +## Common patterns + +### The stream-plus-future pattern + +A recurring pattern in WASI P3 pairs a `stream` with a `future` that signals completion or error: + +```wit +read-via-stream: func() -> tuple, future>>; +``` + +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 P2, write operations gave you an `output-stream` that you wrote into. In WASI P3, the direction is reversed: you pass in a `stream` and receive a `future` that resolves when the write completes. This applies to stdout, stderr, filesystem writes, and TCP sends. + +```wit +// WASI P2: get a stream, write to it +get-stdout: func() -> output-stream; + +// WASI P3: pass data in, get a completion future +write-via-stream: func(data: stream) -> future>; +``` + +## Tooling support + +`async func`, `stream`, and `future` are native Canonical ABI features in the Component Model, supported by: + +- [Wasmtime](https://wasmtime.dev/) 43 and later, via `-Sp3 -W component-model-async=y` when running components. +- [`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen) with the `async` feature enabled, for Rust guest bindings. +- [jco](https://github.com/bytecodealliance/jco) for JavaScript environments, via the `preview3-shim` package. + +For an end-to-end Rust example, see [Creating Runnable Components in Rust](../language-support/creating-runnable-components/rust.md). For the WIT-level details of how to declare these types in your own interfaces, see [WIT Reference](./wit.md). + +> For a broader overview of what changed in WASI P3, including per-interface diffs, see [WASI P3](https://wasi.dev/releases/wasi-p3) on WASI.dev. diff --git a/component-model/src/design/component-model-concepts.md b/component-model/src/design/component-model-concepts.md index c4b67924..1d88fe18 100644 --- a/component-model/src/design/component-model-concepts.md +++ b/component-model/src/design/component-model-concepts.md @@ -65,6 +65,10 @@ For example, the [wasi-http](https://github.com/WebAssembly/WASI/blob/main/propo an `imports` world encapsulating the interfaces that an HTTP proxy depends on, and a `proxy` world that depends on `imports`. +### Async, Streams, and Futures + +The Component Model includes [`async func`, `stream`, and `future`](./async.md) as native Canonical ABI primitives, introduced alongside WASI P3. Together, they let interfaces express asynchronous operations that compose across component boundaries. + ### Platforms In the context of WebAssembly, a _host_ refers to a WebAssembly runtime From 3ea81113e07839ebce3ad26aee992f8de9565c85 Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Fri, 12 Jun 2026 14:31:08 -0400 Subject: [PATCH 02/23] docs(design): add Migrating from WASI P2 to WASI P3 guide Add a new design/migrating-to-p3.md covering the conceptual P2 -> P3 mapping for component authors: a "Do you need to migrate?" framing, the wasi:io-to-Canonical-ABI concept mapping table, three before/after WIT patterns (stream-plus-future, write-direction flip, two-step calls collapsed), short per-interface notes for wasi:io/http/sockets/ filesystem/cli/clocks/random, a tooling requirements table, and a version-pinning callout for the current RC-to-0.3.0 transition. Hook the page into the Understanding section after WIT Reference. Signed-off-by: Eric Gregory --- component-model/src/SUMMARY.md | 1 + component-model/src/design/migrating-to-p3.md | 129 ++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 component-model/src/design/migrating-to-p3.md diff --git a/component-model/src/SUMMARY.md b/component-model/src/SUMMARY.md index 2ac9186a..98c1839c 100644 --- a/component-model/src/SUMMARY.md +++ b/component-model/src/SUMMARY.md @@ -14,6 +14,7 @@ - [Async, Streams, and Futures](./design/async.md) - [WIT By Example](./design/wit-example.md) - [WIT Reference](./design/wit.md) +- [Migrating from WASI P2 to WASI P3](./design/migrating-to-p3.md) # Using WebAssembly Components diff --git a/component-model/src/design/migrating-to-p3.md b/component-model/src/design/migrating-to-p3.md new file mode 100644 index 00000000..67227f89 --- /dev/null +++ b/component-model/src/design/migrating-to-p3.md @@ -0,0 +1,129 @@ +# Migrating from WASI P2 to WASI P3 + +WASI P3 reshapes WASI's interfaces around the [native async primitives](./async.md) — `async func`, `stream`, and `future` — that the Component Model added for this release. Most of the per-package changes in `wasi:cli`, `wasi:http`, `wasi:filesystem`, and `wasi:sockets` are mechanical consequences of moving onto these primitives. + +This page covers the mapping. For a worked WIT-level diff of every WASI P3 interface, see [WASI P3](https://wasi.dev/releases/wasi-p3) on WASI.dev. + +## Do you need to migrate? + +Not immediately. WASI P3 runtimes can polyfill P2 by mapping P2 imports onto native P3 primitives at the host boundary, and Wasmtime's `wasmtime serve` already runs both P3 and P2 components from the same binary, dispatching per component. Migration is the right call when you want: + +- Composable async across component boundaries (the [sandwich problem](./async.md#why-native-async) goes away). +- The newer interface shapes — in particular, `wasi:http`'s collapse of eight resources down to two. +- First-class support in P3-targeted toolchains as they continue to land. + +## Concept mapping + +WASI P3 replaces every `wasi:io` resource with a Canonical ABI primitive. The translation is mostly one-to-one: + +| WASI P2 (`wasi:io`) | WASI P3 (Component Model) | +| -------------------------------- | ---------------------------------------- | +| `resource pollable` | `future` | +| `resource input-stream` | `stream` | +| `resource output-stream` | `stream` (passed *into* the call) | +| `poll(list)` | `await` on a future | +| `subscribe()` on a resource | return a `future` from the call | +| `start-foo` / `finish-foo` | a single `func` or `async func` | + +## What changed in WIT + +Three patterns appear over and over in the rebased interfaces. + +### Stream-plus-future for reads + +In P2, a read call returned an `input-stream`. In P3, it returns a tuple of a `stream` plus a `future>` that resolves once the stream closes: + +```wit +// WASI P2 (filesystem read) +read-via-stream: func(offset: filesize) -> result; + +// WASI P3 (filesystem read) +read-via-stream: func(offset: filesize) -> tuple, future>>; +``` + +The stream delivers data incrementally. The future signals terminal success or failure independently of how much of the stream the caller consumes. + +### Write-direction flip + +P2 write calls handed you an `output-stream` to write into. P3 reverses the direction: you pass a `stream` *into* the call and receive a `future` that resolves when the write completes: + +```wit +// WASI P2: get a stream, write into it +get-stdout: func() -> output-stream; + +// WASI P3: pass data in, get a completion future +write-via-stream: func(data: stream) -> future>; +``` + +### Two-step calls collapsed + +P2 modeled operations that could suspend as a `start-foo` / `finish-foo` pair, with a `pollable` for readiness in between. P3 collapses each pair into a single call: + +```wit +// WASI P2 +start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; +finish-connect: func() -> result, error-code>; + +// WASI P3 +connect: async func(remote-address: ip-socket-address) -> result<_, error-code>; +``` + +The collapsed call is `async func` when the operation needs to suspend in the host (such as `connect`); operations that historically only used the two-step shape for non-blocking dispatch may collapse to plain `func` instead (`bind`, `listen`). + +## Per-interface notes + +### `wasi:io` (removed) + +The package is gone. Everything it expressed is now Canonical ABI primitives. + +### `wasi:http` + +The most dramatic restructuring. The eight P2 resources — `incoming-request`, `outgoing-request`, `incoming-response`, `outgoing-response`, `incoming-body`, `outgoing-body`, `future-trailers`, and `future-incoming-response` — collapse to two: `request` and `response`. Bodies are `stream`; trailers are a `future`. The handler is an `async func`: + +```wit +// WASI P2 +handle: func(request: incoming-request, response-out: response-outparam); + +// WASI P3 +handle: async func(request: request) -> result; +``` + +The `proxy` world is replaced by `service`, with a new `middleware` world that imports and exports the handler. + +### `wasi:sockets` + +The seven P2 interfaces (`network`, `instance-network`, `tcp`, `tcp-create-socket`, `udp`, `udp-create-socket`, `ip-name-lookup`) collapse to a unified `types` interface containing both `tcp-socket` and `udp-socket` resources, plus `ip-name-lookup`. The `network` resource is removed entirely — network access is now granted at the world level. `start-*` / `finish-*` pairs collapse as described above. TCP `listen` returns `stream` directly instead of requiring a separate `accept` call. + +### `wasi:filesystem` + +Most `descriptor` methods are `async func`. Streaming reads and writes use the stream-plus-future pattern; `read-directory` returns `stream`. The `error-code` enum gains a catch-all `other(option)` variant. + +### `wasi:cli` + +stdin, stdout, and stderr use `stream` with the stream-plus-future pattern. `run` becomes `async func`. A shared `wasi:cli/types` interface carries an `error-code` variant. The `exit-with-code` function stabilizes alongside `exit`. + +### `wasi:clocks` + +`wall-clock` is renamed to `system-clock`, and `datetime` is renamed to `instant`. The `instant` record uses `s64` seconds (instead of `u64`), supporting pre-epoch timestamps. `subscribe-instant` and `subscribe-duration` are replaced by `wait-until` and `wait-for` `async func`s. + +### `wasi:random` + +The `len` parameter is renamed to `max-len` on `get-random-bytes` and `get-insecure-random-bytes`. Implementations may now return fewer bytes than requested, so callers should loop. + +## Tooling requirements + +| Tool | Minimum | Notes | +| ------------- | --------------------------------------------------------------- | ------------------------------------------------------------------- | +| Wasmtime | 43+ for `wasmtime run`; 44+ for `wasmtime serve` | Enable with `-Sp3 -W component-model-async=y`. | +| `wit-bindgen` | 0.46+ | Use the `async` feature for P3 binding generation. | +| jco | latest | P3 host bindings ship in the `preview3-shim` package. | +| `wkg` | 0.15+ | Required to fetch `wasi:cli@0.3.0-rc-2026-03-15` and related packages. | +| Rust | nightly | Current stable bundles a `wasm-component-ld` too old for P3 outputs of `wit-bindgen` 0.58. | + +> **Version pinning.** As of WASI 0.3.0's release on 2026-06-11, Wasmtime and `wit-bindgen` still vendor the `0.3.0-rc-2026-03-15` snapshot of the WIT. Components pinning to the published `0.3.0` will fail to instantiate against current Wasmtime; use the RC pin until those tools refresh. + +## Further reading + +- [Async, Streams, and Futures](./async.md) — the conceptual foundation +- [Creating Runnable Components in Rust](../language-support/creating-runnable-components/rust.md) — worked Rust example with the P3 `async fn run()` pattern +- [WASI P3](https://wasi.dev/releases/wasi-p3) on WASI.dev — full WIT-level diff per interface From 58a60a26941a2c18ecba48f3bca5396370616904 Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Fri, 12 Jun 2026 14:41:51 -0400 Subject: [PATCH 03/23] Improve clarity in migrating to WASI P3 documentation Refine language for clarity and consistency in the migration guide. --- component-model/src/design/migrating-to-p3.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/component-model/src/design/migrating-to-p3.md b/component-model/src/design/migrating-to-p3.md index 67227f89..25370969 100644 --- a/component-model/src/design/migrating-to-p3.md +++ b/component-model/src/design/migrating-to-p3.md @@ -1,8 +1,8 @@ # Migrating from WASI P2 to WASI P3 -WASI P3 reshapes WASI's interfaces around the [native async primitives](./async.md) — `async func`, `stream`, and `future` — that the Component Model added for this release. Most of the per-package changes in `wasi:cli`, `wasi:http`, `wasi:filesystem`, and `wasi:sockets` are mechanical consequences of moving onto these primitives. +WASI P3 reshapes WASI's interfaces around the [native async primitives](./async.md) `async func`, `stream`, and `future`. Most of the changes in `wasi:cli`, `wasi:http`, `wasi:filesystem`, and `wasi:sockets` are consequences of moving to these primitives. -This page covers the mapping. For a worked WIT-level diff of every WASI P3 interface, see [WASI P3](https://wasi.dev/releases/wasi-p3) on WASI.dev. +This page covers the mapping between concepts in WASI P2 and WASI P3. For a WIT-level comparison of every WASI P3 interface, see [WASI P3](https://wasi.dev/releases/wasi-p3) on WASI.dev. ## Do you need to migrate? @@ -27,8 +27,6 @@ WASI P3 replaces every `wasi:io` resource with a Canonical ABI primitive. The tr ## What changed in WIT -Three patterns appear over and over in the rebased interfaces. - ### Stream-plus-future for reads In P2, a read call returned an `input-stream`. In P3, it returns a tuple of a `stream` plus a `future>` that resolves once the stream closes: @@ -70,7 +68,7 @@ connect: async func(remote-address: ip-socket-address) -> result<_, error-code>; The collapsed call is `async func` when the operation needs to suspend in the host (such as `connect`); operations that historically only used the two-step shape for non-blocking dispatch may collapse to plain `func` instead (`bind`, `listen`). -## Per-interface notes +## Interface notes ### `wasi:io` (removed) @@ -78,7 +76,7 @@ The package is gone. Everything it expressed is now Canonical ABI primitives. ### `wasi:http` -The most dramatic restructuring. The eight P2 resources — `incoming-request`, `outgoing-request`, `incoming-response`, `outgoing-response`, `incoming-body`, `outgoing-body`, `future-trailers`, and `future-incoming-response` — collapse to two: `request` and `response`. Bodies are `stream`; trailers are a `future`. The handler is an `async func`: +The most dramatic restructuring. The eight P2 resources (`incoming-request`, `outgoing-request`, `incoming-response`, `outgoing-response`, `incoming-body`, `outgoing-body`, `future-trailers`, and `future-incoming-response`) collapse to two: `request` and `response`. Bodies are `stream`; trailers are a `future`. The handler is an `async func`: ```wit // WASI P2 @@ -92,7 +90,7 @@ The `proxy` world is replaced by `service`, with a new `middleware` world that i ### `wasi:sockets` -The seven P2 interfaces (`network`, `instance-network`, `tcp`, `tcp-create-socket`, `udp`, `udp-create-socket`, `ip-name-lookup`) collapse to a unified `types` interface containing both `tcp-socket` and `udp-socket` resources, plus `ip-name-lookup`. The `network` resource is removed entirely — network access is now granted at the world level. `start-*` / `finish-*` pairs collapse as described above. TCP `listen` returns `stream` directly instead of requiring a separate `accept` call. +The seven P2 interfaces (`network`, `instance-network`, `tcp`, `tcp-create-socket`, `udp`, `udp-create-socket`, `ip-name-lookup`) collapse to a unified `types` interface containing both `tcp-socket` and `udp-socket` resources, plus `ip-name-lookup`. The `network` resource is removed entirely; network access is now granted at the world level. `start-*` / `finish-*` pairs collapse as described above. TCP `listen` returns `stream` directly instead of requiring a separate `accept` call. ### `wasi:filesystem` From fc2a837c7ffe21679ca29a262f0bd2cc51f4f258 Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Fri, 12 Jun 2026 15:43:28 -0400 Subject: [PATCH 04/23] docs(design): reframe async page as P3-only, trim duplication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply content-ownership guidance by reframing design/async.md to describe the Component Model primitives in P3 terms throughout, rather than as a P2-to-P3 contrast that overlaps with wasi.dev's WASI P3 page. - Drop the A→B→Host diagram and surrounding A/B/Host references. - Drop P2 comparisons from the primitive descriptions and from the write-pattern code example. - Rename 'Common patterns' → 'How the primitives work in WASI P3'. - Replace 'Tooling support' (which duplicated wasmtime.md) with a brief 'Where to go next' pointer block. In migrating-to-p3.md: - Replace 'Interface notes' (40 lines of per-package detail) with 'Interface highlights' (3 load-bearing changes + link to wasi.dev). - Rewrite the WIT pattern openings with phrasing distinct from both the reframed async.md and wasi.dev/wasi-p3. - Fix a broken intra-document anchor and a wasi:http resource count inconsistency (eight vs nine). Signed-off-by: Eric Gregory --- component-model/src/design/async.md | 56 ++++++------------- component-model/src/design/migrating-to-p3.md | 48 +++++----------- 2 files changed, 31 insertions(+), 73 deletions(-) diff --git a/component-model/src/design/async.md b/component-model/src/design/async.md index 085184bb..a5616c0e 100644 --- a/component-model/src/design/async.md +++ b/component-model/src/design/async.md @@ -2,41 +2,31 @@ WASI P3 is built on three new Canonical ABI primitives in the Component Model: `async func`, `stream`, and `future`. Together, they let interfaces express asynchronous operations that compose across component boundaries. -## Why native async? +For migration mechanics (e.g., how a WASI P2 component maps onto these primitives) see [Migrating from WASI P2 to WASI P3](./migrating-to-p3.md). For the WASI release view, including the full per-interface diff, see [WASI P3](https://wasi.dev/releases/wasi-p3) on WASI.dev. This page focuses on the Component Model concepts themselves. -WASI P2 modeled asynchronous I/O through the `wasi:io` package, which exposed three resources: +## Native async -- `pollable`: an opaque handle representing readiness -- `input-stream`: a byte source -- `output-stream`: a byte sink +The Component Model's Canonical ABI defines how typed values cross component boundaries. Until WASI P3, that vocabulary had no notion of suspension or asynchronous completion; every interface call returned synchronously, and asynchronous I/O was modeled with resources (`pollable` for readiness, `input-stream` and `output-stream` for byte channels) scoped to whichever component obtained them. -These work fine when a component talks directly to its host. They break down when components are composed. Consider a three-layer chain: +That arrangement holds up for two-party interactions, but it falters once components are composed in a chain. If a component awaits work that another component delegates further, the readiness signal has to travel back up the chain. When readiness is expressed as a resource scoped to a single component, the intermediate component is stuck running an event loop purely to forward the wake-up to its caller; the runtime cannot help, because the resource doesn't live in a place the runtime can reach across. This is sometimes called the **sandwich problem**: an async vocabulary that describes a single hop just fine but cannot propagate readiness past one. -``` -A → B → Host -``` - -Component A makes a call into Component B that requires waiting on the Host. B calls the Host, which returns a `pollable` representing the eventual completion of the work. That `pollable` is a Component Model resource scoped to B's instance — A cannot hold it, poll it, or name it. B would have to actively check the `pollable` and forward the wake-up to A, but there is no Canonical ABI hook for "when this `pollable` becomes ready, run this code in B." In practice the wake-up gets dropped, and B has to actively poll its own `pollable` just to relay readiness back to A. - -This is sometimes called the **sandwich problem**: WASI P2 could express async, but could not compose it across component boundaries. - -The Component Model solves this by pushing async down into the Canonical ABI. The runtime owns scheduling and wake-up propagation, so async works correctly regardless of how many components sit between a producer and a consumer. +Native primitives close the gap. With `async func`, `stream`, and `future` in the Canonical ABI, scheduling and wake-up propagation become the runtime's job rather than any individual component's. Components can pass futures and streams along the chain without keeping their own event loops running to relay readiness. ## The three primitives ### `async func` -A function declared as asynchronous in WIT: +A WIT function declared `async` tells the runtime that the call may suspend before producing its result. The Canonical ABI handles the suspension and resumption; the guest doesn't see a `pollable`, and the host doesn't see a polling loop. ```wit handle: async func(request: request) -> result; ``` -Bindings generators emit language-native async constructs: `async fn` in Rust, `Promise` in JavaScript, coroutines in Python. +Code generated from the WIT picks up each language's natural async idiom: `async fn` in Rust, a `Promise`-returning function in JavaScript, a coroutine in Python. ### `stream` -A typed, asynchronous data channel. Unlike `input-stream` and `output-stream` in WASI P2, a `stream` is a *value* that can be passed across component boundaries the same way a `u32` can. +A typed, asynchronous channel for a sequence of `T` values. Crucially, `stream` is a Canonical ABI *value*, not a resource: it can be returned from a call, accepted as a parameter, and handed from one component to another without giving up ownership of the underlying buffer. The same value can also be passed straight through a middle component without that component having to relay any wake-ups. ```wit read-via-stream: func() -> tuple, future>>; @@ -44,44 +34,32 @@ read-via-stream: func() -> tuple, future>>; ### `future` -A single-value async completion. Where WASI P2 used `pollable` resources, WASI P3 uses `future` values. +A typed handle for a single value that will become available later. Like `stream`, `future` is a value rather than a resource, so it crosses component boundaries the same way a primitive does. A function returning `future` does not block; the caller awaits the result when it needs it. ```wit write-via-stream: func(data: stream) -> future>; ``` -## Common patterns +## How the primitives work in WASI P3 -### The stream-plus-future pattern +### Stream plus terminal future -A recurring pattern in WASI P3 pairs a `stream` with a `future` that signals completion or error: +Reads return both a data channel and a completion handle, packed into a tuple: ```wit read-via-stream: func() -> tuple, future>>; ``` -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 two halves are independent. The caller can consume the stream eagerly, sample it, or drop it part-way through; either way the future resolves once the operation has terminated, carrying the success-or-failure outcome. The same shape appears in stdin, filesystem reads, TCP receives, and directory listings. -### The write-direction flip +### Stream parameter, future return -In WASI P2, write operations gave you an `output-stream` that you wrote into. In WASI P3, the direction is reversed: you pass in a `stream` and receive a `future` that resolves when the write completes. This applies to stdout, stderr, filesystem writes, and TCP sends. +Writes use the symmetric shape: the guest supplies the data as a `stream` parameter, and the host returns a `future` that resolves once it has consumed the stream. Stdout, stderr, filesystem writes, and TCP sends all follow this shape: ```wit -// WASI P2: get a stream, write to it -get-stdout: func() -> output-stream; - -// WASI P3: pass data in, get a completion future write-via-stream: func(data: stream) -> future>; ``` -## Tooling support - -`async func`, `stream`, and `future` are native Canonical ABI features in the Component Model, supported by: - -- [Wasmtime](https://wasmtime.dev/) 43 and later, via `-Sp3 -W component-model-async=y` when running components. -- [`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen) with the `async` feature enabled, for Rust guest bindings. -- [jco](https://github.com/bytecodealliance/jco) for JavaScript environments, via the `preview3-shim` package. - -For an end-to-end Rust example, see [Creating Runnable Components in Rust](../language-support/creating-runnable-components/rust.md). For the WIT-level details of how to declare these types in your own interfaces, see [WIT Reference](./wit.md). +## Where to go next -> For a broader overview of what changed in WASI P3, including per-interface diffs, see [WASI P3](https://wasi.dev/releases/wasi-p3) on WASI.dev. +For an end-to-end Rust example that uses these primitives in practice, see [Creating Runnable Components in Rust](../language-support/creating-runnable-components/rust.md). For runtime support and CLI flags, see [Wasmtime](../running-components/wasmtime.md). For the WIT syntax in detail, see [WIT Reference](./wit.md). diff --git a/component-model/src/design/migrating-to-p3.md b/component-model/src/design/migrating-to-p3.md index 25370969..24cec9eb 100644 --- a/component-model/src/design/migrating-to-p3.md +++ b/component-model/src/design/migrating-to-p3.md @@ -8,8 +8,8 @@ This page covers the mapping between concepts in WASI P2 and WASI P3. For a WIT- Not immediately. WASI P3 runtimes can polyfill P2 by mapping P2 imports onto native P3 primitives at the host boundary, and Wasmtime's `wasmtime serve` already runs both P3 and P2 components from the same binary, dispatching per component. Migration is the right call when you want: -- Composable async across component boundaries (the [sandwich problem](./async.md#why-native-async) goes away). -- The newer interface shapes — in particular, `wasi:http`'s collapse of eight resources down to two. +- Composable async across component boundaries (the [sandwich problem](./async.md#native-async) goes away). +- The newer interface shapes — in particular, `wasi:http`'s collapse of nine resources down to two. - First-class support in P3-targeted toolchains as they continue to land. ## Concept mapping @@ -29,7 +29,7 @@ WASI P3 replaces every `wasi:io` resource with a Canonical ABI primitive. The tr ### Stream-plus-future for reads -In P2, a read call returned an `input-stream`. In P3, it returns a tuple of a `stream` plus a `future>` that resolves once the stream closes: +A P2 read call returned a single `input-stream` resource and surfaced terminal errors only as you consumed it. P3 splits those concerns: the call returns a `stream` for the data and a `future>` for the outcome, packed into a tuple. ```wit // WASI P2 (filesystem read) @@ -39,17 +39,17 @@ read-via-stream: func(offset: filesize) -> result; read-via-stream: func(offset: filesize) -> tuple, future>>; ``` -The stream delivers data incrementally. The future signals terminal success or failure independently of how much of the stream the caller consumes. +In P3 the caller does not have to drain the stream to learn whether the read finished cleanly; the future resolves either way. ### Write-direction flip -P2 write calls handed you an `output-stream` to write into. P3 reverses the direction: you pass a `stream` *into* the call and receive a `future` that resolves when the write completes: +P2 write paths handed a guest some host-owned resource (an `output-stream`) and let the guest push bytes into it. P3 inverts that: the guest supplies the data as a `stream` value, and the host returns a `future` that resolves once it has finished consuming the stream. ```wit -// WASI P2: get a stream, write into it +// WASI P2: receive an output-stream resource, write into it get-stdout: func() -> output-stream; -// WASI P3: pass data in, get a completion future +// WASI P3: pass a stream value in, receive a completion future write-via-stream: func(data: stream) -> future>; ``` @@ -68,15 +68,12 @@ connect: async func(remote-address: ip-socket-address) -> result<_, error-code>; The collapsed call is `async func` when the operation needs to suspend in the host (such as `connect`); operations that historically only used the two-step shape for non-blocking dispatch may collapse to plain `func` instead (`bind`, `listen`). -## Interface notes +## Interface highlights -### `wasi:io` (removed) +The complete per-interface diff lives on [WASI P3](https://wasi.dev/releases/wasi-p3#what-changed-in-each-interface) at WASI.dev. The three changes most likely to drive migration work are: -The package is gone. Everything it expressed is now Canonical ABI primitives. - -### `wasi:http` - -The most dramatic restructuring. The eight P2 resources (`incoming-request`, `outgoing-request`, `incoming-response`, `outgoing-response`, `incoming-body`, `outgoing-body`, `future-trailers`, and `future-incoming-response`) collapse to two: `request` and `response`. Bodies are `stream`; trailers are a `future`. The handler is an `async func`: +- **`wasi:io` is gone.** The package has no 0.3.0 release. Every resource it exposed (`pollable`, `input-stream`, `output-stream`) is replaced by a Component Model primitive, per the [concept mapping](#concept-mapping) above. +- **`wasi:http` collapses from nine resources to two.** The incoming/outgoing × request/response/body matrix plus `future-trailers`, `future-incoming-response`, and `response-outparam` all become `request` and `response`, with `stream` bodies and a `future` for trailers. The handler is now an `async func`: ```wit // WASI P2 @@ -86,27 +83,10 @@ handle: func(request: incoming-request, response-out: response-outparam); handle: async func(request: request) -> result; ``` -The `proxy` world is replaced by `service`, with a new `middleware` world that imports and exports the handler. - -### `wasi:sockets` - -The seven P2 interfaces (`network`, `instance-network`, `tcp`, `tcp-create-socket`, `udp`, `udp-create-socket`, `ip-name-lookup`) collapse to a unified `types` interface containing both `tcp-socket` and `udp-socket` resources, plus `ip-name-lookup`. The `network` resource is removed entirely; network access is now granted at the world level. `start-*` / `finish-*` pairs collapse as described above. TCP `listen` returns `stream` directly instead of requiring a separate `accept` call. - -### `wasi:filesystem` - -Most `descriptor` methods are `async func`. Streaming reads and writes use the stream-plus-future pattern; `read-directory` returns `stream`. The `error-code` enum gains a catch-all `other(option)` variant. - -### `wasi:cli` - -stdin, stdout, and stderr use `stream` with the stream-plus-future pattern. `run` becomes `async func`. A shared `wasi:cli/types` interface carries an `error-code` variant. The `exit-with-code` function stabilizes alongside `exit`. - -### `wasi:clocks` - -`wall-clock` is renamed to `system-clock`, and `datetime` is renamed to `instant`. The `instant` record uses `s64` seconds (instead of `u64`), supporting pre-epoch timestamps. `subscribe-instant` and `subscribe-duration` are replaced by `wait-until` and `wait-for` `async func`s. - -### `wasi:random` +The `proxy` world is replaced by `service`, and a new `middleware` world both imports and exports the handler. +- **`wasi:sockets` drops its `network` resource.** Network access is granted at the world level instead of being threaded through every `bind`, `connect`, and DNS lookup. The seven P2 socket interfaces consolidate into one `types` interface plus `ip-name-lookup`, and TCP `listen` returns `stream` directly instead of requiring a separate `accept` loop. -The `len` parameter is renamed to `max-len` on `get-random-bytes` and `get-insecure-random-bytes`. Implementations may now return fewer bytes than requested, so callers should loop. +Smaller per-interface changes — filesystem methods becoming `async func`, the `wasi:clocks` rename pass (`wall-clock` → `system-clock`, `datetime` → `instant`), the `max-len` rename in `wasi:random`, the new shared `wasi:cli/types` interface — are documented in the WASI.dev page linked above. ## Tooling requirements From c91de0602ad0507620ef8b0534a332a8ca6b6eef Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Tue, 16 Jun 2026 14:46:17 -0400 Subject: [PATCH 05/23] docs: rename WASI P2/P3 to WASI 0.2/0.3 in design pages Per WASI maintainer guidance, refer to the WASI specifications as "WASI 0.2" and "WASI 0.3" in prose, including page titles, section headings, the migration guide's mapping table, and the new component-model-concepts and SUMMARY entries. Filename and URL slugs (migrating-to-p3.md, wasi-p3 in wasi.dev URLs) are unchanged. Signed-off-by: Eric Gregory --- component-model/src/SUMMARY.md | 2 +- component-model/src/design/async.md | 8 +-- .../src/design/component-model-concepts.md | 2 +- component-model/src/design/migrating-to-p3.md | 52 +++++++++---------- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/component-model/src/SUMMARY.md b/component-model/src/SUMMARY.md index 98c1839c..90cbb740 100644 --- a/component-model/src/SUMMARY.md +++ b/component-model/src/SUMMARY.md @@ -14,7 +14,7 @@ - [Async, Streams, and Futures](./design/async.md) - [WIT By Example](./design/wit-example.md) - [WIT Reference](./design/wit.md) -- [Migrating from WASI P2 to WASI P3](./design/migrating-to-p3.md) +- [Migrating from WASI 0.2 to WASI 0.3](./design/migrating-to-p3.md) # Using WebAssembly Components diff --git a/component-model/src/design/async.md b/component-model/src/design/async.md index a5616c0e..54084e53 100644 --- a/component-model/src/design/async.md +++ b/component-model/src/design/async.md @@ -1,12 +1,12 @@ # Async, Streams, and Futures -WASI P3 is built on three new Canonical ABI primitives in the Component Model: `async func`, `stream`, and `future`. Together, they let interfaces express asynchronous operations that compose across component boundaries. +WASI 0.3 is built on three new Canonical ABI primitives in the Component Model: `async func`, `stream`, and `future`. Together, they let interfaces express asynchronous operations that compose across component boundaries. -For migration mechanics (e.g., how a WASI P2 component maps onto these primitives) see [Migrating from WASI P2 to WASI P3](./migrating-to-p3.md). For the WASI release view, including the full per-interface diff, see [WASI P3](https://wasi.dev/releases/wasi-p3) on WASI.dev. This page focuses on the Component Model concepts themselves. +For migration mechanics (e.g., how a WASI 0.2 component maps onto these primitives) see [Migrating from WASI 0.2 to WASI 0.3](./migrating-to-p3.md). For the WASI release view, including the full per-interface diff, see [WASI 0.3](https://wasi.dev/releases/wasi-p3) on WASI.dev. This page focuses on the Component Model concepts themselves. ## Native async -The Component Model's Canonical ABI defines how typed values cross component boundaries. Until WASI P3, that vocabulary had no notion of suspension or asynchronous completion; every interface call returned synchronously, and asynchronous I/O was modeled with resources (`pollable` for readiness, `input-stream` and `output-stream` for byte channels) scoped to whichever component obtained them. +The Component Model's Canonical ABI defines how typed values cross component boundaries. Until WASI 0.3, that vocabulary had no notion of suspension or asynchronous completion; every interface call returned synchronously, and asynchronous I/O was modeled with resources (`pollable` for readiness, `input-stream` and `output-stream` for byte channels) scoped to whichever component obtained them. That arrangement holds up for two-party interactions, but it falters once components are composed in a chain. If a component awaits work that another component delegates further, the readiness signal has to travel back up the chain. When readiness is expressed as a resource scoped to a single component, the intermediate component is stuck running an event loop purely to forward the wake-up to its caller; the runtime cannot help, because the resource doesn't live in a place the runtime can reach across. This is sometimes called the **sandwich problem**: an async vocabulary that describes a single hop just fine but cannot propagate readiness past one. @@ -40,7 +40,7 @@ A typed handle for a single value that will become available later. Like `stream write-via-stream: func(data: stream) -> future>; ``` -## How the primitives work in WASI P3 +## How the primitives work in WASI 0.3 ### Stream plus terminal future diff --git a/component-model/src/design/component-model-concepts.md b/component-model/src/design/component-model-concepts.md index 1d88fe18..940d881b 100644 --- a/component-model/src/design/component-model-concepts.md +++ b/component-model/src/design/component-model-concepts.md @@ -67,7 +67,7 @@ and a `proxy` world that depends on `imports`. ### Async, Streams, and Futures -The Component Model includes [`async func`, `stream`, and `future`](./async.md) as native Canonical ABI primitives, introduced alongside WASI P3. Together, they let interfaces express asynchronous operations that compose across component boundaries. +The Component Model includes [`async func`, `stream`, and `future`](./async.md) as native Canonical ABI primitives, introduced alongside WASI 0.3. Together, they let interfaces express asynchronous operations that compose across component boundaries. ### Platforms diff --git a/component-model/src/design/migrating-to-p3.md b/component-model/src/design/migrating-to-p3.md index 24cec9eb..ea16753d 100644 --- a/component-model/src/design/migrating-to-p3.md +++ b/component-model/src/design/migrating-to-p3.md @@ -1,22 +1,22 @@ -# Migrating from WASI P2 to WASI P3 +# Migrating from WASI 0.2 to WASI 0.3 -WASI P3 reshapes WASI's interfaces around the [native async primitives](./async.md) `async func`, `stream`, and `future`. Most of the changes in `wasi:cli`, `wasi:http`, `wasi:filesystem`, and `wasi:sockets` are consequences of moving to these primitives. +WASI 0.3 reshapes WASI's interfaces around the [native async primitives](./async.md) `async func`, `stream`, and `future`. Most of the changes in `wasi:cli`, `wasi:http`, `wasi:filesystem`, and `wasi:sockets` are consequences of moving to these primitives. -This page covers the mapping between concepts in WASI P2 and WASI P3. For a WIT-level comparison of every WASI P3 interface, see [WASI P3](https://wasi.dev/releases/wasi-p3) on WASI.dev. +This page covers the mapping between concepts in WASI 0.2 and WASI 0.3. For a WIT-level comparison of every WASI 0.3 interface, see [WASI 0.3](https://wasi.dev/releases/wasi-p3) on WASI.dev. ## Do you need to migrate? -Not immediately. WASI P3 runtimes can polyfill P2 by mapping P2 imports onto native P3 primitives at the host boundary, and Wasmtime's `wasmtime serve` already runs both P3 and P2 components from the same binary, dispatching per component. Migration is the right call when you want: +Not immediately. WASI 0.3 runtimes can polyfill 0.2 by mapping 0.2 imports onto native 0.3 primitives at the host boundary, and Wasmtime's `wasmtime serve` already runs both 0.3 and 0.2 components from the same binary, dispatching per component. Migration is the right call when you want: - Composable async across component boundaries (the [sandwich problem](./async.md#native-async) goes away). - The newer interface shapes — in particular, `wasi:http`'s collapse of nine resources down to two. -- First-class support in P3-targeted toolchains as they continue to land. +- First-class support in 0.3-targeted toolchains as they continue to land. ## Concept mapping -WASI P3 replaces every `wasi:io` resource with a Canonical ABI primitive. The translation is mostly one-to-one: +WASI 0.3 replaces every `wasi:io` resource with a Canonical ABI primitive. The translation is mostly one-to-one: -| WASI P2 (`wasi:io`) | WASI P3 (Component Model) | +| WASI 0.2 (`wasi:io`) | WASI 0.3 (Component Model) | | -------------------------------- | ---------------------------------------- | | `resource pollable` | `future` | | `resource input-stream` | `stream` | @@ -29,40 +29,40 @@ WASI P3 replaces every `wasi:io` resource with a Canonical ABI primitive. The tr ### Stream-plus-future for reads -A P2 read call returned a single `input-stream` resource and surfaced terminal errors only as you consumed it. P3 splits those concerns: the call returns a `stream` for the data and a `future>` for the outcome, packed into a tuple. +A 0.2 read call returned a single `input-stream` resource and surfaced terminal errors only as you consumed it. 0.3 splits those concerns: the call returns a `stream` for the data and a `future>` for the outcome, packed into a tuple. ```wit -// WASI P2 (filesystem read) +// WASI 0.2 (filesystem read) read-via-stream: func(offset: filesize) -> result; -// WASI P3 (filesystem read) +// WASI 0.3 (filesystem read) read-via-stream: func(offset: filesize) -> tuple, future>>; ``` -In P3 the caller does not have to drain the stream to learn whether the read finished cleanly; the future resolves either way. +In 0.3 the caller does not have to drain the stream to learn whether the read finished cleanly; the future resolves either way. ### Write-direction flip -P2 write paths handed a guest some host-owned resource (an `output-stream`) and let the guest push bytes into it. P3 inverts that: the guest supplies the data as a `stream` value, and the host returns a `future` that resolves once it has finished consuming the stream. +0.2 write paths handed a guest some host-owned resource (an `output-stream`) and let the guest push bytes into it. 0.3 inverts that: the guest supplies the data as a `stream` value, and the host returns a `future` that resolves once it has finished consuming the stream. ```wit -// WASI P2: receive an output-stream resource, write into it +// WASI 0.2: receive an output-stream resource, write into it get-stdout: func() -> output-stream; -// WASI P3: pass a stream value in, receive a completion future +// WASI 0.3: pass a stream value in, receive a completion future write-via-stream: func(data: stream) -> future>; ``` ### Two-step calls collapsed -P2 modeled operations that could suspend as a `start-foo` / `finish-foo` pair, with a `pollable` for readiness in between. P3 collapses each pair into a single call: +0.2 modeled operations that could suspend as a `start-foo` / `finish-foo` pair, with a `pollable` for readiness in between. 0.3 collapses each pair into a single call: ```wit -// WASI P2 +// WASI 0.2 start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; finish-connect: func() -> result, error-code>; -// WASI P3 +// WASI 0.3 connect: async func(remote-address: ip-socket-address) -> result<_, error-code>; ``` @@ -70,21 +70,21 @@ The collapsed call is `async func` when the operation needs to suspend in the ho ## Interface highlights -The complete per-interface diff lives on [WASI P3](https://wasi.dev/releases/wasi-p3#what-changed-in-each-interface) at WASI.dev. The three changes most likely to drive migration work are: +The complete per-interface diff lives on [WASI 0.3](https://wasi.dev/releases/wasi-p3#what-changed-in-each-interface) at WASI.dev. The three changes most likely to drive migration work are: - **`wasi:io` is gone.** The package has no 0.3.0 release. Every resource it exposed (`pollable`, `input-stream`, `output-stream`) is replaced by a Component Model primitive, per the [concept mapping](#concept-mapping) above. - **`wasi:http` collapses from nine resources to two.** The incoming/outgoing × request/response/body matrix plus `future-trailers`, `future-incoming-response`, and `response-outparam` all become `request` and `response`, with `stream` bodies and a `future` for trailers. The handler is now an `async func`: ```wit -// WASI P2 +// WASI 0.2 handle: func(request: incoming-request, response-out: response-outparam); -// WASI P3 +// WASI 0.3 handle: async func(request: request) -> result; ``` The `proxy` world is replaced by `service`, and a new `middleware` world both imports and exports the handler. -- **`wasi:sockets` drops its `network` resource.** Network access is granted at the world level instead of being threaded through every `bind`, `connect`, and DNS lookup. The seven P2 socket interfaces consolidate into one `types` interface plus `ip-name-lookup`, and TCP `listen` returns `stream` directly instead of requiring a separate `accept` loop. +- **`wasi:sockets` drops its `network` resource.** Network access is granted at the world level instead of being threaded through every `bind`, `connect`, and DNS lookup. The seven 0.2 socket interfaces consolidate into one `types` interface plus `ip-name-lookup`, and TCP `listen` returns `stream` directly instead of requiring a separate `accept` loop. Smaller per-interface changes — filesystem methods becoming `async func`, the `wasi:clocks` rename pass (`wall-clock` → `system-clock`, `datetime` → `instant`), the `max-len` rename in `wasi:random`, the new shared `wasi:cli/types` interface — are documented in the WASI.dev page linked above. @@ -93,15 +93,15 @@ Smaller per-interface changes — filesystem methods becoming `async func`, the | Tool | Minimum | Notes | | ------------- | --------------------------------------------------------------- | ------------------------------------------------------------------- | | Wasmtime | 43+ for `wasmtime run`; 44+ for `wasmtime serve` | Enable with `-Sp3 -W component-model-async=y`. | -| `wit-bindgen` | 0.46+ | Use the `async` feature for P3 binding generation. | -| jco | latest | P3 host bindings ship in the `preview3-shim` package. | +| `wit-bindgen` | 0.46+ | Use the `async` feature for 0.3 binding generation. | +| jco | latest | 0.3 host bindings ship in the `preview3-shim` package. | | `wkg` | 0.15+ | Required to fetch `wasi:cli@0.3.0-rc-2026-03-15` and related packages. | -| Rust | nightly | Current stable bundles a `wasm-component-ld` too old for P3 outputs of `wit-bindgen` 0.58. | +| Rust | nightly | Current stable bundles a `wasm-component-ld` too old for 0.3 outputs of `wit-bindgen` 0.58. | > **Version pinning.** As of WASI 0.3.0's release on 2026-06-11, Wasmtime and `wit-bindgen` still vendor the `0.3.0-rc-2026-03-15` snapshot of the WIT. Components pinning to the published `0.3.0` will fail to instantiate against current Wasmtime; use the RC pin until those tools refresh. ## Further reading - [Async, Streams, and Futures](./async.md) — the conceptual foundation -- [Creating Runnable Components in Rust](../language-support/creating-runnable-components/rust.md) — worked Rust example with the P3 `async fn run()` pattern -- [WASI P3](https://wasi.dev/releases/wasi-p3) on WASI.dev — full WIT-level diff per interface +- [Creating Runnable Components in Rust](../language-support/creating-runnable-components/rust.md) — worked Rust example with the 0.3 `async fn run()` pattern +- [WASI 0.3](https://wasi.dev/releases/wasi-p3) on WASI.dev — full WIT-level diff per interface From 35629a7d697f906646adb69675c6dc13505d6471 Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Tue, 16 Jun 2026 14:52:31 -0400 Subject: [PATCH 06/23] docs(design/migrating-to-p3): fix awkward bare-decimal references The earlier WASI P2/P3 -> WASI 0.2/0.3 rename left several sentences starting with or hinging on a bare "0.2" or "0.3", which reads awkwardly. Prefix those with "WASI" so the version reads as a noun phrase. The tooling-requirements table cells stay terse. Signed-off-by: Eric Gregory --- component-model/src/design/migrating-to-p3.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/component-model/src/design/migrating-to-p3.md b/component-model/src/design/migrating-to-p3.md index ea16753d..7ba61c0b 100644 --- a/component-model/src/design/migrating-to-p3.md +++ b/component-model/src/design/migrating-to-p3.md @@ -29,7 +29,7 @@ WASI 0.3 replaces every `wasi:io` resource with a Canonical ABI primitive. The t ### Stream-plus-future for reads -A 0.2 read call returned a single `input-stream` resource and surfaced terminal errors only as you consumed it. 0.3 splits those concerns: the call returns a `stream` for the data and a `future>` for the outcome, packed into a tuple. +A WASI 0.2 read call returned a single `input-stream` resource and surfaced terminal errors only as you consumed it. WASI 0.3 splits those concerns: the call returns a `stream` for the data and a `future>` for the outcome, packed into a tuple. ```wit // WASI 0.2 (filesystem read) @@ -39,11 +39,11 @@ read-via-stream: func(offset: filesize) -> result; read-via-stream: func(offset: filesize) -> tuple, future>>; ``` -In 0.3 the caller does not have to drain the stream to learn whether the read finished cleanly; the future resolves either way. +In WASI 0.3, the caller does not have to drain the stream to learn whether the read finished cleanly; the future resolves either way. ### Write-direction flip -0.2 write paths handed a guest some host-owned resource (an `output-stream`) and let the guest push bytes into it. 0.3 inverts that: the guest supplies the data as a `stream` value, and the host returns a `future` that resolves once it has finished consuming the stream. +WASI 0.2 write paths handed a guest some host-owned resource (an `output-stream`) and let the guest push bytes into it. WASI 0.3 inverts that: the guest supplies the data as a `stream` value, and the host returns a `future` that resolves once it has finished consuming the stream. ```wit // WASI 0.2: receive an output-stream resource, write into it @@ -55,7 +55,7 @@ write-via-stream: func(data: stream) -> future>; ### Two-step calls collapsed -0.2 modeled operations that could suspend as a `start-foo` / `finish-foo` pair, with a `pollable` for readiness in between. 0.3 collapses each pair into a single call: +WASI 0.2 modeled operations that could suspend as a `start-foo` / `finish-foo` pair, with a `pollable` for readiness in between. WASI 0.3 collapses each pair into a single call: ```wit // WASI 0.2 @@ -84,7 +84,7 @@ handle: async func(request: request) -> result; ``` The `proxy` world is replaced by `service`, and a new `middleware` world both imports and exports the handler. -- **`wasi:sockets` drops its `network` resource.** Network access is granted at the world level instead of being threaded through every `bind`, `connect`, and DNS lookup. The seven 0.2 socket interfaces consolidate into one `types` interface plus `ip-name-lookup`, and TCP `listen` returns `stream` directly instead of requiring a separate `accept` loop. +- **`wasi:sockets` drops its `network` resource.** Network access is granted at the world level instead of being threaded through every `bind`, `connect`, and DNS lookup. The seven WASI 0.2 socket interfaces consolidate into one `types` interface plus `ip-name-lookup`, and TCP `listen` returns `stream` directly instead of requiring a separate `accept` loop. Smaller per-interface changes — filesystem methods becoming `async func`, the `wasi:clocks` rename pass (`wall-clock` → `system-clock`, `datetime` → `instant`), the `max-len` rename in `wasi:random`, the new shared `wasi:cli/types` interface — are documented in the WASI.dev page linked above. From ffec37a16da120dc20e27044504e934fc42bd737 Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Thu, 2 Jul 2026 10:05:29 -0400 Subject: [PATCH 07/23] Update component-model/src/design/async.md Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/design/async.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/component-model/src/design/async.md b/component-model/src/design/async.md index 54084e53..4341c16d 100644 --- a/component-model/src/design/async.md +++ b/component-model/src/design/async.md @@ -1,6 +1,11 @@ # Async, Streams, and Futures -WASI 0.3 is built on three new Canonical ABI primitives in the Component Model: `async func`, `stream`, and `future`. Together, they let interfaces express asynchronous operations that compose across component boundaries. +WASI 0.3 adds new Canonical ABI primitives to the Component Model that enable async functionality. Components that target WASI 0.3 can use the new features in their WIT files: +* `async func` +* `stream` +* `future` + +These new types let interfaces express asynchronous operations that compose across component boundaries. For migration mechanics (e.g., how a WASI 0.2 component maps onto these primitives) see [Migrating from WASI 0.2 to WASI 0.3](./migrating-to-p3.md). For the WASI release view, including the full per-interface diff, see [WASI 0.3](https://wasi.dev/releases/wasi-p3) on WASI.dev. This page focuses on the Component Model concepts themselves. From c56583f21060a3d894824c450ca9339fb41d42e8 Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Thu, 2 Jul 2026 10:06:12 -0400 Subject: [PATCH 08/23] Update component-model/src/design/async.md Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/design/async.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/component-model/src/design/async.md b/component-model/src/design/async.md index 4341c16d..541a8107 100644 --- a/component-model/src/design/async.md +++ b/component-model/src/design/async.md @@ -7,7 +7,11 @@ WASI 0.3 adds new Canonical ABI primitives to the Component Model that enable as These new types let interfaces express asynchronous operations that compose across component boundaries. -For migration mechanics (e.g., how a WASI 0.2 component maps onto these primitives) see [Migrating from WASI 0.2 to WASI 0.3](./migrating-to-p3.md). For the WASI release view, including the full per-interface diff, see [WASI 0.3](https://wasi.dev/releases/wasi-p3) on WASI.dev. This page focuses on the Component Model concepts themselves. +For migration mechanics (e.g., how a WASI 0.2 component maps onto these primitives) see [Migrating from WASI 0.2 to WASI 0.3](./migrating-to-p3.md). + +For the a closer look at WASI 0.3 release, including a full per-interface diff, see [WASI 0.3](https://wasi.dev/releases/wasi-p3) on WASI.dev. + +This page focuses on the Component Model concepts themselves. ## Native async From 3b498825d0df9c030478606e56bd51618973bf6c Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Thu, 2 Jul 2026 10:06:26 -0400 Subject: [PATCH 09/23] Update component-model/src/design/async.md Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/design/async.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/component-model/src/design/async.md b/component-model/src/design/async.md index 541a8107..94fc69f7 100644 --- a/component-model/src/design/async.md +++ b/component-model/src/design/async.md @@ -19,7 +19,9 @@ The Component Model's Canonical ABI defines how typed values cross component bou That arrangement holds up for two-party interactions, but it falters once components are composed in a chain. If a component awaits work that another component delegates further, the readiness signal has to travel back up the chain. When readiness is expressed as a resource scoped to a single component, the intermediate component is stuck running an event loop purely to forward the wake-up to its caller; the runtime cannot help, because the resource doesn't live in a place the runtime can reach across. This is sometimes called the **sandwich problem**: an async vocabulary that describes a single hop just fine but cannot propagate readiness past one. -Native primitives close the gap. With `async func`, `stream`, and `future` in the Canonical ABI, scheduling and wake-up propagation become the runtime's job rather than any individual component's. Components can pass futures and streams along the chain without keeping their own event loops running to relay readiness. +Native async primitives help close this expressivity gap. With updated Component ABI mechanics that enable `async func`, `stream`, and `future` available at the WIT level, scheduling and wake-up propagation become the runtime's job rather than any individual component's. + +Components can pass futures and streams along without keeping their own event loops running to relay readiness, as was necessary with WASI 0.2. ## The three primitives From 2695ed4ed5587476d2fd81bb2d4ca7065a8366cf Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Thu, 2 Jul 2026 10:06:37 -0400 Subject: [PATCH 10/23] Update component-model/src/design/async.md Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/design/async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component-model/src/design/async.md b/component-model/src/design/async.md index 94fc69f7..da57afe9 100644 --- a/component-model/src/design/async.md +++ b/component-model/src/design/async.md @@ -23,7 +23,7 @@ Native async primitives help close this expressivity gap. With updated Component Components can pass futures and streams along without keeping their own event loops running to relay readiness, as was necessary with WASI 0.2. -## The three primitives +## Async functions, Streams, and Futures ### `async func` From edd2d2d0a3ac72fcc38971e253359bb0ca05d65f Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Thu, 2 Jul 2026 10:08:26 -0400 Subject: [PATCH 11/23] Apply suggestion from @vados-cosmonic Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/design/async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component-model/src/design/async.md b/component-model/src/design/async.md index da57afe9..19c99ff5 100644 --- a/component-model/src/design/async.md +++ b/component-model/src/design/async.md @@ -13,7 +13,7 @@ For the a closer look at WASI 0.3 release, including a full per-interface diff, This page focuses on the Component Model concepts themselves. -## Native async +## The async problem that WASI 0.3 solves The Component Model's Canonical ABI defines how typed values cross component boundaries. Until WASI 0.3, that vocabulary had no notion of suspension or asynchronous completion; every interface call returned synchronously, and asynchronous I/O was modeled with resources (`pollable` for readiness, `input-stream` and `output-stream` for byte channels) scoped to whichever component obtained them. From 84aab9e6a8ccbdf062eaff093acf0e3337dad2e0 Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Thu, 2 Jul 2026 10:08:48 -0400 Subject: [PATCH 12/23] Apply suggestion from @vados-cosmonic Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/design/async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component-model/src/design/async.md b/component-model/src/design/async.md index 19c99ff5..311a0f19 100644 --- a/component-model/src/design/async.md +++ b/component-model/src/design/async.md @@ -1,4 +1,4 @@ -# Async, Streams, and Futures +# Native Async with WASI 0.3 WASI 0.3 adds new Canonical ABI primitives to the Component Model that enable async functionality. Components that target WASI 0.3 can use the new features in their WIT files: * `async func` From 707b97e87fb2caaac2160f93880f6b9296e1b360 Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Thu, 2 Jul 2026 10:09:38 -0400 Subject: [PATCH 13/23] Apply suggestion from @vados-cosmonic Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/design/async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component-model/src/design/async.md b/component-model/src/design/async.md index 311a0f19..2ce8e44a 100644 --- a/component-model/src/design/async.md +++ b/component-model/src/design/async.md @@ -25,7 +25,7 @@ Components can pass futures and streams along without keeping their own event lo ## Async functions, Streams, and Futures -### `async func` +### Async Functions (`async func`) A WIT function declared `async` tells the runtime that the call may suspend before producing its result. The Canonical ABI handles the suspension and resumption; the guest doesn't see a `pollable`, and the host doesn't see a polling loop. From c041079322c6ac1d68b4b3d25c33e5395820ab09 Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Thu, 2 Jul 2026 10:10:01 -0400 Subject: [PATCH 14/23] Apply suggestion from @vados-cosmonic Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/design/async.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/component-model/src/design/async.md b/component-model/src/design/async.md index 2ce8e44a..8da4d7af 100644 --- a/component-model/src/design/async.md +++ b/component-model/src/design/async.md @@ -29,9 +29,9 @@ Components can pass futures and streams along without keeping their own event lo A WIT function declared `async` tells the runtime that the call may suspend before producing its result. The Canonical ABI handles the suspension and resumption; the guest doesn't see a `pollable`, and the host doesn't see a polling loop. -```wit -handle: async func(request: request) -> result; -``` +```diff +- handle: func(request: request) -> result; ++ handle: async func(request: request) -> result; Code generated from the WIT picks up each language's natural async idiom: `async fn` in Rust, a `Promise`-returning function in JavaScript, a coroutine in Python. From 120f4569e72c5b58b7501b4d1c8b31cc088ffb26 Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Thu, 2 Jul 2026 10:10:16 -0400 Subject: [PATCH 15/23] Apply suggestion from @vados-cosmonic Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/design/async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component-model/src/design/async.md b/component-model/src/design/async.md index 8da4d7af..24c59b33 100644 --- a/component-model/src/design/async.md +++ b/component-model/src/design/async.md @@ -35,7 +35,7 @@ A WIT function declared `async` tells the runtime that the call may suspend befo Code generated from the WIT picks up each language's natural async idiom: `async fn` in Rust, a `Promise`-returning function in JavaScript, a coroutine in Python. -### `stream` +### Streams (`stream`) A typed, asynchronous channel for a sequence of `T` values. Crucially, `stream` is a Canonical ABI *value*, not a resource: it can be returned from a call, accepted as a parameter, and handed from one component to another without giving up ownership of the underlying buffer. The same value can also be passed straight through a middle component without that component having to relay any wake-ups. From 9860cc1d80e8c4e8b927745fc52345cb7ed13640 Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Thu, 2 Jul 2026 10:10:35 -0400 Subject: [PATCH 16/23] Apply suggestion from @vados-cosmonic Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/design/async.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/component-model/src/design/async.md b/component-model/src/design/async.md index 24c59b33..3b742e84 100644 --- a/component-model/src/design/async.md +++ b/component-model/src/design/async.md @@ -37,7 +37,9 @@ Code generated from the WIT picks up each language's natural async idiom: `async ### Streams (`stream`) -A typed, asynchronous channel for a sequence of `T` values. Crucially, `stream` is a Canonical ABI *value*, not a resource: it can be returned from a call, accepted as a parameter, and handed from one component to another without giving up ownership of the underlying buffer. The same value can also be passed straight through a middle component without that component having to relay any wake-ups. +A typed, asynchronous channel for a sequence of `T` values. Crucially, `stream` is a Canonical ABI *value*, not a resource (as opposed to WASI 0.2) -- it can be returned from a call, accepted as a parameter, and handed from one component to another without giving up ownership of the underlying buffer. + +The same value can also be passed straight through a one or more components without those component(s) having to relay any wake-ups. ```wit read-via-stream: func() -> tuple, future>>; From 58aabb2db197ac1606748ea153c4b114d5b1bebc Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Thu, 2 Jul 2026 10:10:57 -0400 Subject: [PATCH 17/23] Apply suggestion from @vados-cosmonic Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/design/async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component-model/src/design/async.md b/component-model/src/design/async.md index 3b742e84..e3127746 100644 --- a/component-model/src/design/async.md +++ b/component-model/src/design/async.md @@ -45,7 +45,7 @@ The same value can also be passed straight through a one or more components with read-via-stream: func() -> tuple, future>>; ``` -### `future` +### Futures (`future`) A typed handle for a single value that will become available later. Like `stream`, `future` is a value rather than a resource, so it crosses component boundaries the same way a primitive does. A function returning `future` does not block; the caller awaits the result when it needs it. From 2275fbf8627bd6e8ee904aa73483cf3e75ceaefd Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Thu, 2 Jul 2026 10:11:16 -0400 Subject: [PATCH 18/23] Apply suggestion from @vados-cosmonic Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/design/async.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/component-model/src/design/async.md b/component-model/src/design/async.md index e3127746..6a5f3637 100644 --- a/component-model/src/design/async.md +++ b/component-model/src/design/async.md @@ -47,7 +47,9 @@ read-via-stream: func() -> tuple, future>>; ### Futures (`future`) -A typed handle for a single value that will become available later. Like `stream`, `future` is a value rather than a resource, so it crosses component boundaries the same way a primitive does. A function returning `future` does not block; the caller awaits the result when it needs it. +A typed handle for a single value that will become available later. Like `stream`, `future` is a value rather than a resource, so it crosses component boundaries the same way a primitive does. + +Note that synchronous functions which return `future`s *cannot* block; the caller can awaits the result when it needs it. ```wit write-via-stream: func(data: stream) -> future>; From 63df4c105a0ec31938b4e7bb2133a5e9bc4447e4 Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Thu, 2 Jul 2026 10:11:42 -0400 Subject: [PATCH 19/23] Apply suggestion from @vados-cosmonic Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/design/component-model-concepts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component-model/src/design/component-model-concepts.md b/component-model/src/design/component-model-concepts.md index 940d881b..dd9c4f3b 100644 --- a/component-model/src/design/component-model-concepts.md +++ b/component-model/src/design/component-model-concepts.md @@ -67,7 +67,7 @@ and a `proxy` world that depends on `imports`. ### Async, Streams, and Futures -The Component Model includes [`async func`, `stream`, and `future`](./async.md) as native Canonical ABI primitives, introduced alongside WASI 0.3. Together, they let interfaces express asynchronous operations that compose across component boundaries. +New Component Model primitives that enable use of [`async func`, `stream`, and `future`](./async.md), were introduced alongside WASI 0.3. Together, they let interfaces express asynchronous operations that compose across component boundaries. ### Platforms From 2d0f79407226d2cecf9eb5d477346128373ba682 Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Thu, 2 Jul 2026 10:12:00 -0400 Subject: [PATCH 20/23] Apply suggestion from @vados-cosmonic Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/design/async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component-model/src/design/async.md b/component-model/src/design/async.md index 6a5f3637..9a6368a5 100644 --- a/component-model/src/design/async.md +++ b/component-model/src/design/async.md @@ -55,7 +55,7 @@ Note that synchronous functions which return `future`s *cannot* block; the ca write-via-stream: func(data: stream) -> future>; ``` -## How the primitives work in WASI 0.3 +## A look at async patterns in WASI 0.3 ### Stream plus terminal future From d43e89e0c19266bd7168729ddff1078bd7540a5d Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Thu, 2 Jul 2026 10:12:47 -0400 Subject: [PATCH 21/23] Apply suggestion from @vados-cosmonic Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/design/migrating-to-p3.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/component-model/src/design/migrating-to-p3.md b/component-model/src/design/migrating-to-p3.md index 7ba61c0b..b4a379d5 100644 --- a/component-model/src/design/migrating-to-p3.md +++ b/component-model/src/design/migrating-to-p3.md @@ -6,7 +6,9 @@ This page covers the mapping between concepts in WASI 0.2 and WASI 0.3. For a WI ## Do you need to migrate? -Not immediately. WASI 0.3 runtimes can polyfill 0.2 by mapping 0.2 imports onto native 0.3 primitives at the host boundary, and Wasmtime's `wasmtime serve` already runs both 0.3 and 0.2 components from the same binary, dispatching per component. Migration is the right call when you want: +Not immediately -- WASI 0.2 can be used in hosts just as before, WASI 0.3 is a purely additive change. + +Separately, WASI 0.3 runtimes can polyfill 0.2 by mapping 0.2 imports onto native 0.3 primitives at the host boundary, and Wasmtime's `wasmtime serve` already runs both 0.3 and 0.2 components from the same binary, dispatching per component. Migration is the right call when you want: - Composable async across component boundaries (the [sandwich problem](./async.md#native-async) goes away). - The newer interface shapes — in particular, `wasi:http`'s collapse of nine resources down to two. From 3421c7a4d0a6832c3953b8f7aee35500bd838cef Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Thu, 2 Jul 2026 10:38:55 -0400 Subject: [PATCH 22/23] docs(design): address review feedback on async and migration pages - Add a note that WASI 0.3 builds on 0.2 and the two can coexist - Link the read-via-stream and write-via-stream examples to their in-situ definitions in wasi-filesystem - Update the sandwich-problem anchor to match the renamed heading - Update the tooling table for Wasmtime 46, which enables WASI 0.3 and component-model-async by default and vendors 0.3.0 stable - Close an open diff fence, fix a couple of typos in the async page --- component-model/src/design/async.md | 14 +++++++++----- component-model/src/design/migrating-to-p3.md | 8 +++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/component-model/src/design/async.md b/component-model/src/design/async.md index 9a6368a5..9ec67c1a 100644 --- a/component-model/src/design/async.md +++ b/component-model/src/design/async.md @@ -9,10 +9,13 @@ These new types let interfaces express asynchronous operations that compose acro For migration mechanics (e.g., how a WASI 0.2 component maps onto these primitives) see [Migrating from WASI 0.2 to WASI 0.3](./migrating-to-p3.md). -For the a closer look at WASI 0.3 release, including a full per-interface diff, see [WASI 0.3](https://wasi.dev/releases/wasi-p3) on WASI.dev. +For a closer look at the WASI 0.3 release, including a full per-interface diff, see [WASI 0.3](https://wasi.dev/releases/wasi-p3) on WASI.dev. This page focuses on the Component Model concepts themselves. +> [!NOTE] +> WASI 0.3 builds on WASI 0.2 rather than replacing it. Runtimes can host both versions side by side, and a 0.3 host can polyfill 0.2 imports at the boundary, so applications can migrate incrementally as toolchains and dependencies land 0.3 support. + ## The async problem that WASI 0.3 solves The Component Model's Canonical ABI defines how typed values cross component boundaries. Until WASI 0.3, that vocabulary had no notion of suspension or asynchronous completion; every interface call returned synchronously, and asynchronous I/O was modeled with resources (`pollable` for readiness, `input-stream` and `output-stream` for byte channels) scoped to whichever component obtained them. @@ -32,6 +35,7 @@ A WIT function declared `async` tells the runtime that the call may suspend befo ```diff - handle: func(request: request) -> result; + handle: async func(request: request) -> result; +``` Code generated from the WIT picks up each language's natural async idiom: `async fn` in Rust, a `Promise`-returning function in JavaScript, a coroutine in Python. @@ -39,7 +43,7 @@ Code generated from the WIT picks up each language's natural async idiom: `async A typed, asynchronous channel for a sequence of `T` values. Crucially, `stream` is a Canonical ABI *value*, not a resource (as opposed to WASI 0.2) -- it can be returned from a call, accepted as a parameter, and handed from one component to another without giving up ownership of the underlying buffer. -The same value can also be passed straight through a one or more components without those component(s) having to relay any wake-ups. +The same value can also be passed straight through one or more intermediate components without those components having to relay any wake-ups. ```wit read-via-stream: func() -> tuple, future>>; @@ -49,7 +53,7 @@ read-via-stream: func() -> tuple, future>>; A typed handle for a single value that will become available later. Like `stream`, `future` is a value rather than a resource, so it crosses component boundaries the same way a primitive does. -Note that synchronous functions which return `future`s *cannot* block; the caller can awaits the result when it needs it. +Note that synchronous functions which return `future`s *cannot* block; the caller can await the result when it needs it. ```wit write-via-stream: func(data: stream) -> future>; @@ -59,7 +63,7 @@ write-via-stream: func(data: stream) -> future>; ### Stream plus terminal future -Reads return both a data channel and a completion handle, packed into a tuple: +Reads return both a data channel and a completion handle, packed into a tuple ([`read-via-stream`](https://github.com/WebAssembly/wasi-filesystem/blob/main/wit/types.wit#L308) in `wasi-filesystem`): ```wit read-via-stream: func() -> tuple, future>>; @@ -69,7 +73,7 @@ The two halves are independent. The caller can consume the stream eagerly, sampl ### Stream parameter, future return -Writes use the symmetric shape: the guest supplies the data as a `stream` parameter, and the host returns a `future` that resolves once it has consumed the stream. Stdout, stderr, filesystem writes, and TCP sends all follow this shape: +Writes use the symmetric shape: the guest supplies the data as a `stream` parameter, and the host returns a `future` that resolves once it has consumed the stream. Stdout, stderr, filesystem writes, and TCP sends all follow this shape ([`write-via-stream`](https://github.com/WebAssembly/wasi-filesystem/blob/main/wit/types.wit#L320) in `wasi-filesystem`): ```wit write-via-stream: func(data: stream) -> future>; diff --git a/component-model/src/design/migrating-to-p3.md b/component-model/src/design/migrating-to-p3.md index b4a379d5..112f29eb 100644 --- a/component-model/src/design/migrating-to-p3.md +++ b/component-model/src/design/migrating-to-p3.md @@ -10,7 +10,7 @@ Not immediately -- WASI 0.2 can be used in hosts just as before, WASI 0.3 is a p Separately, WASI 0.3 runtimes can polyfill 0.2 by mapping 0.2 imports onto native 0.3 primitives at the host boundary, and Wasmtime's `wasmtime serve` already runs both 0.3 and 0.2 components from the same binary, dispatching per component. Migration is the right call when you want: -- Composable async across component boundaries (the [sandwich problem](./async.md#native-async) goes away). +- Composable async across component boundaries (the [sandwich problem](./async.md#the-async-problem-that-wasi-03-solves) goes away). - The newer interface shapes — in particular, `wasi:http`'s collapse of nine resources down to two. - First-class support in 0.3-targeted toolchains as they continue to land. @@ -94,14 +94,12 @@ Smaller per-interface changes — filesystem methods becoming `async func`, the | Tool | Minimum | Notes | | ------------- | --------------------------------------------------------------- | ------------------------------------------------------------------- | -| Wasmtime | 43+ for `wasmtime run`; 44+ for `wasmtime serve` | Enable with `-Sp3 -W component-model-async=y`. | +| Wasmtime | 46+ | WASI 0.3 and `component-model-async` are on by default. | | `wit-bindgen` | 0.46+ | Use the `async` feature for 0.3 binding generation. | | jco | latest | 0.3 host bindings ship in the `preview3-shim` package. | -| `wkg` | 0.15+ | Required to fetch `wasi:cli@0.3.0-rc-2026-03-15` and related packages. | +| `wkg` | 0.15+ | Required to fetch `wasi:cli@0.3.0` and related packages. | | Rust | nightly | Current stable bundles a `wasm-component-ld` too old for 0.3 outputs of `wit-bindgen` 0.58. | -> **Version pinning.** As of WASI 0.3.0's release on 2026-06-11, Wasmtime and `wit-bindgen` still vendor the `0.3.0-rc-2026-03-15` snapshot of the WIT. Components pinning to the published `0.3.0` will fail to instantiate against current Wasmtime; use the RC pin until those tools refresh. - ## Further reading - [Async, Streams, and Futures](./async.md) — the conceptual foundation From c3ed7d340dd20ec234c14a58559bf4d38476b1b7 Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Tue, 16 Jun 2026 15:11:49 -0400 Subject: [PATCH 23/23] docs(design/wit): add stream, future, async func reference entries Extend the WIT Reference with the three primitives that the Component Model added for WASI 0.3: stream and future as new built-in types after Tuples, and async func as a new subsection of Functions. Each entry covers the WIT syntax with worked examples, notes the value-not-resource property of the new types, and cross-links to design/async.md for the underlying motivation. Signed-off-by: Eric Gregory --- component-model/src/design/wit.md | 64 +++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/component-model/src/design/wit.md b/component-model/src/design/wit.md index 47602e7b..572af257 100644 --- a/component-model/src/design/wit.md +++ b/component-model/src/design/wit.md @@ -196,6 +196,54 @@ tuple // An integer, then a string, then an integer This is similar to tuples in Rust or OCaml. +### Streams + +`stream` for any type `T` denotes an asynchronous, ordered sequence of values of type `T`. +Unlike a `list`, which contains all of its values at once, +a `stream` delivers values incrementally: +the producer pushes elements as they become available, +and the consumer receives them as they arrive. + +```wit +stream // a stream of bytes +stream // a stream of records +``` + +A `stream` is a Canonical ABI value, not a resource: +it can be returned from a function, accepted as a parameter, +and passed across component boundaries the same way a primitive type is. +A middle component can also hand a stream along without consuming it. + +Streams were added for WASI 0.3 and are most often paired with a [`future`](#futures) +that signals when the stream has terminated; +the stream-plus-future tuple shape appears throughout +[`wasi:filesystem`](https://github.com/WebAssembly/WASI/tree/main/proposals/filesystem), +[`wasi:cli`](https://github.com/WebAssembly/WASI/tree/main/proposals/cli), +and [`wasi:sockets`](https://github.com/WebAssembly/WASI/tree/main/proposals/sockets). + +For the underlying motivation, see [Async, Streams, and Futures](./async.md). + +### Futures + +`future` for any type `T` is a typed handle for a single value of type `T` +that will become available later. +A function returning a `future` does not block on the value being produced; +the caller awaits the future when it needs the value. + +```wit +future // a future that will resolve to a u32 +future> // a future that will resolve to either a response or an error +``` + +Like `stream`, `future` is a Canonical ABI value rather than a resource, +so it travels across component boundaries the same way primitives do. + +Futures were added for WASI 0.3. +They are similar to a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) in JavaScript +or a [`std::future::Future`](https://doc.rust-lang.org/std/future/trait.Future.html) in Rust. + +For the underlying motivation, see [Async, Streams, and Futures](./async.md). + ## User-defined types New domain-specific types can be defined within an `interface` or `world`. @@ -393,6 +441,22 @@ get-customers-paged: func(cont: continuation-token) -> tuple, con A function can be declared inside an [interface](#interfaces), or can be declared as an import or export in a [world](#worlds). +### Asynchronous functions + +A function can be declared `async`, indicating that the call may suspend before producing its result: + +```wit +// An async function returning a result +handle: async func(request: request) -> result; +``` + +The runtime owns the scheduling; from the WIT perspective, the call looks like an ordinary function. +Bindings generators emit each side in the host language's natural async idiom: +an `async fn` in Rust, a `Promise`-returning function in JavaScript, and so on. + +Asynchronous functions were added for WASI 0.3, alongside [`stream`](#streams) and [`future`](#futures). +For the underlying motivation, see [Async, Streams, and Futures](./async.md). + ## Interfaces An interface is a named set of types and functions,