Architecture¶
flowchart LR
subgraph Client
CLI[n4cluster CLI]
SDK[ClusterClient SDK]
end
subgraph Server["FastAPI server · single process"]
API[REST + WebSocket]
SCHED[scheduler · state machines + matching]
DB[(SQLite · WAL)]
OBJ[(object store · sha256)]
EV[event broker]
end
subgraph Worker
AGENT[agent · lease + heartbeat]
RUN[subprocess runner]
end
CLI & SDK -- REST/WS --> API
AGENT -- long-poll lease + heartbeat --> API
AGENT --> RUN
RUN -- imports --> N4A[nirs4all.run]
API --- SCHED --- DB
API --- OBJ
API --- EV
A single server process owns the queue (SQLite in WAL mode), a content-addressed object store, the scheduler (state machines + worker matching), and an in-process event broker. Workers poll the server (long-poll lease + heartbeat) rather than receiving pushes — simpler for a LAN and for machines behind NAT.
The load-bearing invariant¶
The only module that imports nirs4all is the runner subprocess
(nirs4all_cluster/runners/nirs4all_run.py). The server, client, worker agent,
materializer and executor stay nirs4all-free. This buys three things: the control plane
runs without the library installed; a native-backend segfault can’t kill the worker; and
the parent can terminate() the child for real cancellation.
A task’s life¶
sequenceDiagram
participant C as Client
participant S as Server
participant W as Worker
C->>S: POST /v1/jobs
W->>S: POST /v1/workers/{id}/lease (long-poll)
S-->>W: TaskPayload
W->>S: POST /v1/tasks/{id}/start
W->>S: POST /v1/tasks/{id}/events (progress)
W->>S: POST /v1/tasks/{id}/artifacts (upload by sha256)
W->>S: POST /v1/tasks/{id}/complete (TaskResult + fingerprint)
C->>S: GET /v1/jobs/{id} (aggregate + ranking)
Recovery rests on leases: a lease has a TTL renewed on every heartbeat, so a long task is not wrongly reaped while its worker is healthy; if the worker goes silent the lease lapses and the task is requeued (or failed after its attempts). A cancelled job’s reaped lease is never relaunched. See Operations.