Skip to content

`harmos-guest`

harmos-guest owns the Component Model WIT world, guest lifecycle bindings, capability wrappers, static declaration parser, and the host-side harmos-component builder.

The root harmos crate exposes direct loading only behind its guest feature.

  • #[harmos::guest] embeds artifact identity, the Cargo package version, and typed config name. Description defaults to Cargo; an explicit contextual description is optional.
  • #[harmos::requires] embeds artifact dependencies.
  • capability attributes embed the edges a guest declares.
  • Declaration::inspect reads final component bytes without compiling or instantiating Wasm.
  • Plugin::register contributes typed static registrations before initialization.
  • #[harmos::guest(routes(echo, ...))] composes dispatch and its complete static route inventory from one list. An omitted list explicitly declares an empty table.
  • harmos::guest::Host offers application capabilities, compiles a valid component, collects registrations, and initializes it under a fuel budget.

The guest receives no Runtime, Journal, Streams, filesystem, or socket. Every effect crosses the WIT boundary and is revalidated by the host.

A route is a Rust type declared once and named by both ends: the method, the protobuf payload, the protobuf reply, and the bound one call is held to. The same declaration serves a sidecar, so a tenant moves between facilities without its callers being rewritten.

A self-contained handler exports its typed descriptor and composition function:

#[harmos::route(id = "text.echo", deadline_ms = 250)]
pub fn echo(request: String) -> Result<String, harmos::guest::RouteRefusal> {
Ok(request)
}
#[harmos::guest(
id = "text", description = "Echoes text.",
config = Settings, routes(echo),
)]
struct Text;

The lifecycle remains in impl Guest for Text; no second route list belongs there. The host calls guest.call::<echo::Route>(&request).await?.

Declaration::inspect reads a complete inventory qualified by guest and the artifact id. It ignores other owners' lists and unowned linked route fragments; a missing or repeated matching inventory is refused. Inspection runs no code. These bytes are the artifact's declaration, not proof of arbitrary executable behavior. Runtime admission may compare actual served declarations separately.

Low-level kits implement RouteTable and emit a complete manifest using harmos::guest::inventory::{RouteEntry, encoded_len, encode}. Native SDKs use the same helpers under harmos::sidecar::inventory, with facility sidecar and the final executable identity. RouteEntry::of::<Descriptor>() reads the method and deadline at compile time. Generate both dispatch and this manifest from the same composition; a descriptor linked into a library alone claims no served route.

Every route call crosses the world's one answer export, which every guest carries whether or not it serves routes — a world is one shape. Declaring a route is therefore a source change in one crate rather than a world change, and the world's hash does not move when one is added.

Handlers refuse with RouteRefusal, the envelope both facilities speak: a numeric code a caller branches on and a message a caller shows. Methods under harmos. belong to the facility and are refused at the site that registers one and again at the site that calls one.

A route call is bounded twice, and the two bounds answer different questions. Fuel bounds computation and is refilled for each call, so no guest draws one lifetime budget down across its whole life. An epoch deadline derived from the route's declared bound bounds time, which fuel cannot: a guest suspended inside a host call burns no fuel at all. No number converts one into the other.

A call that ends badly ends in exactly one named way — the bound elapsed, the fuel ran out, or the guest trapped. Each discards the instance, and every later call is refused without reaching the guest. Nothing is re-run: a route that changed application state before failing changed it once.

Build the guest as a wasm32-unknown-unknown core module, then run harmos-component <core.wasm> <guest.wasm>. The component encoder and the Wasmtime version that consumes it resolve from the same workspace lockfile.

mise run run:minischematic:guest builds and runs Autoroute. The host inspects metadata before compilation, offers net.rename, observes declared progress, finalizes, and reopens the journal without the guest.

The direct host implements every WIT edge. Invoke enters the application journal; record and recall use a durable guest-local namespace; publish collects declared guest output; survey and subscription require the guest to have declared the edge, which is a wiring check rather than a permission. Egress also requires an application-owned adapter registered with Host::egress, which receives only requests already bounded and proven to stay inside the declared endpoint family.

Stokker Technologies markDesigned and built by Stokker Technologies