The runtime

Worker runtime

Most runtimes give you one worker per core and let you guess which task is blocking. Yatta splits work into two pools so the answer is built in.

Two pools

The scheduler reads navigator.hardwareConcurrency at startup and sizes two pools from it.

  • cpu-pool — cores * 0.35 workers, concurrency 1. Hashing, crypto, compression. One task at a time so a heavy job cannot starve its neighbours.
  • io-pool — min(16, cores * 0.8) workers, concurrency 500. Database queries, HTTP, mail. Many tasks in flight per worker.

On a two-core machine that is 1 CPU worker and 2 I/O workers. On small hosts I/O workers also start with Bun's smol heap to keep memory down.

Subsystems

A subsystem is a module mounted as an isolated graph. Each file in yatta/func/ is one.

yatta/main.tsts
import { createRuntime, defineSubsystem } from "yatta/runtime"; const runtime = createRuntime({ taskTimeoutMs: 30_000 });await runtime.start(); const graphId = await runtime.registerSubsystem(  defineSubsystem({    name: "billing",    entrypoint: new URL("./func/billing.ts", import.meta.url),    workload: "cpu",  }),); // Dispatch a named export from that module.const receipt = await runtime.execute(graphId, "computeReceipt", payload);
namestring
Unique subsystem name; the graph id is graph-<name>.
entrypointURL | string
Module to mount. Exported functions become dispatchable handlers.
workload"cpu" | "io"
Selects the pool.
envRecord<string, string>
Extra environment overlay visible inside the subsystem.
timeoutMsnumber
Per-subsystem deadline. 0 disables.

Choosing the workload

cpu or iots
// CPU-bound: Argon2id, AES, image work, large JSON transformsdefineSubsystem({ name: "auth", workload: "cpu", ... }) // I/O-bound: SQLite, fetch, mail, S3defineSubsystem({ name: "db", workload: "io", ... })
Note
Marking something cpu is how you tell the scheduler it may block. Getting it wrong is the usual cause of “my server freezes under load”.

Dispatch

The scheduler picks the least-loaded worker for the graph. When every slot is busy the task waits in a fast queue and is drained as capacity frees up.

Dispatchts
// Fire and forgetvoid runtime.execute(graphId, "sendReport", { id: 1 }); // Awaitconst result = await runtime.execute(graphId, "computeReceipt", payload); // Concurrent calls share the workersawait Promise.all(  orders.map((o) => runtime.execute(graphId, "computeReceipt", o)),);

Micro-batching

Under a backlog the scheduler stops sending one message per task and packs up to 32 into a single IPC message (64 past 256 queued). This removes most of the thread-hop cost under burst load without changing your code.

Inspecting the fleet

Introspectionts
console.log(runtime.getTopology());// {//   cpuCores: 2,//   cpuWorkers: 1,//   ioWorkers: 2,//   totalWorkers: 3,//   suggestSmol: true// } console.log(runtime.getActiveTaskCount()); // in flight + queued

Environment

Each subsystem can override the scheduler-wide deadline:

tsts
const runtime = createRuntime({  cpuWorkers: 2,       // override the computed default  ioWorkers: 4,  taskTimeoutMs: 30_000,  silent: false,       // suppress the startup banner}); // Per-subsystemdefineSubsystem({  name: "exports",  entrypoint: "./func/exports.ts",  workload: "cpu",  timeoutMs: 120_000,  // a long report gets more time});
Warning
Setting timeoutMs: 0 disables the deadline for that subsystem. Only do this for work you control — see Reliability for what an unbounded task costs.