Skip to main content
XORCISE reads its configuration from ~/.xorcise/config.toml, ~/.xorcise/.env and the process environment. Every key below is settable in all three places.

Every configuration key

config.toml is a flat key-and-value file with no sections. Keys are lowercase and unprefixed. The environment variable for any key is XORCISE_ followed by the key in upper case.

Precedence

Highest wins:
CLI flags sit at the top because xorcise up and xorcise serve resolve --port, --otlp-port and --stub and then stamp the resolved values into the environment before settings are read. The rung that surprises people is the third one: .env beats config.toml. A key written in both files is read from .env. Since the web UI and the xorcise config setters all write to .env, a value you edited by hand in config.toml can be silently overridden by a value the UI wrote. An XORCISE_* variable set in your shell beats both files, including when the CLI is only talking to a brain rather than starting one. Setting XORCISE_REST_PORT in your shell is read as a deliberate “talk to this port” instruction.

Where to put each key

.env is created at mode 0600 and re-set to 0600 on every write. config.toml is world-readable. That difference is the whole rule. Put in .env: Put in config.toml: non-secret shape — role, host, and the port keys. This is what xorcise up scaffolds on first start, and it is where up appends its managed Headscale block. Both files are created by the first xorcise up and never overwritten afterwards.

Judge model keys

The judge is bring-your-own-model. XORCISE bundles none, and calls whichever OpenAI-compatible endpoint you configure.
Judge and terrain calls are billed to you by your model provider. terrain_auto_attribute defaults to true, so every run that reaches a terminal state makes attribution calls without anyone opening the run. Set it to false to keep that spend strictly to runs you view.

model_key

str · default unset · XORCISE_MODEL_KEY Your API key. With no key set, the judge half of every score degrades to 0.0 and the run is still graded deterministically. Keep it in .env.

model_base_url

str · default unset · XORCISE_MODEL_BASE_URL The OpenAI-compatible base URL. Unset means the client’s own default, https://api.openai.com/v1. Point it at a self-hosted endpoint to keep transcripts on infrastructure you control.

model_name

str · default unset · XORCISE_MODEL_NAME The model to grade with, for example gpt-4o-mini.

model_timeout_seconds

float · default 120.0 · XORCISE_MODEL_TIMEOUT_SECONDS HTTP timeout for one judge call. The default is generous so that a slow reasoning model does not report as judge-unavailable.

judge_transcript_max_tokens

int · default 0 · XORCISE_JUDGE_TRANSCRIPT_MAX_TOKENS A pre-flight ceiling on the estimated judge prompt. 0 disables it, which is the default: the token count is an estimate, so a hard gate risks rejecting prompts the provider would have accepted. Letting the provider’s own limit be the gate returns an accurate error, and the run stays re-gradeable. Set a positive value only for a small-context self-hosted judge.

judge_span_max_tokens

int · default 2000 · XORCISE_JUDGE_SPAN_MAX_TOKENS Caps each span’s body in the distilled transcript, keeping a head and tail window with an elision marker between them. Transcripts are fat-tailed — a few enormous tool-output spans dominate the count — so this collapses the tail without dropping any span. 0 disables it.

judge_tokenizer

str · default "o200k_base" · XORCISE_JUDGE_TOKENIZER The tiktoken encoding used to count the budget. Tokenizers are not interchangeable, so the choice is explicit. An unrecognised name degrades to a coarse characters-divided-by-four heuristic. For a non-OpenAI judge this is an approximation, not an exact count.

Terrain attribution keys

Terrain attribution asks a model which part of the mission map each event touched. Each key falls back to its judge equivalent when unset, so by default terrain uses the same model as the judge. The timeout is always model_timeout_seconds.

terrain_model_key

str · default unset · XORCISE_TERRAIN_MODEL_KEY Overrides model_key for attribution only. Secret; keep it in .env.

terrain_model_base_url

str · default unset · XORCISE_TERRAIN_MODEL_BASE_URL Overrides model_base_url for attribution only.

terrain_model_name

str · default unset · XORCISE_TERRAIN_MODEL_NAME Overrides model_name for attribution only. A cheaper, smaller model is a reasonable choice here.

terrain_transcript_max_tokens

int · default 256000 · XORCISE_TERRAIN_TRANSCRIPT_MAX_TOKENS A safety cap on a single attribution call. Unlike the judge, terrain attributes in bounded batches, and an oversized batch is shrunk by dropping trailing events rather than failed. Lower it for a small-context attribution model.

terrain_auto_attribute

bool · default true · XORCISE_TERRAIN_AUTO_ATTRIBUTE Attributes the terrain plane when a run goes terminal, so every run’s map is complete afterwards regardless of who looked at it. It is a no-op when no terrain or judge model is configured.

Storage keys

Each of these is empty in the model and filled at load time with a path under ~/.xorcise. Setting one moves that store.

database_url

str · default sqlite:///~/.xorcise/xorcise.db · XORCISE_DATABASE_URL A SQLAlchemy URL. This one database holds agents, runs, results, grades and the raw OTLP traces and logs. PostgreSQL needs the postgres extra and is not a tested path for normal use.

missions_root

str · default ~/.xorcise/missions · XORCISE_MISSIONS_ROOT Where ingested and pulled missions are installed.

tailscale_cache_root

str · default ~/.xorcise/cache/tailscale · XORCISE_TAILSCALE_CACHE_ROOT Where the pinned static Tailscale client is cached, one tarball of roughly 37 MB per architecture. Pre-seeding this directory lets an air-gapped agent join the tailnet with no public egress.

Mission library keys

XORCISE is connected to the hosted library when catalog_enabled is true and catalog_url is set. Both ship that way, so a default install is connected to the production catalog and the library is populated on first run. Disconnected means an empty remote library, never a fabricated one. No API key, account or sign-in is involved at any point.

catalog_url

str · default https://api.xorcise.ai · XORCISE_CATALOG_URL The library endpoint, defaulting to the production XORCISE catalog. Point it at another tier to use one, or clear it and the library reports disconnected rather than probing an unreachable host. Use catalog_enabled rather than this key to disconnect: the switch is a clean boolean, and clearing the URL is what a disconnect should not depend on.

catalog_enabled

bool · default true · XORCISE_CATALOG_ENABLED The connect and disconnect switch, written by the Settings page and by xorcise catalog connect. It is a boolean rather than an empty URL so that disconnecting cannot silently revert to the default endpoint.

catalog_key

str · default unset · XORCISE_CATALOG_KEY Declared but read by nothing. Setting it has no effect. See keys that nothing reads.

Runner keys

use_stubs

bool · default false · XORCISE_USE_STUBS Forces stub adapters so XORCISE boots without Docker. Runs created in this mode do not launch containers. xorcise up --stub sets it for one boot.

docker_platform

str · default "linux/amd64" · XORCISE_DOCKER_PLATFORM The platform passed to docker pull and docker run. Mission images are built for amd64 only, so an arm64 host must request linux/amd64 and run them under emulation, or the pull fails on a missing manifest. An empty value lets Docker choose the host platform.

Role and endpoint keys

XORCISE serves two planes: the REST API with the web UI on 3001, and OTLP/HTTP ingest on 4318. Both scan up to 50 ports above the requested one when the port is busy, printing a notice for each move, so read the ports xorcise up printed rather than assuming the defaults. There is no MCP plane and no port 3100.

role

str · default "all" · XORCISE_ROLE Which service role xorcise serve boots. See deployment roles. xorcise up always boots all.

host

str · default "127.0.0.1" · XORCISE_HOST The address XORCISE prints in URLs, health-polls and scans during preflight. It is not the bind address. In the default local topology both planes bind 127.0.0.1 and ::1, plus the Docker bridge gateway on native Linux. Setting host to any value other than 0.0.0.0 leaves that bind as it is; setting it to 0.0.0.0 is the one value that widens it to the IPv4 wildcard. See Security and isolation.

rest_port

int · default 3001 · XORCISE_REST_PORT The REST API and web UI port. xorcise up --port overrides it for one boot.

otlp_port

int · default 4318 · XORCISE_OTLP_PORT The OTLP/HTTP ingest port, matching the OpenTelemetry specification default. xorcise up --otlp-port overrides it for one boot.

runner_port

int · default 8800 · XORCISE_RUNNER_PORT Only used by the experimental runner role, which serves nothing but a health endpoint.

headscale_port

int · default 8080 · XORCISE_HEADSCALE_PORT A fallback used to derive a plain-HTTP login server when headscale_url is unset. On a normal install xorcise up always writes headscale_url, so this never applies.

Per-run network keys

xorcise up writes headscale_url, headscale_ca_cert, headscale_host_alias and headscale_advertise_host into a managed block in config.toml, delimited by # >>> xorcise headscale (managed) >>>. The block is rewritten on every up and any hand-written configuration outside it survives. Set these by hand only when you are pointing XORCISE at a control plane you run yourself, and use xorcise config set-network, which is marked experimental and needs a restart.

headscale_container

str · default "headscale" · XORCISE_HEADSCALE_CONTAINER The container name that control operations docker exec into. Control is container-based, not URL-based, which is why xorcise doctor probes the container.

orchestrator_user

str · default "orchestrator" · XORCISE_ORCHESTRATOR_USER The Headscale user that owns per-run subnet routers.

router_tag

str · default "tag:router" · XORCISE_ROUTER_TAG The access-policy tag whose auto-approvers accept each router’s advertised routes.

headscale_url

str · default "" · XORCISE_HEADSCALE_URL The login server tailnet joins use. A locally provisioned control plane sets https://<host-ip>:443. A value that does not match the managed block is treated as an external control plane, verified at boot rather than provisioned.

headscale_ca_cert

str · default "" · XORCISE_HEADSCALE_CA_CERT Path to the PEM delivered to each run’s router so it trusts the self-signed control-plane certificate.

headscale_host_alias

str · default "" · XORCISE_HEADSCALE_HOST_ALIAS A headscale.local:<ip> pair injected as the router’s extra_hosts, and the source of the host-to-IP rewrite in a join URL.

headscale_advertise_host

str · default "" · XORCISE_HEADSCALE_ADVERTISE_HOST The host per-run routers and agents dial. host is the REST bind address and is not reachable from inside the fused container’s nested network, so this is set to the Docker bridge gateway instead.

deployment_topology

"local" or "distributed" · default "local" · XORCISE_DEPLOYMENT_TOPOLOGY local binds both planes to loopback — plus, on native Linux, the Docker bridge gateway — which is the narrowest set that still lets a container-launched agent reach them through host.docker.internal. distributed binds host alone and adds an orchestrator router to the control-plane compose file. Distributed operation is unfinished; treat it as experimental.

base_network

str · default "10.200.0.0/16" · XORCISE_BASE_NETWORK The address pool each run’s subnet is carved from. Change it if it collides with your own networks.

cidr_prefix

int · default 24 · XORCISE_CIDR_PREFIX The size of each run’s carved subnet. With the defaults this gives 254 concurrent runs.

key_expiration

str · default "1h" · XORCISE_KEY_EXPIRATION Time-to-live of the single-use Headscale pre-auth keys minted for each run’s agent and router.

Run lifecycle keys

default_budget_seconds

int · default 3600 · XORCISE_DEFAULT_BUDGET_SECONDS The wall-clock budget a run gets when you do not set one. A run terminated by its budget is marked partial.

budget_watchdog_interval_seconds

float · default 5.0 · XORCISE_BUDGET_WATCHDOG_INTERVAL_SECONDS How often XORCISE scans for runs past their budget. Expired runs are terminated even when they send no traffic.

readiness_timeout_seconds

float · default 90.0 · XORCISE_READINESS_TIMEOUT_SECONDS How long a deployed run may take to bring its environment up before XORCISE closes it out as deploy_failed. Deployment does not block on bring-up, so without this gate a run whose environment died would sit non-terminal forever while the agent worked a target that never existed. 0 disables the gate.

readiness_scan_interval_seconds

float · default 5.0 · XORCISE_READINESS_SCAN_INTERVAL_SECONDS How often the readiness gate scans.

telemetry_drain_seconds

float · default 5.0, clamped to 0 through 300 · XORCISE_TELEMETRY_DRAIN_SECONDS How long OTLP ingest stays open after a run goes terminal. Short-lived harnesses commonly flush their final tool result after completing the run, and sealing before that flush rejects the last batch permanently.

attachment_ttl_seconds

int · default 300 · XORCISE_ATTACHMENT_TTL_SECONDS Lifetime of a signed attachment download URL.

Signing and telemetry keys

run_control_signing_secret

str · default "dev-insecure-signing-secret" · XORCISE_RUN_CONTROL_SIGNING_SECRET The HMAC secret that signs short-lived attachment download URLs. The default is insecure by name. Override it in .env on any host other than your own laptop, then restart XORCISE.

otel_mirror_enabled

bool · default false · XORCISE_OTEL_MIRROR_ENABLED A reserved seam for mirroring traces to your own OpenTelemetry service. No exporter is built. Setting it to true fails the boot with an explicit message rather than quietly doing nothing.

otel_mirror_endpoint

str · default "" · XORCISE_OTEL_MIRROR_ENDPOINT Reserved alongside otel_mirror_enabled. Nothing reads it.

Keys that nothing reads

Two keys exist in the settings model and have no effect. They are listed here so that setting one and seeing no change is not mistaken for a bug. headscale_port is a third near-miss: it is read, but only as a fallback that a normal install never reaches.

Deployment roles

xorcise up always boots role all and never passes a role flag, so a default install cannot reach any other role. The role surface is reachable only through xorcise serve --role, and the CLI labels it EXPERIMENTAL. Booting an experimental role prints its caveat before it does any work.

The ~/.xorcise directory

$XORCISE_HOME overrides the location. The first xorcise up creates everything below.
Never delete ~/.xorcise/headscale/.owned by hand. It is the only thing that lets xorcise down find and remove the Headscale container and its volume. Without it they keep running and no XORCISE command will clean them up again.
xorcise down removes only logs/, runtime-ports.json and xorcise.pid. xorcise down --purge removes the entire directory, including .env and your database.