Remote Actors
Run an actor in a separate OS process, bridged over two named pipes. One import defines both ends of the wire.
defineRemoteActor
import { defineRemoteActor } from "posipaki/remote";
const { actor, runRemoteRoot, isRemoteRoot } = defineRemoteActor(
echoActor,
import.meta.url,
);
Wraps a normal defineActor definition and returns:
actor— a proxyActorDefinition. Spawn it like any actor; the first thing it does is boot the child process and bridge messages.runRemoteRoot()— run the child side manually (seemanualbelow).isRemoteRoot—truewhen this process is the spawned child.
One file, both ends
The same module works as host and child. defineRemoteActor detects the
child via a --remote=<hash> argument in process.argv and starts the
remote side automatically. Otherwise you are the host and spawn as usual:
import { defineActor, defineMessages } from "posipaki";
import { defineRemoteActor } from "posipaki/remote";
const echoActor = defineActor({
name: "echo",
inMessages: defineMessages<{ type: "PING"; count: number }>(),
outMessages: defineMessages<{ type: "PONG"; count: number }>(),
setup: () => ({ pings: 0 }),
handlers: {
PING(msg) {
this.state.pings++;
this.emit({ type: "PONG", count: msg.count });
},
},
});
const { actor: remoteEcho, isRemoteRoot } = defineRemoteActor(echoActor, import.meta.url);
if (!isRemoteRoot) {
const proc = await remoteEcho.spawn({});
await proc.ready();
proc.send({ type: "PING", count: 1 });
await proc.wait();
}
When the child's actor exits, the host's proc.wait() resolves — the same
shutdown story as a local spawn.
Live state
The child streams state updates over the wire, so proc.state on the host
tracks the child's current state exactly like a local process.
Connectors
defineRemoteActor(actor, url, { connector }) picks how the child is launched:
bunConnector—bun <script>nodeConnector—node <script>defaultConnector— auto-detects:bunif running under Bun, elsenodecommandConnector— spawn a raw command array
import { defineRemoteActor, nodeConnector } from "posipaki/remote";
const { actor } = defineRemoteActor(echoActor, import.meta.url, {
connector: nodeConnector,
});
RemoteProxy
The bridge between host and child. defineRemoteActor builds one for you; you
can also write your own Connector that returns one:
state— the child's live stateready()— resolves once connectedsend(msg)— send an in-message to the childonMessage(cb)— observe out-messages from the childwait()— resolves when the child exits, with{ code, state }
Wire protocol
Host and child speak NDJSON — one JSON object per line — over two named fifos. The frames are:
| direction | frame | payload |
|---|---|---|
| child → host | $proto |
protocol version (ndjson.v1) |
| child → host | $state |
state snapshot / update |
| child → host | $msg |
out-message { fromName, body } |
| child → host | $exit |
{ code, state } |
| host → child | $init |
init args + parent identity |
| host → child | $msg |
in-message { fromName, body } |
Notes
- Remote actors require a Node or Bun runtime — they spawn child processes and use named fifos, so they do not run in the browser.
- The child inherits stdio and runs with
cwd: process.cwd().