Cortex

Configuration

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

LOOM_HOST

localhost

Hostname of the Loom server to register with.

LOOM_PORT

8092

Loom REST + WebSocket port. Cortex dials out to this port.

CORTEX_MONITORING_PORT

8093

Cortex health / readiness HTTP port.

CORTEX_META_PATH

~/.cache/metaloom/cortex/meta

Local sidecar metadata cache directory.

CORTEX_NODE_ID

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

LOOM_TOKEN

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

CORTEX_NODE_WHITELIST

Comma-separated node kinds this worker will run. Empty/unset = accept anything.

CORTEX_NODE_BLACKLIST

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

CORTEX_DRAIN_TIMEOUT_MS

30000

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

GET /api/health

Liveness — 200 while the process is up.

GET /api/ready

Readiness — 200 once connected and registered with Loom, else 503.

See Monitoring API for health and readiness, and Metrics for the Prometheus scrape endpoint and the cortex_* catalog.

Looking for something else?