Cortex is a daemon and is configured primarily through environment variables, which is convenient
for container-based deployments. A cortex CLI wrapper (picocli) also exists for local runs, but in a
typical online deployment you just set environment variables and start the process — it connects to
Loom, registers, and serves node tasks.
Connecting to Loom
| Variable | Default | Description |
|---|---|---|
|
|
Hostname of the Loom server to register with. |
|
|
Loom REST + WebSocket port. Cortex dials out to this port. |
|
|
Cortex health / readiness HTTP port. |
|
|
Local sidecar metadata cache directory. |
|
generated |
Stable worker id. Set it so a restarted worker keeps its identity — Loom keys registrations on it. |
|
Note
|
Some older material references a Cortex↔Loom port of 7733. The running Loom server and the
demo/start scripts use 8092; prefer that.
|
Authentication
An online Cortex worker authenticates to Loom for the WebSocket (?token=<jwt>) and for REST result
writes (Bearer <jwt>) using a single token:
| Variable | Description |
|---|---|
|
A JWT used for both the WebSocket handshake and REST calls. |
Without a token a worker still registers and answers tasks; it simply skips result persistence.
|
Note
|
The Python worker example additionally accepts LOOM_USER / LOOM_PASSWORD
to log in for a token — that is a convenience of that example, not the JVM daemon, which uses
LOOM_TOKEN.
|
Advertising Node Kinds
A Cortex instance advertises the node kinds it is willing to run when it registers. Loom only
dispatches a task to a worker whose restriction accepts that kind, which is how you pin heavy nodes
(e.g. facedetect, whisper) to specific hardware.
| Variable | Description |
|---|---|
|
Comma-separated node kinds this worker will run. Empty/unset = accept anything. |
|
Comma-separated node kinds this worker refuses, even if the whitelist would admit them. |
An administrator can further restrict a worker from the Loom UI; the effective restriction is
reconciled on registration and persisted as a durable cortex_instance record.
How Loom Picks a Worker
Among the workers that accept a node kind, Loom dispatches to the one with the highest priority. Priority is your placement decision and is never overridden — a busy high-priority worker is still preferred over an idle low-priority one.
Where several workers share the same priority, the least loaded one is chosen. Load is taken from the status update every worker sends every 20 seconds, and is the higher of its CPU and disk utilisation, because a worker saturated on either cannot take on more. A worker that has not reported a load in the last minute counts as neither idle nor busy, so a quiet worker is not favoured over one that has proven it has capacity.
Only workers that are online receive work. A paused, starting or shutting-down worker is skipped.
Shutting a Worker Down
Stopping a worker with SIGTERM (docker stop, a Kubernetes eviction, a systemd restart) does not
throw away the work it was holding. The worker first tells Loom it is terminating, which stops any
further tasks being sent to it, then gives the tasks already running a grace period to finish. Anything
still running when that period expires is handed back, and Loom places it on another worker
immediately.
Without this hand-back, work held by a stopped worker is only recovered when its lease expires — ten minutes by default — so every scale-down or rolling deploy would stall the affected runs for that long.
| Variable | Default | Description |
|---|---|---|
|
|
How long a shutdown lets running tasks finish before handing them back. |
The default matches Kubernetes' 30 second termination grace period. Waiting longer than your
orchestrator will wait achieves nothing — the process is killed mid-drain and the work falls back to
lease expiry — so if you run minutes-long nodes such as whisper or ocr, raise this and
terminationGracePeriodSeconds together. Otherwise those tasks are simply re-run elsewhere, which
costs time but loses nothing.
|
Caution
|
A worker that is in the middle of scanning a source when it is stopped is the one case that is not recovered automatically: there is no way to hand a half-finished scan back. The run waits, and has to be started again. |
Optional YAML File & Per-Node Options
Global connection settings come from environment variables (or CLI flags). Fine-grained per-node
options can also be supplied in an optional YAML file at ~/.config/metaloom/cortex.yml (a default
is written there on first run if missing). Cortex also ships sensible per-node default timeouts (e.g.
sha512 30 s, thumbnail 120 s, whisper/llm 600 s) and a media-level concurrency default of 4.
Monitoring / Health
The daemon exposes a small monitoring HTTP server (default port 8093), which maps cleanly onto
Kubernetes probes:
| Endpoint | Meaning |
|---|---|
|
Liveness — |
|
Readiness — |
See Monitoring API for health and readiness, and Metrics for the
Prometheus scrape endpoint and the cortex_* catalog.