Motoko actors on actor platforms
A Motoko actor compiled by moxzi is one .wasm file. You can run it on the Internet Computer, under moxzid, in a browser tab — or on an actor platform your organisation already runs: Dapr, Restate, Orleans, Akka (as Apache Pekko), Proto.Actor, Cloudflare Durable Objects, Temporal, Ray or wasmCloud. The actor does not change; the platform provides what it is good at.
Two forms#
| In process (alpha-7) | Beside the platform (first built) | |
|---|---|---|
| Where Motoko runs | inside the platform's own process, through the moxzi-rts library | in a separate moxzi-actor-host service the platform calls over HTTP |
| Where the actor's state lives | the platform's own store, committed by the platform each turn | the service's disk (a snapshot and a log) |
| Cost per active actor | ~110 KiB; activation from the store ~0.4 ms | a service process per deployment |
| Update latency vs the platform's own actor | 1.3–1.5x on Linux (Proto.Actor, Temporal, Restate) | one more HTTP hop |
| Details | /docs/hosts/in-process/ | each platform page below |
In both forms the platform drives and Motoko never calls the platform back, which is what lets an await stay outstanding across a crash of either side, or across an upgrade.
Who does what#
| The platform owns | Motoko owns |
|---|---|
identity: one actor per key Type/id | executing the actor's code |
| placement: which machine runs which actor | transactional turns: a trap rolls back the whole turn |
| one message at a time per actor | the await: the caller parks until the answer comes |
| routing a call from one actor to another | which timers are due and what they run |
| durable timers (reminders, alarms) | upgrades: drain, then migrate the heap |
| in process: the actor's durable image | beside the platform: the actor's heap, on the service's disk |
Start a project#
moxzi host new <platform> <dir> writes a project that runs a Motoko actor inside the platform's own process: actor.mo (a counter), moxzi's adapter for the platform in host/, and a run.sh that builds both, starts what the platform needs (Postgres in Docker; a Temporal dev server), starts the host, hands it the actor and calls it.
moxzi host new protoactor hello
cd hello && ./run.sh
# Counter/1.inc() -> [1]
curl -X POST localhost:8090/actors/Counter/1/method/inc -H 'content-type: application/json' -d '[]'
| Platform | host new | Needs |
|---|---|---|
| Proto.Actor | protoactor | Go, Docker |
| Dapr | dapr | Go, the Dapr CLI after dapr init |
| Temporal | temporal | Go, the Temporal CLI, Docker |
| Orleans | orleans | .NET 8 SDK, Docker |
| Akka (Pekko) | akka | a JDK and coursier (cs), Docker |
| Ray | ray | Python 3 with host/requirements.txt, Docker |
| Restate | restate | Docker (the Restate server); the host is prebuilt |
| wasmCloud | wasmcloud | nothing else: the wash-runtime host and its components are prebuilt |
| Cloudflare | cloudflare | Node.js and npm (wrangler dev, local); the engine is prebuilt |
host new needs the release's SDK bundle unpacked beside moxzi (moxzi-sdk-<version>-<target>.tar.gz: the runtime library and the adapter sources). Restate, wasmCloud and Cloudflare hosts build against moxzi's own workspace, so a release ships them prebuilt in moxzi-hosts-<version>-<target>.tar.gz (under hosts/) and host new copies them into the project (from the repository: scripts/stage_hosts.sh). Both bundles unpack into the same directory as the toolchain tarball (/docs/hosts/in-process/). scripts/host_new_gate.sh runs each project twice: the first run answers [1], and a second, from a new host process, answers [2], because the state stayed on the platform's side.
Use cases: --example#
moxzi host new <platform> <dir> --example <name> writes a project whose run.sh demonstrates something a platform's own actors usually leave to you (moxzi host examples lists them):
| Example | Actors | What the demo proves |
|---|---|---|
counter (default) | Counter | one actor per key; state survives a new host process |
ledger | Account | a deposit retried with the same invocation id is applied ONCE, even after the host process restarts; an overdraft traps and the whole turn rolls back; a transfer awaits the other account's credit |
saga | Trip, Hotel, Flight | a booking parked on an await survives the host process being killed and finishes when the held call is delivered; a full flight triggers a compensating release of the room |
moxzi host new orleans shop --example ledger
cd shop && ONCE=1 ./run.sh
# Account/alice.deposit(100), invocation dep-1 -> [100]
# (the host process was killed and started again)
# retry dep-1 after a host restart -> [100]
# Account/alice.withdraw(500), an overdraft -> trapped
# Account/alice.balance() after the trap -> [100]
# Account/alice.transfer("Account/bob", 30) -> [70]
# Account/bob.balance() -> [30]
An actor calls another actor by its key through the host's management canister, which answers the principal and learns the route at once (hosted actors only; not a method on the IC):
transient let host : actor { moxzi_actor_of : ({ key : Text }) -> async Principal } = actor "aaaaa-aa";
let bob : actor { credit : (Nat) -> async Nat } = actor (debug_show (await host.moxzi_actor_of({ key = "Account/bob" })));
The keys an actor resolves or calls travel in its image, so nothing is re-learned after a restart. The gate runs both examples on every scaffoldable platform and checks every answer.
What "passes the matrix" means#
Every platform page ends with the same sixteen-row table: the conformance suite (spec §61) run against the real platform, with real process kills and restarts. Persistence, trap rollback, parallelism across machines, duplicate delivery, an await that survives a crash, upgrades while an await is outstanding, timers that survive passivation and crashes, tiny durable writes on a large heap, and ICRC-72 pub/sub on the standard's wire types (oneway notifications, confirmations, and a subscriber that checks the caller). All nine platforms pass all sixteen, in both forms (Cloudflare skips Actor Parallelism locally: wrangler dev runs every Durable Object in one isolate). Run it yourself:
HOST=dapr-inproc scripts/host_matrix_gate.sh # in process; drop -inproc for the service form
# or restate, orleans, pekko, protoactor, cloudflare, temporal, ray, wasmcloud
Choosing a platform#
- Already run Dapr or Kubernetes sidecars? Dapr — actors, reminders and state from the sidecar you have.
- Want durable execution everywhere? Restate — every step is journaled; recovery is Restate's replay.
- .NET shop? Orleans — grains, reminders and ADO.NET storage.
- JVM shop? Akka — Cluster Sharding entities (run as Apache Pekko).
- Go shop, or want the smallest host? Proto.Actor — the reference host the contract was proven on.
- Edge, serverless? Cloudflare Durable Objects — one object per actor, alarms as timers.
- Workflows already on Temporal? Temporal — an entity workflow per actor; durable timers and replay.
- Python / ML cluster? Ray — a named Ray actor per key.
- WebAssembly components everywhere? wasmCloud — a component plus a service, state in
wasi:keyvalue.