Skip to content

Run a daemon node

spinloop serve runs an engine in front of you until one of you exits. spinloop daemon is the long-lived form: it supervises one engine over the HTTP control API, so anything that can reach the machine — a fleet, a gateway, a laptop across the network — can start it, stop it, and watch it.

A daemon is what a node is: a machine on your network that serves engines on request. A fleet is daemons, named.

Run it

spinloop daemon                      # listens on :4242, binds all interfaces
spinloop daemon --loopback           # 127.0.0.1:4242; needs no token

The daemon stays in the foreground itself — background it with tmux, systemd, launchd, or a container. It writes the engine's output to daemon/engine.log under spinloop's config directory and tracks the engine's state: idle, running, stopped, crashed. A crash is reported, never auto-restarted.

Authenticate it

Requests carry Authorization: Bearer <token>. The token comes from one of three places — giving two at once is an error rather than a silent precedence:

Source Notes
--api-token-file <path> The file's contents, trimmed. Use this from a service manager.
SPINLOOP_API_TOKEN The environment.
--api-token <value> The token itself — readable in ps by every local user.

A non-loopback listen with no token refuses to start; a loopback one needs none. A literal in a systemd unit file or plist is a secret in a config file and in the process list, which is why the file form exists.

What a node holds

Nothing but the daemon. A node reads no Spinloop, no preset, and no fleet.yaml — passing a Spinloop path to it is an error rather than being quietly ignored. The client that asks decides what runs: a start request's own deploy config, or the one stored from a previous ask via PUT /v1/deploy-config. With neither, a start says so.

That is why a node and a client want different files. A client's Spinloop names a model and a fleet; a node holds nothing. See examples/fleet-local/ for the whole shape on one machine.

The API

Endpoint Meaning
GET /v1/status Engine state, what is served, the log path, how long it has been idle
POST /v1/start Start the engine (optional deploy-config body; 409 while one runs)
POST /v1/stop Stop the engine (idempotent; never ends the daemon)
GET /v1/metrics Engine token counters plus host GPU/CPU/RAM
GET /v1/logs A slice of the engine's output, by offset
PUT /v1/deploy-config Set what the next start serves

The full contract is openapi.yaml; the HTTP control API page walks the endpoints in detail.

Gating the engine

An engine can require its own key, separately from the token above — one authorises driving the node, the other authorises using its engine. The caller supplies it, in the start request; a node sources no key of its own. The daemon writes it to a private file and points the engine at that path, so the key never appears in the node's process list. /v1/status reports only that a key is required, never what it is.

Logging

Set --log-level warn on a node a fleet polls: a spinloop status refresh every few seconds is a request each, and at that level polling is quiet while a wrong token still shows up. Records go to stderr, so a service manager's journal or log files keep them.

Where next