Loom is configured through a YAML file (loom.yml) with environment-variable overrides layered on
top. The resolution order is:
-
Load
loom.ymlfrom the first location that exists (see below), or fall back to built-in defaults. -
Apply environment variables — any
LOOM_*variable overrides the corresponding YAML value. -
Validate the result (e.g. a keystore password must be present; one is generated if unset).
So for container deployments you can skip the file entirely and configure everything with environment variables.
Config File Locations
Loom looks for loom.yml in the following order (first match wins):
-
/etc/metaloom/loom.yml— system-wide config -
~/.config/metaloom/loom.yml— user home config -
config/loom.yml— relative to the working directory
If none exist (and none is on the classpath), Loom writes a default config/loom.yml on first start.
Full Configuration Example
Every top-level key maps to an options group. Only the keys you want to change need to be present.
database:
host: "127.0.0.1"
port: 5432
username: "postgres"
password: "finger"
databaseName: "loom"
minPoolSize: 5
acquireIncrement: 5
maxPoolSize: 20
server:
restPort: 8092 # REST API + WebSocket + UI
grpcPort: 8091 # gRPC
monitoringPort: 8989 # health / metrics
mcpPort: 4041 # Model Context Protocol server
bindAddress: "0.0.0.0"
auth:
keystorePath: "keystore.jceks"
keystorePassword: "change-me" # generated if omitted
tokenExpirationTime: 3600
oauth2:
enabled: false
# clientId / clientSecret / authUrl / tokenUrl / userInfoUrl / callbackUrl / logoutUrl / scope
storage:
uploadDirectory: "data/storage" # where uploaded asset binaries are stored
ai: # Chat & AI agent — see the "AI Agent" section below
enabled: true
url: "http://127.0.0.1:8080/v1"
modelId: "openai/gpt-oss-20b"
sandbox: # Agentic coding sandbox — off by default
enabled: false
backend: "podman"
memory: # Agent memory bank — off by default
enabled: false
Environment Variables
All settings can be overridden with environment variables.
Environment variables take precedence over values in loom.yml.
Database
| Variable | Default | Description |
|---|---|---|
|
|
PostgreSQL host |
|
|
PostgreSQL port |
|
|
Database username |
|
|
Database password |
|
|
Database name |
|
|
Minimum connection pool size |
|
|
Maximum connection pool size |
Server
| Variable | Default | Description |
|---|---|---|
|
|
HTTP REST port (REST + WebSocket + UI) |
|
|
gRPC port |
|
|
Model Context Protocol server port |
|
|
Bind address for all listeners |
|
|
Monitoring/health port |
Cortex worker presence
Cortex workers heartbeat over the processor WebSocket. A worker whose host dies, is partitioned or freezes may leave a half-open connection behind: the socket never closes, so without a timeout Loom would keep the worker online and keep sending it work. Loom therefore expires a worker that has gone silent, marks it offline, and immediately re-places whatever it was running on another worker. A worker that comes back simply re-registers; its saved node restrictions are untouched.
| Variable | Default | Description |
|---|---|---|
|
|
Expire workers that stop heartbeating. Set to |
|
|
How often a worker is expected to report in; also how often Loom checks |
|
|
Reports a worker may miss before it is expired. With the defaults that is 60 seconds of silence |
|
Tip
|
Raise LOOM_PROCESSOR_MISSED_HEARTBEATS rather than lowering it on a congested
network. Expiring a worker that was only briefly unreachable is safe but costs duplicated
work, because the tasks it was running are handed to another worker while it may still be
finishing them.
|
Storage
| Variable | Default | Description |
|---|---|---|
|
|
Directory where uploaded asset binaries are stored |
Near-duplicate video lookup
Powers "which other videos look like this one?", and with it the near-duplicate review workflow of the deduplication nodes. It is switched off by default because it is only useful once videos have been fingerprinted.
The lookup keeps its own index on disk. That index is built entirely from fingerprints already stored with your assets, so it can be discarded and rebuilt at any time without data loss.
| Variable | Default | Description |
|---|---|---|
|
|
Enable near-duplicate lookup. While off, requests to the similar-videos routes are refused with a clear reason rather than answering "nothing found" |
|
|
Directory holding the lookup index. Give each server its own directory — two servers cannot share one |
|
|
Which fingerprint version takes part in the lookup |
|
|
How close a match must be to count as a near-duplicate. Higher is stricter |
|
|
How many similar videos a query returns by default |
|
Note
|
If the index directory cannot be written, the server still starts normally — only the near-duplicate routes report themselves as unavailable. |
Face and image similarity
Powers "where else does this face appear?" and the person grouping built on top of it. It is a separate index from the near-duplicate lookup above: that one holds one fingerprint per video and answers "is this the same recording?", while this one holds one vector per detected face and answers "is this the same person?".
Switched off by default, because it is only useful once face detection has run with embeddings enabled.
Your face vectors are stored with your assets in the database. The index is built entirely from them, so it can be discarded and rebuilt at any time without losing anything — which is also what makes it safe to change the recognition model, or the index technology, later on.
| Variable | Default | Description |
|---|---|---|
|
|
Which index to use: |
|
|
Directory holding the index. Give each server its own directory, and keep it separate from the near-duplicate index |
|
|
How many matches a query returns by default |
|
|
How close a match must be to count. Higher is stricter |
|
|
How often newly stored vectors are picked up. Set |
|
|
How many vectors are picked up per pass |
|
Note
|
If the index directory cannot be written, the server still starts normally — face vectors are still stored, and only the similarity routes report themselves as unavailable. Switching the index on later and running a rebuild picks up everything recorded in the meantime. |
Authentication
| Variable | Default | Description |
|---|---|---|
|
(random) |
Initial admin password (first boot only) |
|
|
JWT token lifetime in seconds |
|
|
Require auth on the MCP endpoints |
|
|
Reject unauthenticated MCP calls (no lenient mode) |
|
|
CORS origins allowed on the MCP SSE endpoint |
The keystore password is read from the auth.keystorePassword config key (a random one is generated
if unset). See Authentication.
OAuth2
Optional OAuth2 login (BFF pattern). Set LOOM_OAUTH2_ENABLED=true and provide your provider’s
endpoints:
| Variable | Description |
|---|---|
|
Enable OAuth2 authentication (default |
|
Client credentials |
|
Provider endpoints |
|
Callback and logout URLs |
|
Scopes to request (default |
AI Agent
Settings for the built-in Chat & AI Agent. The agent is enabled by default
(LOOM_AI_ENABLED=true) but needs a reachable LLM provider to answer.
| Variable | Default | Description |
|---|---|---|
|
|
Master switch for the chat agent |
|
|
Base URL of the OpenAI-compatible server |
|
|
Model id |
|
|
Context window (tokens) |
|
|
Max tool-calling turns per message |
|
|
Per-tool timeout |
|
|
Opt-in true token/reasoning streaming |
|
(model default) |
Enable model "thinking" chunks |
|
(on) |
Auto-title a chat after the first exchange |
|
|
Tokens held back from the window so the model always has room to answer |
|
|
Hard cap on replayed messages. |
|
|
New messages after which the conversation summary is refreshed |
|
|
Maximum length of that summary |
|
|
Items the agent may process in one parallel batch |
|
|
How many of those run at the same time |
|
|
How much of each per-item answer is kept |
|
|
Total model calls one message may cost |
|
Note
|
Questions that span many items. Asked to summarize fifty transcripts or find the themes across a quarter of uploads, the agent processes the items in parallel batches — each one read on its own, then the answers combined. This is what lets it answer over more material than fits in a single context.
The agent always reports how many items succeeded and how many failed, so a partial answer is never presented as a complete one. |
|
Note
|
Long conversations look after themselves. A chat is replayed to the model in full on every
message, so a long one eventually outgrows If the agent seems to lose the thread of a long conversation, raise |
Coding Sandbox
Settings for the per-chat coding sandbox (LOOM_AGENT_SANDBOX_*). Off unless
LOOM_AGENT_SANDBOX_ENABLED=true. See Chat & AI Agent — Coding Sandbox for the full
table and deployment (backends, Kubernetes RBAC, the runner image). Key switches:
LOOM_AGENT_SANDBOX_BACKEND (podman \| kubernetes), LOOM_AGENT_SANDBOX_IMAGE,
LOOM_AGENT_SANDBOX_NAMESPACE, LOOM_AGENT_SANDBOX_IDLE_TTL_S, LOOM_AGENT_SANDBOX_MAX_SESSION_S,
LOOM_AGENT_SANDBOX_MAX_CONCURRENT.
Agent Memory
Settings for the agent memory bank (LOOM_AGENT_MEMORY_*):
| Variable | Default | Description |
|---|---|---|
|
|
Enable the memory bank |
|
(quota) |
Max notes per scope |
|
(quota) |
Max size of a single note |
|
(quota) |
Max total size per scope |
|
(quota) |
Max memory writes per agent run |
|
|
Allow shared (cross-user) scopes |
|
|
Mount memory read-only into the coding sandbox |
|
|
Sandbox mount path for memory |
Demo content
A fresh, empty database is populated with example content on first start — a space, some collections and libraries, a few pipelines, and a set of assets to browse. This setting decides where the pictures and clips behind those assets come from.
| Variable | Default | Description |
|---|---|---|
|
(unset) |
Directory holding the example media ( |
The demo container ships this material and points the setting at it, which is why its asset browser shows photographs and plays video. On a plain server the directory is not present and the example images are drawn at start-up instead, so the screens still have something in them. Either way the example content only ever appears in a database that was empty to begin with — it is never added to one that already holds assets.
CLI
The Loom server main entry point is LoomServerRunner. Configuration is driven via the YAML
file and environment variables shown above.
The metaloom command-line client is a separate program that talks to a running Loom server
over the REST API and has its own configuration file.