Remote actors and ownership
@di-framework/actors can invoke actors across processes through an explicit RPC dispatcher and a pluggable transport. Ownership generations (fencing tokens) reject stale-owner writes. Deduplication caches committed results so a retried requestId does not re-run the method.
This guide is independent of wasmCloud. Multi-host wasmCloud routing is not wired to this protocol. Single-host wasmCloud deploy remains replicas: 1 with a hostPath volume; see Actors on wasmCloud.
Read the local runtime first.
Remote client and dispatcher
Every request includes requestId (client crypto.randomUUID(), reused on retry), optional namespace, actorType, actorKey, method, args, optional callerId, and optional deadline (epoch milliseconds).
Implemented transports:
MemoryActorTransport— in-process; test hooksdropNextResponse(),delayNextRequest(ms)ChildProcessIpcTransport—child.sendor stdin JSON lines
There is no HTTP/TCP/wasmCloud cluster transport in this package. wasmCloud uses a plugin adapter at POST /_actors/invoke, not RemoteActorClient. That adapter sets callerId from the authenticated control identity and ignores a client-supplied callerId. See Control HTTP.
The client retries the same requestId unless the error is ActorAuthorizationError, StaleOwnerWriteError, ActorBackpressureError, ActorDeadlineExceededError, or an application error returned by the actor.
Authorization
Unauthorized requests reject with ActorAuthorizationError before mailbox admission. Empty arrays are whitelists; omit a field (leave it undefined) for no restriction on that axis. This is actor RPC authorization, not @ServiceBinding.
Ownership and fencing
Storage records { actorId, ownerId, generation, acquiredAt, leaseExpiresAt }.
First owner: generation
1Same owner renews without bump
Another owner with a live lease:
ActorOwnershipConflictErrorExpired lease or
force: true: generation+ 1
Every commit() with a generation checks _actor_ownership. A stale generation or wrong ownerId throws StaleOwnerWriteError and rolls back. Operators do not implement this protocol; the runtime does.
ActorRuntime({ ownerId, autoAcquireOwnership }) acquires on invoke when ownerId is set (autoAcquireOwnership defaults true in that case). If auto-acquire is off, a non-owner with an unexpired lease gets ActorNotOwnerError.
Deduplication, deadlines, and backpressure
_actor_idempotencyis written in the same commit as stateA later request with the same
requestIdreturns the cached result (cached: true) without re-running the methodIn-flight duplicate
requestIds join the same promisedeadlineis checked before enqueue and after dequeue →ActorDeadlineExceededErrormaxMailboxSize→ActorBackpressureError
Transport is at-least-once. The idempotency cache makes storage-side execution once per requestId. External side effects (HTTP, email) must still be idempotent: a successful commit can precede a lost response (MemoryActorTransport.dropNextResponse() tests this). Do not treat that as exactly-once I/O.
Multi-process example
There is no workspace app for this path. The package tests spawn workers:
packages/di-framework-actors/tests/multi-process.test.tstests/harness/worker.ts(Bun.spawn, sharedbaseDir,fileLocking: false,ownerId)tests/harness/cluster.ts
Covered: competing acquire (one generation 1, the other ActorOwnershipConflictError), crash + force: true failover (generation 2, recovered state), expired deadline with no side effects, stale-owner commit, and duplicate delivery after a lost response.
Troubleshooting
Symptom | Cause |
|---|---|
| Caller, namespace, type, or method not on the policy |
| Another owner took the generation; retry after re-acquire |
| Live lease on another |
|
|
| Mailbox deeper than |
Duplicate side effects | Lost response after commit; make I/O idempotent on |
Next steps
Actors - Local runtime, SQLite, and CLI
Private service bindings - In-process named contracts, not actor RPC
RPC - JSON-RPC / gRPC for ordinary services
Deployment - Target runtimes