Configuration

Every Loom setting: the loom.yml file, the LOOM_ environment overrides layered on top, and the resolution order between them.

Loom is configured through a YAML file (loom.yml) with environment-variable overrides layered on top. The resolution order is:

  1. Load loom.yml from the first location that exists (see below), or fall back to built-in defaults.

  2. Apply environment variables — any LOOM_* variable overrides the corresponding YAML value.

  3. 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):

  1. /etc/metaloom/loom.yml — system-wide config

  2. ~/.config/metaloom/loom.yml — user home config

  3. 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

LOOM_DB_HOST

127.0.0.1

PostgreSQL host

LOOM_DB_PORT

5432

PostgreSQL port

LOOM_DB_USERNAME

postgres

Database username

LOOM_DB_PASSWORD

finger

Database password

LOOM_DB_NAME

loom

Database name

LOOM_DB_MIN_POOL_SIZE

5

Minimum connection pool size

LOOM_DB_MAX_POOL_SIZE

20

Maximum connection pool size

Server

Variable Default Description

LOOM_SERVER_REST_PORT

8092

HTTP REST port (REST + WebSocket + UI)

LOOM_SERVER_GRPC_PORT

8091

gRPC port

LOOM_SERVER_MCP_PORT

4041

Model Context Protocol server port

LOOM_SERVER_GRPC_BIND_ADDRESS

0.0.0.0

Bind address for all listeners

LOOM_SERVER_MON_PORT

8989

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

LOOM_PROCESSOR_EXPIRY_ENABLED

true

Expire workers that stop heartbeating. Set to false when debugging a worker locally — a worker paused on a breakpoint stops heartbeating

LOOM_PROCESSOR_HEARTBEAT_INTERVAL_MS

10000

How often a worker is expected to report in; also how often Loom checks

LOOM_PROCESSOR_MISSED_HEARTBEATS

6

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

LOOM_STORAGE_UPLOAD_DIR

data/storage

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

LOOM_SIMILARITY_ENABLED

false

Enable near-duplicate lookup. While off, requests to the similar-videos routes are refused with a clear reason rather than answering "nothing found"

LOOM_SIMILARITY_INDEX_PATH

similarity-index

Directory holding the lookup index. Give each server its own directory — two servers cannot share one

LOOM_SIMILARITY_ALGORITHM

metaloom-multisector-v1

Which fingerprint version takes part in the lookup

LOOM_SIMILARITY_SCORE_THRESHOLD

0.10

How close a match must be to count as a near-duplicate. Higher is stricter

LOOM_SIMILARITY_TOPK

10

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

LOOM_VECTOR_INDEX_PROVIDER

none

Which index to use: none or lucene. An unrecognised name stops the server with a clear message rather than starting with no index

LOOM_VECTOR_INDEX_PATH

vector-index

Directory holding the index. Give each server its own directory, and keep it separate from the near-duplicate index

LOOM_VECTOR_INDEX_TOPK

10

How many matches a query returns by default

LOOM_VECTOR_INDEX_SCORE_THRESHOLD

0.35

How close a match must be to count. Higher is stricter

LOOM_VECTOR_INDEX_SYNC_INTERVAL_MS

5000

How often newly stored vectors are picked up. Set 0 to switch the background pass off

LOOM_VECTOR_INDEX_SYNC_BATCH_SIZE

500

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.
Changing the recognition model

Face vectors from different models cannot be compared, so each one is recorded with the model that produced it and queries never mix them.

To move to a new model, change the model name on the face detection node and re-run it. The new vectors are stored alongside the old ones rather than replacing them, so both remain searchable and you can compare the two before discarding either.

Authentication

Variable Default Description

LOOM_INITIAL_PASSWORD

(random)

Initial admin password (first boot only)

LOOM_TOKEN_EXPIRATION_TIME

3600

JWT token lifetime in seconds

LOOM_MCP_AUTH_ENABLED

false

Require auth on the MCP endpoints

LOOM_MCP_AUTH_STRICT_MODE

false

Reject unauthenticated MCP calls (no lenient mode)

LOOM_MCP_AUTH_ALLOWED_ORIGINS

*

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

LOOM_OAUTH2_ENABLED

Enable OAuth2 authentication (default false)

LOOM_OAUTH2_CLIENT_ID / LOOM_OAUTH2_CLIENT_SECRET

Client credentials

LOOM_OAUTH2_AUTH_URL / LOOM_OAUTH2_TOKEN_URL / LOOM_OAUTH2_USERINFO_URL

Provider endpoints

LOOM_OAUTH2_CALLBACK_URL / LOOM_OAUTH2_LOGOUT_URL

Callback and logout URLs

LOOM_OAUTH2_SCOPE

Scopes to request (default openid profile email)

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

LOOM_AI_ENABLED

true

Master switch for the chat agent

LOOM_AI_URL

http://127.0.0.1:8080/v1

Base URL of the OpenAI-compatible server

LOOM_AI_MODEL_ID

openai/gpt-oss-20b

Model id

LOOM_AI_CONTEXT_WINDOW

16384

Context window (tokens)

LOOM_AI_MAX_TURNS

8

Max tool-calling turns per message

LOOM_AI_TOOL_TIMEOUT_MS

30000

Per-tool timeout

LOOM_AI_STREAMING

false

Opt-in true token/reasoning streaming

LOOM_AI_THINK_ENABLED

(model default)

Enable model "thinking" chunks

LOOM_AI_TITLE_GENERATION

(on)

Auto-title a chat after the first exchange

LOOM_AI_CONTEXT_RESERVE_TOKENS

2048

Tokens held back from the window so the model always has room to answer

LOOM_AI_HISTORY_MAX_MESSAGES

0

Hard cap on replayed messages. 0 lets the context budget decide

LOOM_AI_COMPACTION_THRESHOLD_MESSAGES

20

New messages after which the conversation summary is refreshed

LOOM_AI_COMPACTION_MAX_CHARS

4096

Maximum length of that summary

LOOM_AI_FANOUT_MAX_ITEMS

25

Items the agent may process in one parallel batch

LOOM_AI_FANOUT_CONCURRENCY

4

How many of those run at the same time

LOOM_AI_FANOUT_CHILD_MAX_CHARS

1024

How much of each per-item answer is kept

LOOM_AI_MAX_LLM_CALLS_PER_RUN

64

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.

LOOM_AI_FANOUT_CONCURRENCY trades speed against load on your model server; raise it only if the server has headroom. LOOM_AI_MAX_LLM_CALLS_PER_RUN is the safety net on what a single message can cost — if you see the agent stopping early on large batches with a note about reaching its limit, that is the setting to raise.

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 LOOM_AI_CONTEXT_WINDOW. Rather than failing, Loom keeps the most recent exchanges verbatim and folds the older ones into a running summary it writes once and reuses. The agent is always told when this has happened, so it can ask you to repeat something instead of quietly forgetting it.

If the agent seems to lose the thread of a long conversation, raise LOOM_AI_CONTEXT_WINDOW (if your model server supports a larger one) before touching the compaction settings — more room means fewer exchanges get summarized at all.

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

LOOM_AGENT_MEMORY_ENABLED

false

Enable the memory bank

LOOM_AGENT_MEMORY_MAX_ENTRIES_PER_SCOPE

(quota)

Max notes per scope

LOOM_AGENT_MEMORY_MAX_ENTRY_BYTES

(quota)

Max size of a single note

LOOM_AGENT_MEMORY_MAX_SCOPE_BYTES

(quota)

Max total size per scope

LOOM_AGENT_MEMORY_MAX_WRITES_PER_RUN

(quota)

Max memory writes per agent run

LOOM_AGENT_MEMORY_SHARED_SCOPES_ENABLED

false

Allow shared (cross-user) scopes

LOOM_AGENT_MEMORY_MOUNT_ENABLED

false

Mount memory read-only into the coding sandbox

LOOM_AGENT_MEMORY_MOUNT_PATH

/memory

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

LOOM_DEMO_CONTENT_DIR

(unset)

Directory holding the example media (images/, videos/, persons/, users/). When unset, /demo-content and ./demo-content are tried in that order

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.

Looking for something else?