Skip to content

09 — Configuration

This is the configuration reference: how to change the Engine's behavior without touching code, which knob lives in which layer, and whether a change needs a restart.

The Aetherium Engine reads its settings from two worlds. One is file-based — the appsettings*.json files and environment variables that ASP.NET Core resolves at startup, the home for paths, connection strings, tier limits, and infrastructure wiring. The other is the SystemSettings table in PostgreSQL — runtime-mutable knobs you change from the Settings page with no restart, the home for model selection, container timers, and feature flags. The dozen "see §09 for config" cross-links from earlier sections all land somewhere in those two worlds.

This section is organized precedence-first — the layers and how a value resolves — then by concern, with a curated key table per area. The live ground truth is always appsettings.json in the server project and the SystemSettings table itself; the tables below are curated to the keys you will actually reach for, not exhaustive, and the full key list defers to the Technical Reference doc set.

How a value resolves

File-based configuration is layered. ASP.NET Core builds it lowest-to-highest, and a key set in a higher layer wins:

flowchart TB
    A["appsettings.json<br/>committed defaults"]:::base --> B["appsettings.{Environment}.json<br/>per-environment overlay"]
    B --> C["Environment variables"]
    C --> D["appsettings.Local.json<br/>your gitignored overrides"]
    D --> E["SystemSettings table<br/>runtime knobs · no restart"]:::top

    classDef base fill:#D5C3B0,stroke:#2D2D2D,stroke-width:1.5px,color:#1A1A1A
    classDef top fill:#B87333,stroke:#2D2D2D,stroke-width:2px,color:#FFFFFF

In priority order, lowest to highest:

  1. appsettings.json — the committed defaults. What ships in the box.
  2. appsettings.{Environment}.json — a per-environment overlay (appsettings.Development.json in development). Loaded automatically based on ASPNETCORE_ENVIRONMENT.
  3. Environment variables — standard .NET environment configuration.
  4. appsettings.Local.json — your machine's gitignored overrides, added explicitly after the host defaults so it sits above environment variables. This is the highest file-based layer.
  5. SystemSettings (database) — not part of the file pipeline at all. Services read these rows at runtime through a settings service, and for the keys it governs a meaningful value here overrides whatever the file layers resolved.

That last point is the one subtlety worth internalizing. There is no single resolver that merges the two worlds; each service decides. The pattern is an emptiness gate: a service reads its SystemSettings row, and if the value is set and meaningful it uses it, otherwise it falls back to the file-based value. Storage paths are the clearest example — the WorkspaceRootPath row ships empty, so the Engine falls back to the Aetherium:Storage:WorkspaceRoot file value; set it in the UI and the row wins. The model chain works the same way: an empty AI_ModelId row is treated as "not set" and skipped, so leaving it blank is how you defer to a template's own model choice rather than forcing one globally.

appsettings.Local.json outranks environment variables

Because the Engine appends appsettings.Local.json after the standard host configuration, a value in that file beats the same value set as an environment variable. If you expected env vars to win, this is the gotcha.

The per-environment overlay (layer 2) deserves one caveat. The host loads appsettings.{ASPNETCORE_ENVIRONMENT}.json by name — appsettings.Development.json when the environment is Development. A repo can carry an appsettings.Linux.json, but it is not loaded just because you are on Linux; it only takes effect if ASPNETCORE_ENVIRONMENT is literally set to Linux. If a Linux-specific override seems to be ignored, check the environment name first.

Changing each layer

Layer Where you change it Restart required?
appsettings.json Committed file in the server project Yes
appsettings.{Environment}.json Committed per-environment file Yes
Environment variables Shell, host, or container environment Yes
appsettings.Local.json Gitignored file on your machine Yes (bound at startup)
SystemSettings Settings page, or POST /api/settings No — takes effect on the next read

SystemSettings is the only runtime-mutable layer. The container cleanup loop, for instance, re-reads its thresholds at the top of every cycle, so a change you make in the UI is picked up within one interval without a restart.

The SystemSettings table

A SystemSettings row is deliberately simple: a Key (the primary key), a Value stored as a string, an optional Description, and a Group. Values are always strings — a service reading Container_IdleTimeoutMinutes parses it as an integer, one reading a multi-instance config deserializes it as JSON. The Group exists to organize the Settings page; the groups in use today are Projects, Knowledge, Containers, Features, AI, ComfyUI, LoadBalancing, General, and Theme.

The table is runtime-mutable because there is no cache between a read and the database — each lookup opens a fresh context and returns the current row, so a saved change is visible to the next read. You change rows two ways: the Settings page in the UI, or the API — POST /api/settings upserts a key (there is no separate update verb), GET /api/settings/group/{group} reads a whole group, and GET /api/settings/{key} reads one. Infrastructure rows (the container timers and retention counts) ship with seeded defaults; the AI_* model keys are not seeded — they come into being the first time you save them, and until then the code falls back to its built-in defaults.

The Aetherium config section

The structured heart of file-based config is the Aetherium section. Its three load-bearing subsections — Storage, Tiers, and Cleanup — look like this (trimmed):

"Aetherium": {
  "Storage": {
    "WorkspaceRoot": "${AETHERIUM_WORKSPACE_ROOT}",
    "GlobalKnowledgePath": "${AETHERIUM_KNOWLEDGE_PATH}",
    "ArchivePath": "${AETHERIUM_ARCHIVE_PATH}",
    "TempPath": "${AETHERIUM_TEMP_PATH}"
  },
  "Tiers": {
    "Production": { "RetentionDays": -1, "MaxProjects": -1,
      "Resources": { "CpuLimit": "4.0", "MemoryLimit": "8g", "DiskQuota": "50g" } },
    "Staging":    { "RetentionDays": 30, "MaxProjects": 50,
      "Resources": { "CpuLimit": "2.0", "MemoryLimit": "4g", "DiskQuota": "20g" } },
    "Temp":       { "RetentionDays": 7,  "MaxProjects": 20,
      "Resources": { "CpuLimit": "1.0", "MemoryLimit": "2g", "DiskQuota": "5g" } }
  },
  "Cleanup": {
    "ArchiveRetentionDays": 30,
    "CompressArchives": true
  }
}

The ${AETHERIUM_WORKSPACE_ROOT} tokens are the interpolation pattern. At startup the Engine expands any ${ENV_VAR} token in the four storage paths against the environment; if the variable is unset, it substitutes a platform-aware default — C:\aetherium\… on Windows, /var/aetherium/… on Linux. This is what lets one committed file work across machines: set the environment variable per host, or override the path outright in appsettings.Local.json.

Aetherium:Cleanup is mostly inert for container timers

Only ArchiveRetentionDays from this block is read at runtime (by the workspace cleanup service). The idle, destroy-grace, and snapshot timers that also appear here are shadowed by SystemSettings rows of the same name — change those in the Settings page, not in this file.

The appsettings.Local.json override

This is the file you will edit most: the place for your connection string, your machine's paths, and any secret. Secrets use the repo convention REPLACE_ME_VIA_LOCAL_OVERRIDE — never commit a real value.

{
  "ConnectionStrings": {
    "DefaultConnection": "Host=localhost;Port=5432;Database=aetherium_refactor_db;Username=aetherium_user;Password=REPLACE_ME_VIA_LOCAL_OVERRIDE"
  },
  "Aetherium": {
    "Storage": {
      "WorkspaceRoot": "D:/aetherium/workspace",
      "GlobalKnowledgePath": "D:/aetherium/knowledge",
      "GitHubToken": "REPLACE_ME_VIA_LOCAL_OVERRIDE"
    }
  }
}

The file is optional and gitignored; if it is absent, the layers below it still resolve. See §02 — Getting Started for the first-run setup and §14 — Deployment for the deploy-time story.

Keys by concern

Each group below is a curated table — key / what it controls / default / which layer it lives in. The complete list lives in appsettings.json and the live SystemSettings table.

Storage & paths

Key Controls Default Layer
Aetherium:Storage:WorkspaceRoot Project workspace root ${AETHERIUM_WORKSPACE_ROOT} → platform default appsettings
Aetherium:Storage:GlobalKnowledgePath Global knowledge base path ${AETHERIUM_KNOWLEDGE_PATH} → platform default appsettings
Aetherium:Storage:ArchivePath Archive destination ${AETHERIUM_ARCHIVE_PATH} → platform default appsettings
Aetherium:Storage:TempPath Temp workspace ${AETHERIUM_TEMP_PATH} → platform default appsettings
WorkspaceRootPath / DefaultProjectPath Runtime overrides for the above empty (falls back to appsettings) SystemSettings

The SystemSettings path rows ship empty on purpose, so the file-based values win until you override them in the UI. In development, the committed appsettings.Development.json overlay typically points the workspace and knowledge paths at a local drive so a fresh clone runs without touching the system-wide defaults. Platform-aware defaults and interpolation are covered above; deployment paths are in §14 — Deployment.

AI & models

Model selection is the most consequential configuration in the Engine, and it resolves through a chain. When the Engine picks the model for an agent's turn, it evaluates these in order and takes the first that yields a usable local model — a candidate that is empty, "default", or a cloud-prefixed name (gpt-, claude-, o1-, …) is skipped:

  1. Node model — the agent's model_id in the Workflow Template (§08).
  2. Registry routing — the best model for the node's agent_type, looked up in the benchmark model registry, when the node declares one.
  3. Worker default — the AI_WorkerModelId setting, applied to non-supervisor agents only (supervisors skip this tier).
  4. Team default — itself a sub-chain: the operator's AI_ModelId setting, then the template's team.config.model, then whatever LM Studio currently has loaded, then the literal sentinel local-model.

This extends the practical chain §08 sketched (node → team model → operator → engine default): the direction is the same, with the registry and worker-default tiers inserted between, and the clarification that inside the team default the operator's AI_ModelId outranks the template's team.config.model.

Concretely: fullstack-web pins its supervisor to a reasoning model and its workers to a coding model via per-node model_id, so the chain resolves at tier 1 for every agent and never consults your operator default. Strip those node-level pins out and the same team would fall through to tier 4 — your AI_ModelId if you have set one, otherwise the template's team.config.model, otherwise whatever LM Studio happens to have loaded. The cloud-prefix gate means a stray gpt-4o left in any tier is simply skipped rather than dispatched to an endpoint that cannot serve it.

Key Controls Default Layer
AI_ModelId Operator-wide default model (team-default tier) empty SystemSettings
AI_WorkerModelId Default for non-supervisor workers empty SystemSettings
AI_SupervisorModelId Model for Layer-2 knowledge-gap detection only falls back to AI_ModelId SystemSettings
AI_Layer2WorkerModel Model for spawned research subagents inherits parent SystemSettings
AI_BaseUrl LM Studio inference endpoint (runtime chat path) http://localhost:1234/v1 SystemSettings
team.config.context_window Per-agent context-window token budget 16384 Template
team.image_model The team's image model: qwen for Qwen-Image 2.1, z-image for Z-Image Turbo, unset for SDXL unset; qwen in the four image templates and in generated image teams Template
AI_RepetitionRuleArmed Lets the deterministic repetition rule bounce a looping agent, at most twice per loop; off, it only logs [REP-RULE] false SystemSettings
Aetherium:Decision:Enabled / :Shadow The decision layer's semantic-repetition guard; Shadow asks and logs to decision_logs but never bounces false / false appsettings
ModelRegistry:Path Path to the benchmark model registry consumed by tier 2 built-in default appsettings / env
Aetherium:ModelRegistry:FailOnMissing Override the startup registry check in either direction (unset → follow intent) appsettings
Finishing_DirectorModel / Finishing_DirectorInstance Model and instance for the finishing team's director and designer the template's pins SystemSettings
Finishing_WorkerModel / Finishing_WorkerInstance Model and instance for its researchers and writer the template's pins SystemSettings

The AI_* keys are SystemSettings rows, not file config, and several are not seeded — they come into existence when you save them from the Settings page. AI_SupervisorModelId is narrower than its name suggests: it feeds only the Layer-2 gap-detection call, not the main supervisor turn (which uses the team default). A team whose default model is a reasoning model gets wider circuit-breaker ceilings — more turns per step, longer step timeouts — because those models spend turns on the reasoning itself; the values live in thinking_model_overrides on the team's circuit_breaker (see §06 — Key Services). Whether a model counts as one is not configurable: it comes from the Engine's model-capability table, the same one that decides how thinking is suppressed for a given model family. Until 2026-09-08 the opt-in was a separate thinking_model_pattern regex on the circuit-breaker config; two places answered the same question and could disagree, so the regex was removed. Two nuances worth knowing: the check is keyed on the team's default model, not each agent's resolved model, so pinning a single worker to a reasoning model via AI_WorkerModelId will not by itself widen the ceilings; and not every limit in the override block is wider than the base — the session tool-call budget and the session timeout are tighter.

Tier 2 depends on the registry actually loading, and it now says so at startup. Previously a ModelRegistry:Path that resolved to a file which was not there logged a warning, substituted an empty registry, and let every lookup return nothing — so routing quietly fell through to the later tiers while still being described as benchmark-driven. The rule now follows intent: a path that was asked for and is not there stops the process; no path at all is a machine that does not use benchmark-driven routing, which is legitimate, so it is loud but survivable. Aetherium:ModelRegistry:FailOnMissing overrides both directions — true makes an unconfigured registry fatal in production, false gets you past a known-broken state during a bisect. A registry that loads but contains zero models is warned about separately, because its presence makes it look healthy while routing behaves exactly as if the file were missing. GET /api/model-registry/status reports the same verdict at runtime.

The four Finishing_* rows are the same idea applied to one template: they override the models the finishing-team preset pins, without editing the template, so a pass can be re-run against different models from the Settings page. They come in pairs on purpose — see the next paragraph.

Which model runs is separate from which instance runs it. After the chain above picks a model, the Engine routes the call to an LM Studio instance — the node's preferred_instance if it declares one, otherwise a smart-instance choice that scores the configured instances by what they already have loaded. A node that names a model but no instance is first moved, at run start, to the slot Aetherium:VramOrchestration:DefaultNodeInstance names (Worker as shipped) when that slot's standard set holds exactly one model its id matches, and then carries that slot as its preferred_instance; a model the slot does not hold leaves the node as it was. A declared preferred_instance is a pin: if that instance cannot serve the model, the call fails with the reason rather than moving to another instance or running on a different model. The smart choice is only a hint, and the scheduler may still re-route it to an instance that serves the same model, trying alternates in slot order. That decision belongs to the scheduler, covered in §06. Model calls that belong to no node — the step verifier, the trajectory judge, compaction, summaries, lessons, code_review, Test Drive, the team generator, embeddings and a few more — go to the slot Aetherium:RolePlacement names for that role (binding when the entry sets Pin), else to their built-in default; while that slot is known down (its last two health probes failed), an unpinned one runs on Aetherium:LMStudio:OfflineFallbackSlot instead, when that is set.

tool_defaults is not a configuration key

Argument-layer defaults are supplied per orchestrator run as the API's toolDefaults field, not through any config layer or SystemSettings row. They have no key here; the runtime path is covered in §06 and §07.

Containers

Key Controls Default Layer
Container_IdleTimeoutMinutes Idle → stop threshold 60 SystemSettings
Container_DestroyGracePeriodMinutes Stopped → destroyed grace 30 SystemSettings
Container_CleanupIntervalMinutes Cleanup poll interval 5 SystemSettings
Container_AutoSnapshotBeforeCleanup Snapshot before an idle stop (Production and Staging tiers; Temp is never snapshotted) true SystemSettings
Container_MaxSnapshotsPerProject Snapshot retention count 5 SystemSettings
Container_SnapshotRetentionDays Snapshot retention window 30 SystemSettings

The container lifecycle timers and snapshot policy are all SystemSettings rows, read fresh each cleanup cycle — this is the runtime-mutable layer in action. Before an idle container is stopped the Engine snapshots it (a docker commit); that is on by default for Production and Staging projects, never for Temp-tier ones, and those three snapshot rows govern whether it happens and how long the snapshots are kept. A template can override the base image and opt out of cleanup entirely with persistent_container (§08). The cleanup services themselves are described in §06.

Knowledge

Key Controls Default Layer
ChromaDb:Endpoint ChromaDB REST endpoint http://localhost:8000 appsettings
ChromaDb:ScoreFloor Lowest cosine similarity a retrieved chunk may have 0.525 appsettings
ChromaDb:Image The pinned Chroma image the health rag block reports chromadb/chroma:1.5.2@sha256:… appsettings
Aetherium:Embeddings:ModelId Model id the knowledge index embeds with text-embedding-nomic-embed-text-v1.5 appsettings
Aetherium:Embeddings:RawModelId Model id of the untemplated path lessons use text-embedding-nomic-embed-text-v1.5 appsettings
Aetherium:Embeddings:PrefixScheme / QueryTemplate / DocumentTemplate Task prefixes: search_query: {text}, search_document: {title}\n\n{text} nomic-v1 appsettings
Aetherium:Embeddings:BatchSize / MaxInputChars Inputs per embedding request; longest templated input 32 / 6000 appsettings
AI_KnowledgeInjection false turns off knowledge injection into prompts (benchmark mode always does) on SystemSettings
Knowledge_ActiveGeneration Which generation of collections reads and writes use 0 SystemSettings
Aetherium:KnowledgeIndex:WorkRoot Where each index rebuild writes its records (the old generation's export, the new generation's manifest, the report) /var/lib/aetherium/kb-rebuild appsettings
Aetherium:KnowledgeIndex:KbRoot Durable copies of promotion candidates (candidates/p<project>/) and of approved domain documents (domains/<domain>/), with a media document's picture or recording beside its text, and the caption and transcript cache (media/) /var/lib/aetherium/kb appsettings
Aetherium:KnowledgeIndex:SelfRetrievalGate / KnownItemGate / OverlapGate / FingerprintGate Checks a rebuilt generation must pass before it goes live 0.9 / 0.9 / 0.95 / 0.999 appsettings
Aetherium:KnowledgeIndex:LeaseWaitMinutes / PausePollSeconds How long a build waits for the embedding slot; how often it re-checks while a run holds that slot 60 / 15 appsettings
Aetherium:Media:CaptionModel / CaptionSlot The vision model that captions pictures for the knowledge base, and the slot it loads on under a batch lease qwen/qwen3.5-9b / Worker appsettings
Aetherium:Media:SttCommand / SegmentSeconds / MaxAudioSeconds The command that prints one recording's transcript; the piece length a long recording is cut into; the longest recording transcribed /data/stt/stt-local.py / 600 / 3600 appsettings
Aetherium:Media:MaxImageBytes The largest picture captioned 20971520 (20 MB) appsettings
KnowledgeSeeding:Enabled Run the KB seeder at creation true appsettings
KnowledgeSeeding:ResearchSubagentLimit Layer-2 research spawn cap 4 appsettings
seedKnowledgeBase / target_collections Per-template seeding + write routing — Template

Retrieval and embedding are tuned in appsettings.json (the ChromaDb section, which sits at the file root, not under Aetherium, and Aetherium:Embeddings); only the injection switch and the generation pointer are SystemSettings rows. Chunking has no knobs: chunker v2 packs heading sections into 400–1,600 characters, windows code by 60 lines, and caps every chunk at 2,000. Every embedding request names a model id: LM Studio answers any name from whichever embedding model it holds, but a llama-server router answers only for a name that is one of its presets. The model id and prefix scheme together are the index's stamp: a collection is created with the stamp of the vectors written into it and refuses a write under any other. Knowledge packs are a separate axis: the Engine ships a built-in catalog of curated packs (language runtimes, frameworks, and tools), and a team opts into them through the template's knowledge_packs list or by enabling auto_detect_knowledge. Template-level knowledge fields (seedKnowledgeBase, target_collections) are authored as described in §08; the seeding mechanism is in §06.

The relevance floor is ChromaDb:ScoreFloor, applied by the store, by search_knowledge and by injection alike. Scores are cosine similarity whatever space a collection uses, so 0.525 is where the old hardcoded 0.05 on an L2 score sat for unit vectors.

Security

Key Controls Default Layer
allowed_domains Egress domain allowlist empty (DLP only) Template (per team)
Permission rules Declarative tool gates rules: [] (empty) YAML — config/permissions.yaml

Security configuration is mostly per-team rather than global. The egress allowlist travels on the team config; the permission-rules YAML hot-reloads from config/permissions.yaml and ships empty, so it is an opt-in surface rather than a default gate. When a team's allowed_domains list is empty, outbound tools are not blocked by domain — only the credential-scanning DLP pass runs. There is no per-tool call ceiling: the circuit breaker's per_tool_limits map counted nothing and was removed on 2026-10-08. The mechanisms — EgressPolicyService, the audit trail — are detailed in §07 — MCP Architecture.

Config gap: audit retention is unbounded

ToolSecurityAuditService writes every gated call to PostgreSQL but has no retention or purge logic and no config key — the audit table grows without limit. If you run the Engine continuously, plan to prune that table yourself until a retention key exists.

Tiers

Tier RetentionDays MaxProjects CPU Memory Disk
Production -1 (never) -1 (unlimited) 4.0 8g 50g
Staging 30 50 2.0 4g 20g
Temp 7 20 1.0 2g 5g

The three tiers live in the Aetherium:Tiers section and set both the resource ceiling for a tier's containers and its retention policy. A RetentionDays of -1 means never expire — that is why Production projects are kept indefinitely while Temp projects are reclaimed after a week. MaxProjects caps how many projects a tier will hold, and the CPU, memory, and disk values become the container resource limits for everything in that tier. An unknown or empty tier name falls back to Staging. The tier policy and cleanup services that consume these are in §06.

LM Studio & inference

Key Controls Default Layer
AI_BaseUrl Inference endpoint for the chat path http://localhost:1234/v1 SystemSettings
LMStudio_MultiInstanceConfig Per-slot runtime rows: endpoints, enabled flags, per-slot concurrency (Primary and Secondary under fixed keys, any other slot under instances) empty (defaults) SystemSettings
Aetherium:LMStudio:Instances:Primary:* Primary instance (management/VRAM) http://localhost:1234 appsettings
Aetherium:LMStudio:Instances:Secondary:* Secondary instance (management/VRAM) — appsettings
Aetherium:LMStudio:Instances:<slot>:Backend Management dialect for the slot: LmStudio or LlamaServer. On LlamaServer, a model whose preset sets slot-save-path has slot 0 saved before each unload and restored after its next load, so its last prompt survives an eviction LmStudio appsettings
Aetherium:LMStudio:Instances:<slot>:InteractiveCopies Model id → a second loaded copy that serves interactive relay traffic Secondary: gemma-4-e4b-it → gemma-interactive appsettings
Aetherium:LMStudio:Instances:<slot>:Machine The machine the slot's server runs on, for the Inference page's grouping only; set it for a slot on another machine reached through a local port forward unset (this host for a card with no Host or a loopback URL, else the URL's host) appsettings
Aetherium:LMStudio:Instances:<slot>:Card / :Unit / :GpuApi The physical card the slot runs on, the systemd unit that serves it on that card's machine, and that unit's llama.cpp build (Hip or Vulkan) unset; Hip appsettings
Aetherium:LMStudio:Instances:<slot>:RestoreDelaySeconds Seconds after the slot's last lease ends before its standard set is restored, restarted by every release 0 (restore at once) appsettings
Aetherium:LMStudio:SlotOrder Catalog order of the slots: failover and the relay's model lookup follow it Primary, Secondary, then by name appsettings
Aetherium:Gpus:Cards The physical cards (ROCm UUID, Vulkan device id), named by slots' Card; a card in another machine adds Host (its SSH destination) and ListenAddress (the address its routers and GPU services bind) empty appsettings
Aetherium:Gpus:RemoteSshKey Identity file for SSH to a card's Host, where it is forced to the services/aetherium-gpu-node wrapper unset (ssh's defaults) appsettings
Aetherium:RolePlacement:<role> Slot (and optional pin) for a model call that belongs to no node empty (built-in defaults) appsettings
Aetherium:LMStudio:OfflineFallbackSlot Slot an unpinned side role runs on while its own slot is known down (its last two health probes failed) unset (the role stays and fails) appsettings
AI_ReasoningEffort Reasoning on the two planning turns only (low/medium/high/xhigh); a template's config.reasoning_effort covers its worker turns unset (off) SystemSettings
AI_InferenceLeaseIdleMinutes Idle window of an interactive inference lease (the /v1 relay's chat client) 15 SystemSettings
Aetherium:VramOrchestration:PreemptGraceSeconds Grace a preempted batch lease gets to release itself before eviction 20 appsettings
Aetherium:VramOrchestration:DefaultNodeInstance The slot a node that names a model but no instance runs on, when that slot's standard set holds its model Worker appsettings

LM Studio has two config homes, and they serve different paths. The runtime chat and routing path reads SystemSettings (AI_BaseUrl, LMStudio_MultiInstanceConfig); the model management and VRAM orchestration path reads the Aetherium:LMStudio:Instances appsettings block. For Primary and Secondary they are not synchronized, so set the endpoint you actually chat against in the Settings page — changing only the appsettings instance URL will move where models are loaded and unloaded without moving where inference requests are sent, which is a confusing state to debug (the Engine logs a [SLOTS] warning when the two differ). Any other slot has one source: its runtime row is built from the registry entry, endpoint included. Discovery on an LmStudio slot uses LM Studio's api/v0/models endpoint because it reports load state; anything but a 200 is a failed probe and the instance's state is treated as unknown rather than read from v1/models, which carries no load state. A LlamaServer slot is probed at the router's /models ledger instead, and loads and unloads go through /models/load and /models/unload; the served context length is a property of the model's preset on that server, so a context the Engine requests is logged and ignored rather than causing a reload, and a model that is not in the preset list is rejected by the residency planner before a run starts. Routing and scheduling are covered in §06 and the integration in §13 — Integrations.

Devices

Key What it controls Default Source
Aetherium:Devices:SteamFrame:Host The Steam Frame headset's address, for the device tools a LAN address code
Aetherium:Devices:SteamFrame:KeyPath / KnownHostsPath This host's own devkit pairing key, and the headset's host key ~/.config/steamos-devkit/devkit_rsa / ~/.config/aetherium/frame/known_hosts_frame code
Aetherium:Devices:SteamFrame:BuildsRoot A folder of builds an APK may come from besides the workspace /srv/share/steam-frame/builds code

The full list, and what each key does, is in Configuration reference; the tools themselves are in §07 — MCP Architecture.

Where to go next