Load Guests and Sidecars
Harmos installs the file that executes: a native executable is a sidecar and a WebAssembly component is a guest. They are parallel artifact kinds, not children of another runtime abstraction.
| Sidecar | Guest | |
|---|---|---|
| File | Native executable | Wasm component |
| Boundary | Supervised process | Wasmtime Component Model |
| Declaration | #[harmos::sidecar] | #[harmos::guest] |
| Calls | Typed routes over gRPC | Host-mediated capabilities |
| Configuration | Typed Rust value; JSON only at IPC | Typed Rust value; bytes only at WIT |
| Registration | Plugin::register | Plugin::register |
| Failure policy | Embedded, host-bounded restart policy | Trapped instance is discarded; no operation retry |
| Version | One artifact semver | One artifact semver |
Enable only what the application ships:
harmos = { version = "0.2", features = ["sidecar"] }# or features = ["guest"]# or features = ["sidecar", "guest"]Inspect Before Execution
Section titled “Inspect Before Execution”Both authoring macros embed bounded JSON declaration fragments into the final artifact. Inspection scans the bytes and validates the declaration; it does not start the executable, compile the component, run constructors, or instantiate Wasm.
let bytes = std::fs::read(path)?;let sidecar = harmos::sidecar::Declaration::inspect(&bytes)?;// or harmos::guest::Declaration::inspect(&bytes)?Inspection proves what the artifact declares, not that native code is benign. Enforce provenance and file integrity at installation, then enforce the declaration again at the transport boundary. Unknown routes, undeclared guest capabilities, oversized frames, and undeclared publications are refused.
Plan Dependencies
Section titled “Plan Dependencies”#[harmos::requires] names only an artifact id and a semantic-version range:
#[harmos::requires(id = "probe-bridge", version = "^1")]#[harmos::guest( id = "calibrator", description = "Calibrates probe readings.",)]struct Calibrator;It applies to both artifact kinds and intentionally names no route. The author knows which compatible artifact version supplies the behavior they need.
Convert inspected declarations to harmos::load::Artifact values and call
harmos::load::Plan::resolve. Planning refuses duplicate ids, missing
dependencies, incompatible versions, and cycles before anything executes. Its
answer is dependency-first initialization order.
Run a Sidecar
Section titled “Run a Sidecar”The complete executable is Minibench.
Its host reads the declaration, resolves an absolute Executable, installs a
resident Supervised using the file's validated restart policy, receives static
registrations, and calls a typed route. Tonic/prost owns the fixed gRPC envelope
over stdio and the Prost derives on request, reply, and sample types own those
payloads. Typed configuration and registrations cross as JSON.
Publications use a bounded non-blocking queue. A device producer never waits on
the runtime; sequence gaps expose overload. Run mise run bench:sidecar to
record current throughput and drop accounting.
Run a Guest
Section titled “Run a Guest”The Autoroute example builds a component and
passes that .wasm file directly to the application:
let host = harmos::guest::Host::new(&runtime)? .transaction::<RenameNet>() .survey::<Voltage>();let loaded = host.load(&std::fs::read(component)?)?;let registered = loaded.register().await?;apply_registrations(registered.registrations())?;let active = registered .initialize(&AutorouteSettings::default()) .await?;Static validation precedes Wasmtime compilation. A restricted pre-initialization
instance returns registrations, then initialization receives typed config while
the instance remains under a fuel budget. Accepted transactions enter ordinary
application history under guest:<id>, so recovery neither loads nor executes the
guest. Guest facts enter harmos:guest/<id>/… and recall can only page that
same namespace. Survey and subscription edges require the guest to have declared
them — an undeclared edge is a wiring bug, not a refused permission — and egress
additionally requires an application-owned adapter, so Harmos and the guest never
acquire sockets or credentials.
If a guest traps, its instance is discarded. A later operation may instantiate a fresh one, but Harmos does not retry the failed operation: replaying an effect would violate at-most-once behavior.
Ask a Guest a Question
Section titled “Ask a Guest a Question”Delivery hands an entry down and takes no reply. To ask a guest something and be answered, call a route — a Rust type carrying the method both ends speak, the payload, the reply, and the bound one call is held to:
let answered = active.call(&StartRecording { output: path }).await?;The same declaration serves a sidecar, so moving a tenant between facilities
changes how it is declared and installed rather than the call site. A guest
serving no routes — including one built before routes existed — refuses by name
rather than failing, and Active::answers says which kind you loaded.
Each call refills its own fuel budget and arms an epoch deadline from the route's declared bound. Fuel stops a guest that loops; the epoch stops one that spent its bound suspended in a host call, which fuel cannot see. A call that ends badly says which of the three ended it — the bound, the fuel, or a trap — and the instance is discarded either way, so a later call is refused without running.
Registrations Belong to the Loaded File
Section titled “Registrations Belong to the Loaded File”Both artifact kinds return the same Registration { namespace, schema, payload }
envelope before initialization. A consumer decodes only namespaces it owns and
atomically replaces the set keyed by artifact id. A crashing sidecar therefore
does not unregister its catalogs while it backs off; replacing or unloading the
artifact replaces or removes that source as one operation.
No Parallel Contracts Tree
Section titled “No Parallel Contracts Tree”Settings, request types, replies, and stream rows live with the sidecar or guest that owns them. The macros contribute their declarations from those same Rust types. There is no manifest to synchronize and no archive-building xtask.
Cargo owns executable metadata
Section titled “Cargo owns executable metadata”Native and guest declarations derive their version from the consuming Cargo
package on every build. Omit version from #[harmos::sidecar] and
#[harmos::guest]; explicit versions are rejected. Dependency requirements such
as #[harmos::requires(version = "^1")] remain explicit version constraints.
Descriptions default to Cargo's package.description. Set a nonblank package
description, or provide description = "…" when one executable needs a more
specific purpose than its package. The explicit owner id remains independent
of the Cargo package name. The generated constants and inert declaration bytes
use the same tracked Cargo values; inspection still executes no plugin code.