Skip to content

Configuration reference

By default, Anvil Serving reads live operator-owned TOML files from the configuration home. router.toml defines Capability Gateway routes and policy; serves*.toml, services.toml, voice.toml, host.toml, events.toml, and operator-topology.toml remain owned by the product family that operates each domain. Configuration stores environment-variable names for credentials, never credential literals.

The capability route and metadata authority are separate decisions. The operator always owns alias -> tier -> endpoint; metadata_source decides only whether the router config or that already-selected inference service owns the tier's served model, context, and allowlisted runtime facts. No setting in this file enables intent routing, candidate ranking, or fallback. See Capability meta-router.

Configuration locations

anvil-serving init creates a complete, editable starting set in the operator config home: $ANVIL_SERVING_HOME, or ~/.anvil-serving/ when that variable is unset. It copies the packaged templates mirrored from configs/ and the reference manifests into that directory. When an optional path is omitted, the runtime resolves live configuration from that config home. Explicit --config, --manifest, --registry, and --topology paths always take precedence. Public checkout examples are never live defaults. Immutable packaged catalogs, such as the shipped recipe registry, may remain read-only discovery fallbacks; write operations require an explicit or operator-owned destination.

For a real deployment, point ANVIL_SERVING_HOME at an access-controlled companion repository's host-specific operator-home/ directory. Track real topology, deployment overlays, promoted assignments, and operator recipes there. Keep credentials outside Git in environment variables or file-backed secret stores. See Public product and private operator state.

The operator files include router.toml, serves*.toml, services.toml, serve-recipes.toml, voice.toml, host.toml, events.toml, operator-topology.toml, Compose files, and .env.example. Each existing file is backed up beside the target as a numbered .anvil.bak.N file before init replaces it. Content-identical files are left untouched, so repeated runs do not create redundant backups, including for .env.example.

services.toml is the operator-owned inventory for portable launchd and Docker service bindings. It records only pinned supervisor facts and dependency paths; it does not become a model recipe or a raw process-command registry. See Host-supervised services for its schema, staged-definition installation, state contract, and model-admission boundary.

Lifecycle events (events.toml)

Lifecycle event recording is optional and disabled when $ANVIL_SERVING_HOME/events.toml is absent or its [events] table does not set enabled = true. When enabled, successful state changes from serves up, serves down, serves profile apply, and serves promote invoke the stdlib-only anvil-events CLI. Dry runs, failed operations, already-satisfied serves, and no-op profile transitions do not create false history.

[events]
enabled = true
command = "anvil-events"
node = "node-a"
producer = "node-a:anvil-serving"
root = "/var/lib/anvil/events"

command is one executable name or path and is invoked without a shell. node and producer are public event-envelope identities; the producer's first token must be the node. root is the absolute local v2 SQLite store root shared with the node's anvil-events serve process. Real operator identity and paths belong only in the private operator home. The retired v1 fields host, nats_url, and nats_url_env are rejected so an old configuration cannot silently target the removed JSONL/emit contract.

The child invocation is anvil-events --root ROOT record KIND .... It sends the JSON payload on standard input, assigns a fresh operation key, and requires machine-readable local-acceptance evidence from the CLI. The command performs no broker I/O. A successful invocation therefore means committed locally, not delivered or acknowledged by JetStream; the independent anvil-events delivery worker owns retry and PubAck evidence. If the executable is missing, times out, cannot commit SQLite, or returns malformed acceptance evidence, the lifecycle action may already be applied; the command returns non-zero and reports that the change was applied but its event was not recorded.

The seam records the frozen lifecycle kinds serve.up, serve.down, profile.enter, profile.leave, promote.applied, and promote.rolled_back. A multi-tier promotion emits one promotion record per declared affected tier while the promotion lock is still held.

By default, init asks nvidia-smi for stable GPU UUIDs and total memory, then assigns the two largest distinct cards to Compute A and Compute B. Equal-VRAM cards use canonical UUID ordering, not runtime index, so a reboot cannot swap their stable roles. Workload capability is independent of A/B placement. It asks tailscale ip -4 for this node's tailnet address. Detected values replace the corresponding template placeholders; unavailable values remain visibly unconfigured. --compute-a-gpu-uuid, --compute-b-gpu-uuid, and --tailnet-ip override individual values. --no-detect-host leaves all host placeholders in place. A one-GPU machine does not silently assign both concurrent roles to the same card.

Operator topology (operator-topology.toml)

Topology-aware commands use $ANVIL_SERVING_HOME/operator-topology.toml (default ~/.anvil-serving/operator-topology.toml) when --topology is omitted. An explicit path wins. MCP voice tools preserve their existing ANVIL_VOICE_TOPOLOGY override between the explicit argument and config-home default. Deployment overlays remain explicit through --topology-overlay.

init writes the topology beside the other operator configuration. On the reference GPU host it binds the detected Compute A and Compute B GPU UUIDs, tailnet address, and local command-host identity into that file. On the model-free Mini it can bind the Mini address when the host is identifiable as macOS. Values for another host are kept as visible placeholders rather than invented. Missing or invalid topology files fail closed; repository examples and packaged documentation topologies are never runtime fallbacks.

Machine policy (host.toml)

host.toml holds optional, machine-level lifecycle policy such as the default-off WSL cache-reclaim settings. It is always resolved from the operator config home ($ANVIL_SERVING_HOME/host.toml, default ~/.anvil-serving/host.toml). A missing file or [cache_reclaim] table is a valid disabled policy. Once configured, its fields are validated strictly before a lifecycle command can start a model operation.

GPU reservations and operating modes (serves*.toml)

Split-mode serves reserve one stable role:

gpu_role = "dark-compute-a"
vram_mib = 80000
residency = "resident"
groups = ["split-stack"]

An exclusive tensor-parallel candidate reserves both roles. vram_mib is the per-role reservation, not pooled memory:

gpu_roles = ["dark-compute-a", "dark-compute-b"]
vram_mib = 90000
residency = "on-demand"
operating_mode = "dual-gpu-exclusive"
tensor_parallel_size = 2
router_tier = "primary-local"
router_config = "{dir}/router-exclusive.toml"
rollback_router_config = "{dir}/router-split.toml"
native_kv_offload = true

The exclusive entry is inert until a separately qualified model recipe adds it to the private manifest. Ordinary serves up refuses to start it. Use serves mode preview|enter|leave, naming an explicit split restore group. Entry drains and stops every active GPU inference competitor before start; leaving stops the TP=2 owner before restoring the selected group. Active or unresolved exclusive ownership blocks manifest and ad-hoc Compose starts before container mutation. Pre-reservation model experiments are treated as GPU inference by default so they cannot bypass exclusivity. A genuine CPU-only sidecar may declare gpu_inference = false. The two cards remain separate VRAM heaps without NVLink or transparent 192 GB pooling.

A routed exclusive owner must declare router_tier, router_config, and rollback_router_config together. Both direct profiles must route the same caller aliases to that tier. The exclusive profile's tier model must equal the target serve's exact served_name; the rollback profile must match exactly one serve in the selected restore group. Mode entry starts and verifies the target, atomically installs its complete profile, waits for router health, and then guardedly readmits the tier. Any install or readmission failure stops the target, restores the rollback profile, and restores the split group. Leave performs the reverse quiesce, drain, profile, and readmission transaction. An unrouted TP=2 experiment omits all three fields and remains direct-port only.

Both router profile paths are direct dependencies of the operating-mode manifest. Manifest loading resolves {dir} and relative paths against the manifest directory and fails before lifecycle work if either file is missing.

serves up runs a post-start storage write guard on every docker serve: each read-write named-volume mount is probed as the container's real runtime identity before the readiness wait, because a serve can answer its health endpoint while unable to write its volumes (pre-created volume layouts defeat Docker's empty-volume ownership donation, leaving root-owned directories under a non-root workload). A denied mount on an unshared volume is repaired in place — re-owned to the runtime identity and the container restarted so failed initialization re-runs — then re-verified. Declare volumes this serve deliberately shares with other containers:

shared_volumes = ["comfyui-user"]

A declared-shared volume is never auto-re-owned: sharing is a deployment decision and so is its ownership, so the guard fails the serve and prints the manual command instead. A volume found shared at runtime without a declaration is reported as a topology fault in its own right. Bind mounts and read-only mounts are outside the guard's scope; deliberately non-writable storage should be mounted read-only.

Set native_kv_offload = true only when the serve recipe uses vLLM's native CPU KV-offload mmap files. This explicit ownership declaration lets serves down run the bounded, two-scan orphan cleanup even when Docker has already removed the container and its runtime metadata. An absent serve without the declaration remains a no-op. Cleanup only considers exact /dev/shm/vllm_offload_*.mmap candidates and fails closed when active owners, live mappings, a changed second scan, or an unavailable postcondition prevent a safe reclaim.

Minimal Capability Gateway

[router]
relay_timeout = 20
availability_probe_interval = 5
availability_probe_timeout = 1

[[router.tiers]]
id = "primary-local"
base_url = "http://127.0.0.1:30000/v1"
model = "served-model-name"
dialect = "openai"
context_limit = 131072
privacy = "local"
tool_support = true
auth_env = "ANVIL_PRIMARY_LOCAL_KEY"
health_path = "/health"

[router.model_routes]
llm.primary = "primary-local"

[router.model_routes] is required. Its normalized aliases are the only chat model values accepted by the gateway. Matching is case-insensitive after trimming; compatibility prefixes are not accepted. An unknown or missing alias returns 404.

Each alias must map to one configured local tier. A tier cannot stand in for a caller-visible alias; use an explicit route table. The route does not create a fallback pool.

Metadata authority modes

Mode Authority for mutable served-model facts Use when
configured (default) Router configuration Router and inference settings are released together.
upstream The already-selected single-model inference service The model or context can change independently at a stable endpoint.

Both modes use the same exact alias-to-tier route. The difference affects effective metadata, request admission, and the served model id relayed to that endpoint; it never affects tier selection.

Inference-owned model metadata

Use metadata_source = "upstream" when an operator changes the model or context at a single-model OpenAI-compatible inference endpoint independently of the router:

[[router.tiers]]
id = "secondary-local"
base_url = "http://100.64.0.10:39038/v1"
dialect = "openai"
metadata_source = "upstream"
privacy = "local"
tool_support = true
auth_env = "ANVIL_SECONDARY_LOCAL_KEY"
health_path = "/health"
max_concurrency = 1

[router.model_routes]
llm.secondary = "secondary-local"

A direct tier may also set max_concurrency = "auto": the router then derives the tier's dispatch ceiling from the serving engine's own declared scheduler concurrency instead of a hand-maintained integer. The router reads the bounded, read-only runtime-info surface of the engine root (SGLang GET /get_server_info, vLLM GET /server_info; allowlisted keys max_running_requests / max_num_seqs only) on the availability-probe interval and applies the reported value as the in-flight cap. Until the first successful report the tier is uncapped (the engine still enforces its own limit); a transport or shape fault keeps the last known ceiling. Effective changes are logged once each. "auto" is direct-tier only: replica members and replica aggregate ceilings require explicit integers.

Do not set model, context_limit, engine, quantization, model_identity, or params.fingerprint on an upstream-owned tier. The router probes health and requires exactly one entry from GET /v1/models. It reads the served identity and context from that model card when available. For llama.cpp it also reads the bounded, read-only GET /props response for n_ctx, quantization, build, slot, and modality facts. A missing context, ambiguous catalog, malformed value, or identity disagreement fails closed.

The result is cached for availability_probe_interval seconds. After that bounded interval, replacing the model or context at the same endpoint updates request admission and the router's model metadata without a router config edit. The public alias and its exact tier mapping do not change, and a failed request is never retried against another model. max_output_tokens, tool_support, params.capabilities, and media-admission limits remain router safety policy rather than inferred model claims.

[server]

[server]
auth_env = "ANVIL_ROUTER_TOKEN"

auth_env is optional for loopback development. When configured, callers must send its resolved value as a bearer token or x-api-key. Expose non-loopback routers only with token authentication.

Token persistence contract

Token configuration names environment variables; the values must also survive a host reboot. Store each token line in the gitignored operator .env chain — $ANVIL_SERVING_HOME/.env, then ~/.env — and treat the process environment as an override, never the only copy. Router and controller token resolution reads the shell environment first and falls back through that chain, so a freshly rebooted host can authenticate without re-exporting variables in a live shell. Refusals name every location that was checked. Values never appear in tracked files (ADR-0032).

Durability sinks (ADR-0033)

Key Default Meaning
admission_state_path unset Opt-in persisted tier and member quiesce intent. Quiesced tiers (except promotion-owned quiescence) and declared member intent are restored at boot; tier and member readmission remain independent, and readmission still requires the applicable health+identity gate. A corrupt file refuses to serve.
decision_log_path unset Opt-in append-only, metadata-only JSONL of decision records (timestamped), size-capped with one rotated generation.

Both point at writable paths on the router state volume in containerized deployments. Unset keys mean no file I/O.

[router]

Key Default Meaning
relay_timeout 20 Default upstream request timeout in seconds.
availability_probe_interval 5 Seconds to cache a local tier's readiness result.
availability_probe_timeout 1 Per-readiness-probe timeout in seconds.
availability_probe_max_bytes 65536 Maximum readiness response bytes read.
exhaustion_status 503 Status returned for an unavailable or admission-exhausted selected tier.

[[router.tiers]]

Every chat tier needs id, dialect, privacy = "local", tool_support, and auth_env. The default metadata_source = "configured" also requires model and context_limit. An upstream-owned tier instead requires metadata_source = "upstream" and health_path, and omits both values as described above. A direct tier requires base_url; a replica tier instead requires the member contract below. A direct tier's base_url is an OpenAI- or Anthropic-compatible base URL; use 127.0.0.1, never localhost, for same-host serves. Optional health_path, timeout, max_concurrency, max_output_tokens, context_admission, extra_body, and extra_body_defaults control relay behavior. strip_reasoning_history = true is an opt-in policy for a qualified OpenAI-compatible endpoint: remove prior assistant reasoning_content, reasoning, and reasoning_text fields before forwarding, including within a tool cycle. Visible content, tool calls/results, and generation settings remain intact; unfinished reasoning-only messages are omitted. The policy applies to streamed and buffered requests after extra-body merging, so client extensions are not required. It defaults to false, does not change generated reasoning, and must be requalified when the backing model changes. It does not remove thinking embedded in visible text or encrypted reasoning data. engine, quantization, and params are descriptive serve metadata.

context_admission = "estimate" is the default. It rejects a text request before relay when the router's stdlib-only conservative token estimate exceeds the tier's context window. For a directly selected, exact-identity local serve whose inference engine enforces context with the model's real tokenizer, context_admission = "upstream" delegates that one text boundary check to the same selected endpoint. This opt-in requires either model_identity = true or metadata_source = "upstream"; it never enables a retry, fallback, or alternate model. Router usage and decision counters remain estimates. Explicitly enabled media admission remains router-enforced because it also owns declared visual token and media-count policy.

max_output_tokens is an optional per-tier runtime safety ceiling. When a caller requests a larger completion budget, the router forwards the request with the configured ceiling and returns Warning, X-Anvil-Warning, X-Anvil-Max-Tokens-Requested, and X-Anvil-Max-Tokens-Applied response headers. It also records served_output_clamped in the metadata-only decision trail. Tiers without this field preserve caller and upstream behavior.

The tier's model is the upstream served model name. It is not the public capability name.

Qualified same-host replicas

A replica tier has no direct base_url; it declares 2–16 members on one host_id. The alias still maps to the logical tier exactly once. A member is selected only after its independent readiness and tier/member admission pass; the router does not use replica membership as an alias fallback or model/runtime substitution mechanism.

[[router.tiers]]
id = "primary-replicas"
metadata_source = "configured"
model = "primary-model"
dialect = "openai"
context_limit = 4096
privacy = "local"
tool_support = true
auth_env = "ANVIL_PRIMARY_KEY"
health_path = "/health"
model_identity = true
replica_strategy = "capacity"
max_concurrency = 3 # aggregate tier ceiling; never multiplied by members
replicas = [
  { id = "member-a", base_url = "http://127.0.0.1:30000/v1", host_id = "host-a", resource_id = "gpu-a", qualification_ref = "qualification:primary-a", max_concurrency = 1 },
  { id = "member-b", base_url = "http://127.0.0.1:30001/v1", host_id = "host-a", resource_id = "gpu-b", qualification_ref = "qualification:primary-b", max_concurrency = 2 },
]
replica_identity = { model_revision = "revision-1", engine_version = "engine-1.0", image_digest = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", config_fingerprint = "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }

[router.model_routes]
llm.primary = "primary-replicas"

Replica tiers require configured model metadata and exact served-model identity checks. The shared revision, engine version, image digest and config fingerprint are declared deployment identity, not runtime attestation. qualification_ref is an opaque evidence reference; parsing it does not open or verify the referenced qualification. Member readiness proves only the bounded health and served-model checks. Discovery and capacity views retain deployment_identity_source = "declared" and runtime_deployment_identity_verified = false even when every member is ready.

Before activation, run topology validate-router-config to check member ownership and endpoint declarations against the selected topology. The offline result describes one exact-byte config snapshot, not live deployment. Managed router config installation captures and validates its own snapshot before any transition, then installs those same bytes; a previous CLI check is not an activation token. Unsupported single-serve promotion and mode shortcuts refuse replica tiers; see replica lifecycle limits.

replica_strategy defaults to round_robin; an explicit strategy is invalid on a direct tier. In capacity mode every member requires an integer max_concurrency from 1 through 100000. A configured member ceiling is still an admission limit in round-robin mode. Tier max_concurrency is the aggregate limit; when omitted in capacity mode, the aggregate is the sum of member ceilings.

Round-robin starts at the lexically first eligible member ID and rotates deterministically across eligible members. Config order is not routing priority.

Capacity selection uses exact local reservation ratios, then conservative bounded upstream pressure, then a rotating member-ID tie. Fresh pressure is no older than five seconds; stale, failed, missing, or malformed samples rank as unknown rather than zero. The bounded cache has at most two workers and a capacity read does not cause a refresh. These observations help order already eligible members; they do not prove qualification, deployment, or model identity.

Member controls require both tier and member IDs and remain scoped: quiesce, drain, readmit, and transition-status accept --member with --tier. Member drain waits only for that member and never cancels in-flight work. Tier readmission does not clear member intent; member readmission checks only that member's readiness. Persisted admission_state_path state may include a members mapping alongside tier intent, so declared member quiesce survives a restart without changing tier scope. These are explicit operator controls, not automatic lifecycle recovery.

Each accepted request reserves one tier/member lease and makes one upstream attempt. Backend errors, timeout, cancellation, disconnect, and SSE closure do not retry another member; they retain the lease through terminal closure before releasing it. DecisionLog scheduler scores are pre-reservation historical selection evidence, not current counts; current capacity gauges are not a qualification result.

Capacity metadata

GET /v1/models/capacity exposes an allowlisted subset of descriptive metadata from params.capacity, joined with readiness and bounded live /metrics values from the serving engine:

engine = "vllm"
quantization = "nvfp4"
max_concurrency = 1
params = { capacity = { gpu_role = "dark-compute-a", gpu_name = "NVIDIA RTX PRO 6000 Blackwell Max-Q Workstation Edition", gpu_memory_total_mib = 97887, model_memory_gib = 73.22, kv_cache_capacity_tokens = 571950, scheduler_max_num_seqs = 1, image_limit = 1, video_limit = 0 } }

The allowlisted capacity keys are gpu_role, gpu_name, gpu_memory_total_mib, model_memory_gib, kv_cache_capacity_tokens, scheduler_max_num_seqs, image_limit, and video_limit. Other params values are never returned. These values are operator-declared measurements, not runtime discovery. On an upstream-owned tier, keep only stable host or router-policy facts here; do not copy mutable model, KV-cache, or scheduler values that can drift when the inference service changes.

image_limit and video_limit become enforced request admission controls only when media_admission_enabled = true. An enabled policy must also provide non-negative image_tokens_estimate and video_tokens_estimate values. The router then rejects media-count overflow before contacting the selected tier and reserves the estimates when checking context headroom. A declared zero limit therefore rejects the first matching media block; without the explicit enable flag, the values remain metadata for backward compatibility.

The endpoint accepts model and gpu_role filters. Optional images, input_tokens, image_tokens, and output_tokens parameters evaluate a request scenario. Image count alone cannot establish context use because image resolution and preprocessing determine visual token count, so a scenario with images reports context admissibility only when total image_tokens is supplied.

params.capabilities is the allowlisted client-facing declaration used by GET /v1/models/capabilities: modalities, nested thinking fields (supported, default, caller_override, and optional max_tokens), images_per_request, video_per_request, and a nested compat block. The compat block carries OpenClaw-compatible capability declarations: supportsUsageInStreaming, supportsStrictMode, and supportedReasoningEfforts (the exact OpenClaw key, an ordered set of lowercase effort labels such as ["low", "high", "max"] that a tier honors). Set supportsUsageInStreaming to true when a tier's streaming path can emit a usage chunk (so metering clients look for one), supportsStrictMode to true when a tier honors strict JSON-schema structured output, and enumerate the reasoning-effort levels the tier honors under supportedReasoningEfforts. OpenClaw treats the effort list as a membership set, not an ordered ladder; order is cosmetic. This is a declaration surface: an operator maps these to the model's compat in OpenClaw config (OpenClaw does not auto-read this endpoint).

params.fingerprint supplies optional identity evidence for GET /v1/models/fingerprints: model_revision, engine_version, image_digest, and config_fingerprint. Unknown fields in either section stay private. The endpoint reports missing evidence as null; it never fabricates a digest or revision. An upstream-owned tier forbids this configured fingerprint and instead reports allowlisted live values under served_configuration.

See the router observability API for the full read-only surface and privacy boundary.

Purpose models and audio

[[router.purpose_models]] maps an exact model name to an embedding or rerank serve. It is separate from chat aliases and exposes /v1/embeddings or /v1/rerank. See Embeddings and reranking for the field contract, request examples, and why this surface routes by model name.

[[router.audio_routes]] maps a named, operator-owned STT or TTS serve to the normalized /v1/audio/transcriptions or /v1/audio/speech gateway. Audio routes remain separate from chat and purpose-model routing.

Named media workflows

Media generation is configured separately from chat, purpose-model, and audio routes. Each immutable workflow descriptor names one stable ID and version, one media kind, one bounded parameter schema, one graph digest, and exactly one logical media-service target. A descriptor may also define a bounded set of quality profiles. Every profile owns the same declared parameter names and maps them to exact validated values; callers select a returned profile name and cannot override those values. Operator topology resolves that target to one resource owner; neither a profile nor the descriptor contains a private endpoint or fallback list.

Discovery retains the complete legacy schema and additionally publishes a profiledParameterSchema with profile-owned fields removed. Profile-aware callers use that second schema and pass quality_profile; the CLI exposes the same contract through media workflow run --quality-profile and media qualify run --quality-profile.

The initial image workflow exposes draft (512 by 512), standard (768 by 768), and high (1024 by 1024), all at four distilled steps. standard is the explicit default. These labels describe execution settings, not a subjective quality guarantee, and they never select a different workflow, model, host, or provider. The selected profile is persisted with the durable job. Job status reports gateway-observed accepted-to-queued, submission, queue, generation, approval-wait when applicable, and end-to-end latency from its durable events. Those phase values are poll-observed bounds, not backend kernel timings; media qualification separately records caller wall-clock latency. Qualification also fails when decoded output dimensions differ from the selected profile's exact width or height.

The first-release limits and qualification blockers are frozen in ADR-0041. Public workflow descriptors are candidates until compatibility, functional, capacity, artifact, rollback, license, and independent quality evidence make them available. Missing values remain blockers rather than runtime defaults.

Gateway media credentials are environment-variable references and carry explicit scopes such as media:read, media:submit, and media:cancel. Lifecycle approval uses the existing operator confirmation contract; a media credential never inherits controller-wide authority. Artifact storage and job state paths belong in the private operator home and are not exposed by workflow discovery.

Install or reconcile the packaged Hermes skill and its media-only MCP catalog with anvil-serving harness sync hermes-media --dry-run, then apply the exact preview with --confirm. The operation backs up every changed profile, writes only environment-variable references, validates each Hermes profile, and must produce an empty second preview. Image artifact inspection may include native MCP image content up to six binary MiB. Base64 expansion remains below the ten-MiB controller/SDK framing bound; larger images and all video remain authenticated resource-only.

When [server].media_principal enables the gateway surfaces, the process reads the media runtime only from environment variables:

Variable Purpose
ANVIL_MEDIA_BACKEND_URL Required ComfyUI adapter endpoint selected by operator topology.
ANVIL_MEDIA_WORKFLOW_REGISTRY Optional path to the pinned public workflow registry.
ANVIL_MEDIA_STATE_DB Durable job and lifecycle database. The gateway and its same-host lifecycle controller must resolve the exact same local path. Never place this SQLite database on a network filesystem.
ANVIL_MEDIA_ARTIFACT_ROOT Private retained-artifact directory.
ANVIL_MEDIA_CONTROLLER_URL Optional same-host orchestration-controller MCP origin used to preview and approve a cold worker start.
ANVIL_MEDIA_CONTROLLER_TOKEN Controller credential paired with the controller URL; the value is never stored or returned.
ANVIL_MEDIA_SERVE_MANIFEST Optional orchestration-controller manifest name for media-worker tools. It must be empty or a basename such as serves.comfyui.toml; paths are rejected. Empty selects the normal default manifest.
ANVIL_MEDIA_RESOURCE_CONTROLLER_URL Optional resource-owner controller MCP origin used by the lifecycle controller for bounded serves_* operations.
ANVIL_MEDIA_RESOURCE_CONTROLLER_TOKEN Resource-owner controller credential paired atomically with its URL; keep the value only in secret-backed environment state.

Each URL/token pair must be set together. If a selected worker is cold, the gateway atomically reserves the job and calls a co-located lifecycle controller that opens the same host-local ANVIL_MEDIA_STATE_DB. That controller returns only a dry-run media_worker_prepare receipt, and the gateway returns awaiting_approval with a bounded operator action. The exact safe manifest name is stored in both the lifecycle transaction and operator action; retries or teardown requests with a different manifest fail closed.

For a worker on another host, the lifecycle controller then calls that host's resource-owner controller through ANVIL_MEDIA_RESOURCE_CONTROLLER_URL. The resource owner executes only declared serves_status, serves_manage, and serves_logs tools and never opens the gateway's SQLite database. This two-hop layout preserves single-writer, host-local SQLite state while keeping lifecycle mutation at the declared resource owner.

The gateway cannot apply the action. An operator must invoke the typed controller tool with dry_run=false, confirm=true, and human_approved=true; the caller then retries the same workflow and idempotency key after the worker becomes ready. The gateway submits that one durable job once and its reconciliation loop captures completed output into the opaque artifact store. Missing controller configuration fails the reserved job closed without contacting the backend.

Reference files

  • configs/example.toml: direct local Primary and voice aliases.
  • configs/example-docker.toml: the same topology for a Compose-network router.

These files are public templates, not live deployment defaults. They contain generic identities and must not be edited to match an operator workstation.

The removed cloud-routing and mode-manifest examples are intentionally not supported by the Capability Gateway. Send cloud traffic through the owning harness, not through this local capability boundary.