Runtime Architecture
YokeRuntime components, service wiring, and end-to-end data flow through Postgres, fan-out, queues, and Deno.
YokeRuntime is the top-level assembly of every kernel subsystem. It implements the Runtime trait and wires Postgres, fan-out, scheduling, storage, and the embedded Deno host into one long-lived process.
Component map
YokeRuntime holds:
| Field | Responsibility |
|---|---|
services | StreamWithFanout, instance registry, Storage — shared by the op handler |
deno | EmbeddedDenoHost — package load, dispatch_process, filter eval |
fanout | FanoutEngine — index rebuild, async enqueue |
scheduler | QueueDrainScheduler — per-instance queue drain loop |
storage | Storage enum (PgSqlite | S3) — actor SQLite + ctx.state |
_worker_registry | Registers in runtime_workers, claims unassigned instances |
_stream_catchup | Background tail for cold-start / multi-executor fan-out |
_timer_dispatch | Timer dispatch — fires due rows from scheduled_events |
_lease_heartbeat | Optional tenant lease extension (YOKE_EXECUTOR_MODE) |
RuntimeServices groups the three handles the JSON-RPC op handler needs: the stream (with fan-out wrapper), the instance registry, and storage.
Data flow diagram
flowchart TB
subgraph external [External]
PortalHTTP[Portal HTTP routes]
UserTS[User TypeScript package]
end
subgraph deno [Embedded Deno]
WorkerRPC[WorkerRpc JSON-RPC]
ProcessFn["actor.process(ctx, event)"]
PortalIface[Portal interface methods]
end
subgraph rust [YokeRuntime Rust]
OpHandler[RuntimeOpHandler]
StreamWF[StreamWithFanout]
PgStream[PgStream]
Fanout[FanoutEngine]
Queue[PgActorQueue]
Scheduler[QueueDrainScheduler]
Storage[Storage pg_sqlite or S3]
end
subgraph pg [Postgres]
StreamEvents[(stream_events)]
ActorQueues[(actor_queues)]
Instances[(actor_instances)]
ActorSqlite[(actor_sqlite)]
end
PortalHTTP --> OpHandler
UserTS --> WorkerRPC
WorkerRPC <-->|unix socket| OpHandler
OpHandler --> StreamWF
StreamWF --> PgStream
PgStream --> StreamEvents
StreamWF -->|async| Fanout
Fanout --> Queue
Queue --> ActorQueues
Scheduler --> Queue
Scheduler -->|dispatch_process| WorkerRPC
WorkerRPC --> ProcessFn
ProcessFn -->|stream_append op| OpHandler
ProcessFn -->|portal_call op| PortalIface
OpHandler --> Storage
Storage --> ActorSqlite
Fanout --> Instances
Scheduler --> InstancesAppend path (hot path)
- Deno SDK calls
stream_appendop →RuntimeOpHandler. StreamWithFanout::append_and_fanoutwrites viaPgStream::append(assignsseq, inserts row).- Append returns
AppendResult { seq, id }to the caller immediately. - Fan-out dispatches asynchronously (or deferred during nested actor ops — see Fan-out).
record_dispatchedupdates the per-tenant fan-out cursor inruntime_metadata.
Pseudocode:
append_and_fanout(input):
result = stream.append(input)
record metrics
if should_defer_fanout():
spawn fanout.dispatch(event) // avoid re-entering Deno
else
fanout.dispatch(event)
return resultProcess path (actor drain)
QueueDrainSchedulertick loop lists actor instances and activegroup_idvalues.- Per
(instance_id, group_id)it acquires semaphore permits up toqueue_policy.concurrency. claim_nextmarks a rowprocessinginactor_queues.EmbeddedDenoHost::dispatch_processRPC runsprocess()in Deno.- On success →
queue.completedeletes the row. On failure →mark_failedretries or dead-letters.
Op handler bridge
RuntimeOpHandler implements OpHandler and is installed on the Deno worker RPC channel. Every SDK kernel call (ctx.append, ctx.state, ctx.instances.create, conversations, schedules) maps to a method string handled in the runtime.
The handler holds references to stream, instances, fan-out, storage, and conversations so Deno never touches Postgres directly.
Background tasks
| Task | Trigger | Purpose |
|---|---|---|
| Stream catch-up | LISTEN + 30s poll | Fan out events appended while executor was cold |
| Timer dispatch | LISTEN + next-fire sleep | Append due scheduled_events to stream |
| Worker heartbeat | 15s interval | Update runtime_workers.last_heartbeat |
| Executor lease | 15s interval (optional) | Extend tenant_runtimes lease TTL |
Horizontal scaling
Multiple runtime workers share one Postgres database. Each registers in runtime_workers and claims actor_instances rows where worker_id IS NULL. New instances are assigned to the creating worker's ID. The scheduler only drains queues for instances owned by the local worker.
| Variable | Purpose |
|---|---|
YOKE_WORKER_ID | Stable worker identity (defaults to {hostname}-{uuid}) |
Stale workers
Workers with no heartbeat for 60 seconds are excluded from the yoke_runtime_workers gauge. Instance ownership is not automatically migrated yet — plan worker drains before shutdown.
Control plane vs executor
The control-plane HTTP API uses NoOpFanout so appends write to stream_events only. Tenant executors fan-out via stream_catchup (boot and LISTEN yoke_stream_append while hot). Duplicate delivery is prevented by fanout_delivered (see Fan-out).
Subsystem modules
The Rust runtime is organized around these responsibilities:
| Area | Role |
|---|---|
| Runtime assembly + op handler | Boot, JSON-RPC bridge, kernel orchestration |
| Stream | Per-tenant sequenced append to stream_events |
| Fan-out | Two-tier filters → actor_queues |
| Queues | Persisted per-instance work queues |
| Scheduler | Drain loops, concurrency, Deno dispatch_process |
| Timer dispatch | scheduled_events → stream |
| Tenant scheduler | Runtime lifecycle for hosted deployments |
| Storage | pg_sqlite vs S3 backend selection |
| SQLite layer | Per-instance file + __yoke_state CAS |
| Embedded Deno | libdeno worker host |
| Worker RPC | JSON-RPC framing |
| Portal HTTP | Routes, /health, /metrics |
| Migrations | SQL schema applied at boot |