Configuration Reference¶
Every configuration key the Engine reads, where it lives, and whether it is live or inert. This enumerates what §09 — Configuration curates — that section explains why a setting exists and how to reason about the model chain; this page is the exhaustive list. Where they disagree, the code wins and the delta is flagged.
Keys resolve from two worlds: file-based (appsettings*.json + environment variables, bound at startup) and the SystemSettings table (runtime-mutable, no restart). A handful of values are neither — they are hardcoded literals, marked as such below.
Live vs. inert
Live = read by application code at runtime. Inert = defined, bound, or seeded but never read (or shadowed by a higher layer). Inert keys are documented because setting one and expecting an effect is a common dead end. Don't tune a knob this page marks inert.
Resolution order¶
File configuration is layered lowest-to-highest; a key set higher wins. Built in Program.cs:
| # | Layer | Restart to change? |
|---|---|---|
| 1 | appsettings.json (committed defaults) |
Yes |
| 2 | appsettings.{Environment}.json (per-ASPNETCORE_ENVIRONMENT overlay) |
Yes |
| 3 | User Secrets (Development only) | Yes |
| 4 | Environment variables | Yes |
| 5 | Command-line args | Yes |
| 6 | appsettings.Local.json (gitignored; appended last) |
Yes |
| 7 | SystemSettings table (consulted at runtime by specific services) |
No |
Two consequences worth internalizing: appsettings.Local.json is added after the default chain, so it outranks environment variables and command-line args. And SystemSettings is not part of the file pipeline — each service decides whether to consult it, using an emptiness gate (a meaningful row wins; an empty row falls through to the file value).
appsettings.Linux.json is not loaded because the host OS is Linux. It loads only when ASPNETCORE_ENVIRONMENT=Linux. Storage paths additionally pass through a post-configure step that expands ${VAR} / %VAR% tokens and falls back to platform defaults (C:\aetherium\… on Windows, /var/aetherium/… on Linux).
Files present¶
| File | Loaded when |
|---|---|
appsettings.json |
Always (base) |
appsettings.Development.json |
ASPNETCORE_ENVIRONMENT=Development |
appsettings.Linux.json |
ASPNETCORE_ENVIRONMENT=Linux only (never OS-auto) |
appsettings.Local.json |
Always (appended explicitly, highest file layer) |
There is no appsettings.Production.json.
SystemSettings table¶
A SystemSetting row is Key (PK, string), Value (string), Description (string?), Group (string, default General). Values are always strings; services parse them (int, bool, or JSON) on read. There is no cache — each read opens a fresh context, so a saved change is visible to the next read.
Seeded rows¶
Twelve rows ship via HasData. Groups in use: Projects, Knowledge, Containers, Features.
| Key | Default | Group | Live? |
|---|---|---|---|
DefaultProjectPath |
(empty) | Projects | Live — empty so the file value wins |
WorkspaceRootPath |
(empty) | Projects | Live — empty so the file value wins |
GlobalKnowledgePath |
(empty) | Knowledge | Live — empty so the file value wins |
Container_IdleTimeoutMinutes |
60 |
Containers | Live |
Container_DestroyGracePeriodMinutes |
30 |
Containers | Live |
Container_CleanupIntervalMinutes |
5 |
Containers | Live |
Container_AutoSnapshotBeforeCleanup |
true |
Containers | Live |
Container_MaxSnapshotsPerProject |
5 |
Containers | Live |
Container_SnapshotRetentionDays |
30 |
Containers | Live |
Read but not seeded¶
These keys are read at runtime and created on first save from the Settings page (until then, code falls back to a built-in default):
| Key | Fallback when unset | Controls |
|---|---|---|
AI_ModelId |
(skipped in chain) | Operator-wide default model |
AI_WorkerModelId |
(skipped) | Default for non-supervisor workers |
AI_SupervisorModelId |
falls back to AI_ModelId |
Layer-2 gap-detection model |
AI_Layer2WorkerModel |
inherits parent | Spawned research-subagent model |
AI_BaseUrl |
http://localhost:1234 |
LM Studio chat-path endpoint |
LMStudio_MultiInstanceConfig |
built-in defaults | Multi-instance routing config (JSON) |
SearXNG_Url |
DuckDuckGo fallback | web_search backend |
MaintenanceMode |
false |
Gates create/build POST routes (503 when true) |
ToolApproval_TimeoutSeconds |
300 |
Restricted-tool approval window |
Finishing_DirectorModel |
template's pinned model | Model for the finishing team's director and designer |
Finishing_DirectorInstance |
template's pinned instance | Instance slot those two run on |
Finishing_WorkerModel |
template's pinned model | Model for the finishing team's researchers and writer |
Finishing_WorkerInstance |
template's pinned instance | Instance slot those run on. Pinning a model without its instance leaves the node on the primary slot, which is how a whole team ends up on one card |
Keys by concern¶
Each table: key · type · default · layer · live/inert · what it controls. Defaults shown are the committed appsettings.json values unless noted; deployment-specific hosts and secrets are rendered as placeholders.
Hosting, logging, connection strings¶
| Key | Type | Default | Layer | Live? | Controls |
|---|---|---|---|---|---|
ConnectionStrings:DefaultConnection |
string | Host=localhost;Port=5432;Database=aetherium_refactor_db;Username=aetherium_user;Password=<override locally> |
appsettings | Live | Npgsql/EF Core connection |
Logging:LogLevel:Default |
string | Information |
appsettings | Live | Default log level |
Logging:LogLevel:Microsoft.AspNetCore |
string | Warning |
appsettings | Live | ASP.NET log level |
AllowedHosts |
string | * |
appsettings | Live | Host-header filtering |
ApiBaseUrl |
string | http://localhost:5257 |
appsettings | Live | Base address for the server's own HttpClient (self-calls) |
DetailedErrors |
bool | true (Dev) |
appsettings | Live | Detailed error output in responses |
BackendBaseUrl |
string | http://localhost:5000 (code fallback) |
env/Local | Live if set | Not in any appsettings file |
FrontendBaseUrl |
string | http://localhost:5173 (code fallback) |
env/Local | Live if set | Not in any appsettings file |
Storage & paths¶
| Key | Type | Default | Layer | Live? | Controls |
|---|---|---|---|---|---|
Aetherium:Storage:WorkspaceRoot |
string | ${AETHERIUM_WORKSPACE_ROOT} → platform default |
appsettings | Live | Workspace tier root |
Aetherium:Storage:GlobalKnowledgePath |
string | ${AETHERIUM_KNOWLEDGE_PATH} → platform default |
appsettings | Live | Global knowledge library |
Aetherium:Storage:ArchivePath |
string | ${AETHERIUM_ARCHIVE_PATH} → platform default |
appsettings | Live | Archive root |
Aetherium:Storage:TempPath |
string | ${AETHERIUM_TEMP_PATH} → platform default |
appsettings | Live | Temp path |
Aetherium:Storage:GitHubToken |
string | (empty) | appsettings | Live | PAT for knowledge-pack downloads |
WorkspaceRootPath / DefaultProjectPath / GlobalKnowledgePath |
string | (empty) | SystemSettings | Live | Runtime overrides for the above; empty defers to file |
AI & models (runtime chat/routing path)¶
These are SystemSettings rows, not file config. See the model-resolution chain in §09.
| Key | Type | Default | Layer | Live? | Controls |
|---|---|---|---|---|---|
AI_ModelId |
string | (unset) | SystemSettings | Live | Operator-wide default model |
AI_WorkerModelId |
string | (unset) | SystemSettings | Live | Non-supervisor worker default |
AI_SupervisorModelId |
string | (unset) | SystemSettings | Live | Layer-2 gap-detection model only |
AI_Layer2WorkerModel |
string | (unset) | SystemSettings | Live | Spawned research-subagent model |
AI_ReflectionEnabled |
bool | false |
SystemSettings | Live | Master toggle for agent self-critique (reflection); off by default so benchmark output stays unrefined |
AI_VerificationEnabled |
bool | false |
SystemSettings | Live | Enables step verification for teams without a template verification block (the block, when present, wins — a block with enabled: false opts the team out); fail-closed — a settings error leaves verification off |
AI_VerifierModelId |
string | (falls back to AI_SupervisorModelId → AI_ModelId) |
SystemSettings | Live | Judge model for step verification (per-team verifier_model_id overrides) |
AI_TrajectoryJudgeModelId |
string | (falls back to the team's verifier_model_id, then AI_SupervisorModelId) |
SystemSettings | Live | Judge model for the end-of-run trajectory evaluation. A template's verification.trajectory_judge_model_id (and trajectory_judge_preferred_instance) overrides it, and where the template names only a step verifier the trajectory judge follows that — before this the run-end judge read the global setting alone, so a team that pinned its verifier to one model had its trajectory judged by another |
AI_MaxVerificationRetries |
int | (unset → per-team max_retries, default 2) |
SystemSettings | Live | Global cap on verify→fix bounce cycles per step |
AI_StepRounds |
int | 1 (clamped to 1–8) |
SystemSettings | Live | Fresh attempts a plan step may get: each round is a new history-free delegation of the same step plus a validated handoff from the previous round (files written, tools used, summary, verifier findings). 1 is the original single attempt; raising it changes run behaviour, so results are not comparable across the change. A team's own step_rounds (a template field or a create override) takes precedence over this setting |
AI_StepRepairRounds |
int | 2 (clamped to 0–4) |
SystemSettings | Live | Extra rounds granted only when a step failed a deliverable gate — a typed absence or a missing required_output — on top of AI_StepRounds. The two answer different questions: AI_StepRounds is how many passes the work takes and is 1 for most teams, while a repair round acts on a gate message that already names the missing path. When that message names some of the step's files, the step's other files that already meet their conditions are held as they are, so the round works on the named one. 0 restores the pre-repair behaviour |
AI_RepetitionRuleArmed |
bool | false |
SystemSettings | Live — the deterministic repetition rule always writes [REP-RULE] lines for a looping tool call; true lets it bounce the agent as well, at most twice per loop. Off by default |
|
AI_ImageExampleArm |
string | (unset → arm A) | SystemSettings | Live, read per tool loop | B swaps the example in comfyui_generate's workflow description for one without the "8k photorealistic" booster, for an A/B before the description itself changes. Unset or any other value leaves the shipped schema byte for byte. A loop on arm B logs [IMAGE-ARM] |
AI_InstanceAvailabilityCheck |
bool | true |
SystemSettings | Live | Feature flag for the scheduler's instance-availability layer, so a busy alternate instance holds its tasks rather than failing them. Set false to disable the layer without a redeploy |
AI_TrajectoryJudgeModelId |
string | (falls back to AI_SupervisorModelId → AI_ModelId) |
SystemSettings | Live | Model that judges a run's trajectory for the eval / promotion gate |
AI_PromotionScoreThreshold |
int | 7 |
SystemSettings | Live | Minimum trajectory score (1–10) to promote a template to core |
AI_TrajectoryEvalOnRunComplete |
bool | false |
SystemSettings | Live | Score every finalized run's trajectory in the background; off by default so a benchmark host does not pay a judge call per run |
AI_ReasoningEffort |
string | (unset → off) | SystemSettings | Live | low | medium | high | xhigh: turns a thinking model's reasoning on for the two planning turns only (plan creation and the Dynamic-parallel lead's planning phase); every other turn keeps reasoning off unless the team's own config.reasoning_effort turns it on for that team's worker turns. Any other value is treated as unset |
AI_InferenceLeaseIdleMinutes |
int | (unset → Aetherium:VramOrchestration:InteractiveIdleMinutesDefault, 15) |
SystemSettings | Live (read every 30 s) | How long an interactive inference lease (the /v1 relay's chat client) keeps its model pinned against batch eviction after its last request |
AI_BaseUrl |
string | http://localhost:1234 (code fallback) |
SystemSettings | Live | LM Studio chat endpoint |
LMStudio_MultiInstanceConfig |
string (JSON) | (unset) | SystemSettings | Live | Per-slot runtime rows: endpoints, enabled flags, per-slot concurrency. Primary and Secondary sit under the fixed keys primary and secondary; every other slot sits under instances, a row the Engine builds from that slot's registry entry the first time it is read (its endpoint is the registry's InferenceUrl), and the key is omitted when there are none |
Aetherium:AI:BaseUrl |
string | http://localhost:1234 (fallback) |
— | Inert | Orphan — read only by the health endpoint; absent from every appsettings file |
LM Studio (model-management / VRAM path)¶
Separate from the chat path above. This appsettings block drives model load/unload and VRAM orchestration, not where inference requests are sent. Primary and Secondary are always slots; every other key under Instances adds one, and a key starting with _ is a hidden slot the arbiter uses but nothing lists, routes to or fails over to. Each slot names its backend: LmStudio (the default) manages models through LM Studio's native API; LlamaServer drives a llama.cpp llama-server in router mode (/models, /models/load, /models/unload), where ManagementUrl is the server root and the model's context length is a property of its preset rather than a load-time request.
| Key | Type | Default | Layer | Live? |
|---|---|---|---|---|
Aetherium:LMStudio:Instances:Primary:InferenceUrl |
string | http://localhost:1234/v1 |
appsettings | Live |
Aetherium:LMStudio:Instances:Primary:ManagementUrl |
string | http://localhost:1234/api/v1 |
appsettings | Live |
Aetherium:LMStudio:Instances:Primary:Backend |
LmStudio | LlamaServer |
LmStudio |
appsettings | Live |
Aetherium:LMStudio:Instances:Primary:MaxVramGB |
int | 16 |
appsettings | Live |
Aetherium:LMStudio:Instances:Primary:Description |
string | (free-text label) | appsettings | Live |
Aetherium:LMStudio:Instances:Secondary:InferenceUrl |
string | http://<secondary-host>:1234/v1 |
appsettings | Live |
Aetherium:LMStudio:Instances:Secondary:ManagementUrl |
string | http://<secondary-host>:1234/api/v1 |
appsettings | Live |
Aetherium:LMStudio:Instances:Secondary:Backend |
LmStudio | LlamaServer |
LmStudio |
appsettings | Live |
Aetherium:LMStudio:Instances:Secondary:MaxVramGB |
int | 16 |
appsettings | Live |
Aetherium:LMStudio:Instances:Secondary:Description |
string | (free-text label) | appsettings | Live |
Aetherium:LMStudio:Instances:<slot>:InteractiveCopies |
map | Secondary: {"gemma-4-e4b-it": "gemma-interactive"} |
appsettings | Live — model id → the identifier of a second loaded copy that serves interactive relay traffic, so external chat clients keep their own prompt cache apart from engine runs. A request the copy cannot serve is re-sent to the base model |
Aetherium:LMStudio:Instances:<slot>:Enabled |
bool? | (unset) | code (absent from files) | Live — false takes the slot out of service: it is never a failover alternate and its runtime row's enabled flag cannot switch it back on. Unset leaves that to the runtime row |
Aetherium:LMStudio:Instances:<slot>:MaxConcurrentRequests |
int? | (unset → 1) |
code (absent from files) | Live — seeds the runtime row of a slot that has none yet; a stored row's own value wins after that |
Aetherium:LMStudio:Instances:<slot>:RestoreDelaySeconds |
int | 0 |
code (absent from files) | Live — seconds the arbiter waits after the slot's last lease ends before restoring its standard set, restarted by every release; 0 restores at once, inline with the release. Above 0 a run or job that starts inside the window finds the card as the last one left it, with no reload to evict |
Aetherium:LMStudio:Instances:<slot>:Card |
string? | (unset) | code (absent from files) | Live — the physical card the slot runs on, a key of Aetherium:Gpus:Cards. Unset for a slot on another machine, unless that machine's card is listed with a Host. The planner prices the slot's loads with that card's costs and headroom |
Aetherium:LMStudio:Instances:<slot>:Machine |
string? | (unset) | code (absent from files) | Live — display only: the machine the slot's server runs on, which the Inference page groups slots by. Set it for a slot on another machine reached through a local port forward (its URL is loopback); unset, a slot on a card with no Host or at a loopback URL is this host and any other URL names its host |
Aetherium:LMStudio:Instances:<slot>:Unit |
string? | (unset) | code (absent from files) | Live — the systemd user unit that serves the slot (a llama-server router on the card's machine: this host, or the card's Host); scripts/render-gpu-env.py writes its card-pin environment file from Card and GpuApi, and the unit does not start without one |
Aetherium:LMStudio:Instances:<slot>:GpuApi |
Hip | Vulkan |
Hip |
code (absent from files) | Live (read by scripts/render-gpu-env.py, not the Engine) — which llama.cpp build serves the slot, and so how its unit is pinned: by ROCm UUID (Hip) or by Mesa device id (Vulkan) |
Aetherium:LMStudio:SlotOrder |
list of slot | (empty → Primary, Secondary, then the rest by name) | code (absent from files) | Live — catalog order of the slots: the order failover, smart routing, the team generator and the judges' residency hint try slots in, and the relay looks for a model in. A slot not named follows the named ones |
Aetherium:LMStudio:ProbeTimeoutSeconds |
int | 15 |
code (absent from files) | Live — ceiling on a read-only listing against a slot (load state, residency ledger, catalog); a listing that has not answered in time is a failed probe. Loads and unloads keep the management client's 120 s timeout. Connecting is bounded separately — 5 s on the management and /v1 relay clients, 10 s on the default client — so a server whose machine drops the connection attempt fails in seconds; generation time is not affected |
Aetherium:RolePlacement:<role> |
{Slot, Pin} |
(empty) | code (absent from files) | Live — the slot for a model call that belongs to no team node. Roles: Compaction, Summaries, Lessons, CodeReview, TestDrive, DecisionLayer, Fork, Discovery, Verifier, TrajectoryJudge, TeamGenerator, ImagePrompt, Embeddings. Pin: true makes the slot binding (the task fails rather than move); unpinned it is a hint. A role not named keeps its built-in default (Primary for most); a template's own verifier or judge instance, and a fork's own instance, still win. Unknown roles and unknown slots are logged at start-up as [ROLE-PLACEMENT] |
Aetherium:LMStudio:OfflineFallbackSlot |
string? | (unset) | code (absent from files) | Live — where an unpinned side role (an Aetherium:RolePlacement role, or a call site's built-in Primary default) runs while its own slot is known down: the slot's last two health probes (30 s apart) failed and the latest is under two minutes old. A known-down slot also refuses leases at once and is skipped by smart routing and the re-warm loop. A pinned role and every team node stay where they are and fail fast, and nothing moves while the fallback slot is itself down. A role works there only when that slot serves its model. Unset, side roles stay on their slot and fail. Each move is logged [ROLE-PLACEMENT] (at most every five minutes per slot). The team generator also tries this slot's resident model when its own unpinned placement slot holds none, before Primary |
Aetherium:LMStudio:DefaultContextLength |
int | 49152 |
appsettings | Live |
Aetherium:LMStudio:ModelLoadTimeoutSeconds |
int | 120 |
appsettings | Live |
Aetherium:LMStudio:ModelUnloadWaitSeconds |
int | 5 |
appsettings | Live |
Aetherium:LMStudio:ModelUnloadVerifyGraceSeconds |
int | 15 |
appsettings | Live — after the unload wait, how long the verify keeps polling for the model to leave before the unload counts as failed (a backend lets an in-flight request finish first) |
Aetherium:LMStudio:VerifyAfterLoadUnload |
bool | true |
appsettings | Live |
VRAM orchestration¶
| Key | Type | Default | Layer | Live? |
|---|---|---|---|---|
Aetherium:VramOrchestration:Enabled |
bool | true |
appsettings | Live |
Aetherium:VramOrchestration:PreWarmEnabled |
bool | true |
code (absent from files) | Live — loads each slot's standard set at start-up, and every 30 s re-warms a LlamaServer slot with a standard set that has answered empty on two consecutive checks with no lease and nothing in flight (its server restarted under it). MaintenanceMode suspends the re-warm, so a router emptied by hand without a lease comes back within about a minute unless a lease or maintenance mode keeps it empty |
Aetherium:VramOrchestration:HeadroomGB |
double | 1.5 |
appsettings | Live — VRAM the planner will not spend, so a run cannot starve the desktop |
Aetherium:VramOrchestration:HeadroomGBByCard |
map of card → double | (empty) | code (absent from files) | Live — headroom for slots on that card, winning over HeadroomGB (a card that drives a display needs more than one that drives nothing) |
Aetherium:VramOrchestration:UnknownOverheadGB |
double | 0.75 |
appsettings | Live — added to the file size of a model with no measured cost |
Aetherium:VramOrchestration:UnknownKvBytesPerToken |
int | 24576 |
appsettings | Live — per-token KV allowance for an unmeasured model |
Aetherium:VramOrchestration:StandardModels |
map of slot → list | Primary/Secondary sets | appsettings | Live — what each instance carries between leases: loaded at start-up, evicted when a lease needs the room (unless the slot is in ProtectedStandardSetSlots), restored by the arbiter's sweep once no lease is live or queued on the slot |
Aetherium:VramOrchestration:DefaultNodeInstance |
string | Worker |
appsettings | Live — at run start, a node (or subagent_defaults) that names a model but no preferred_instance is moved to this slot when the slot's standard set holds exactly one non-embedding model its id matches, and its model_id is rewritten to that id, since a llama-server slot serves only its preset ids; each move is logged [P4-HOME]. A model the slot does not hold, a pinned node and a node with no model are left alone, and the key is ignored when no slot by this name is registered. Unset, such a node runs on Primary |
Aetherium:VramOrchestration:ModelCosts |
map of model → {BaseBytes, KvBytesPerToken} |
measured entries | appsettings | Live — measured VRAM cost; an unlisted model falls back to the two keys above. KvBytesPerToken: 0 records a KV cache held in system RAM rather than on the GPU, which for some models is a load-time choice, not their default — if that placement is not also forced at load time the entry describes a configuration the Engine never loads. Lookup tolerates an org prefix and a @quant suffix, so two quantisations of one model share an entry |
Aetherium:VramOrchestration:ModelCostsByCard |
map of card → model → {BaseBytes, KvBytesPerToken} |
(empty) | code (absent from files) | Live — measured costs that differ by card (compute buffers, a HIP or Vulkan build); an entry wins over ModelCosts for a slot on that card. Keyed by card, so moving a slot to another card needs no cost edits |
Aetherium:VramOrchestration:PreemptGraceSeconds |
int | 20 |
code (absent from files) | Live — how long a preempted batch lease has to release itself before its model is unloaded anyway |
Aetherium:VramOrchestration:LeaseSweepSeconds |
int | 5 |
code (absent from files) | Live — period of the lease sweep: expiry, granting queued leases, restoring an idle slot |
Aetherium:VramOrchestration:RunLeaseTtlHours |
int | 8 |
code (absent from files) | Live — a run lease that was never released (the run died) expires after this |
Aetherium:VramOrchestration:InteractiveIdleMinutesDefault |
int | 15 |
code (absent from files) | Live — interactive lease idle window when AI_InferenceLeaseIdleMinutes is unset |
Aetherium:VramOrchestration:BatchLeaseDefaultMinutes |
int | 30 |
code (absent from files) | Live — TTL of a batch lease that names none |
Aetherium:Tts:BaseUrl |
string | http://127.0.0.1:8100/v1 |
code (absent from files) | Live — the voice service tts_render posts to; {BaseUrl}/audio/speech, health at the same host's /health |
Aetherium:Tts:RefsDir |
string | ~/.cache/dnd-models/tts-qwen/refs |
code (absent from files) | Live — reference clips with transcripts the voice tools clone |
Aetherium:Tts:DefaultVoice |
string | host |
code (absent from files) | Live |
Aetherium:Tts:GapSeconds |
double | 0.18 |
code (absent from files) | Live |
Aetherium:Tts:LineTimeoutSeconds |
int | 300 |
code (absent from files) | Live — per-line synthesis timeout |
Aetherium:Tts:DesignBaseUrl |
string | http://127.0.0.1:8101/v1 |
code (absent from files) | Live — the VoiceDesign server (the qwen-tts-design GPU service) tts_render asks once per described voice; the clip is saved under {RefsDir}/designed/ |
Aetherium:Tts:RelayVoices |
map of voice name → reference stem | (empty) | appsettings | Restart — the voices POST /v1/audio/speech speaks in: an OpenAI voice name → <stem>.wav + <stem>.txt under RefsDir. A name not in the map is a 400 |
Aetherium:Tts:RelayService |
string | qwen-tts |
code (absent from files) | Restart — the GPU service the speech relay leases and renders with |
Aetherium:Tts:RelayIdleMinutes |
int | 10 |
code (absent from files) | Restart — how long the speech relay keeps the voice service leased after its last request |
Aetherium:Tts:RelayQueueWaitSeconds |
int | 240 |
code (absent from files) | Restart — how long a speech request waits for the lease while the card is busy before a 503 |
Aetherium:Tts:RelayMaxWords |
int | 380 |
code (absent from files) | Restart — the longest input one speech request renders (about 2.5 minutes); longer is a 400, never a truncation |
Aetherium:Tts:RelayRenderBaseSeconds |
int | 30 |
code (absent from files) | Restart — the fixed part of one speech render's budget; a render with no audio after this plus RelayRenderSecondsPerWord per input word (never more than LineTimeoutSeconds) is cut off with a 504 |
Aetherium:Tts:RelayRenderSecondsPerWord |
double | 0.5 |
code (absent from files) | Restart — the per-word part of the render budget above |
Aetherium:VramOrchestration:Services |
map of name → {Unit, Slot, Bytes, HealthUrl?, StartTimeoutSeconds, LingerSeconds, Env, EnvByCard, AloneEnv?} |
(empty) | appsettings | Live — GPU services a lease may name (a systemd user unit): the arbiter starts the unit under the lease, plans Bytes as a resident of that slot, waits for HealthUrl, and stops the unit when no live lease names it. LingerSeconds (default 0: stop at once) keeps an unreferenced unit warm that long; a lease that needs nothing new on the card reuses it, any lease or mid-run load that must load or start something stops every warm unit on the slot first, and the standard set is restored only once they have stopped. Env (and EnvByCard, card → variables laid over it when the service's slot is on that card) are extra variables scripts/render-gpu-env.py writes into the unit's environment file beside the card pin. AloneEnv, when set, makes that cap and Bytes apply only while a model shares the card: a lease that names no model and exactly one service, on a card no other lease pins a model on and no other service runs, starts the unit with AloneEnv instead and books the card's whole budget, so nothing loads beside it (a whole-card booking the planner refuses falls back to the capped one). The variables reach the unit through a per-start file, ~/.config/aetherium/gpu/<unit>.start.env, which the shipped comfyui-qwen unit reads; a unit without that file ignores AloneEnv |
Aetherium:VramOrchestration:ProtectedStandardSetSlots |
list of slot | ["Secondary"] |
appsettings | Live — slots whose standard set no lease may evict; a batch lease there must fit beside it |
Aetherium:VramOrchestration:ServiceStartMaxCelsius |
double | 0 (off) |
code (absent from files) | Live — a run or batch lease that would start a GPU service on a slot of the Engine's own machine (a slot with no card, or whose card has no Host) waits in the queue while the CPU package temperature (k10temp Tctl) is above this, with the reading as its waiting reason, and starts once it is at or under it. A service already running is reused whatever the reading; interactive leases never start services. No sensor means no gate. For machines whose GPU shares the die with the CPU |
Aetherium:VramOrchestration:CoResidentLmStudio |
map of slot → hidden instance name | (empty) | appsettings | Live — an LM Studio sharing a llama-server slot's card (an instance whose name starts with _ is hidden from every listing): whatever it holds is unloaded before the arbiter loads on that slot and reported as a foreign occupant. A slot with no entry here takes the hidden LM Studio instance whose Card is the slot's card |
Aetherium:Gpus:Cards |
map of name → {Uuid, VulkanDevice?, DrivesDisplay, Host?, ListenAddress?, Description} |
(empty) | code (absent from files) | Live — the physical cards, named by slots' Card. Uuid is the ROCm UUID (GPU-<hex>); VulkanDevice (vendor:device) is needed by a slot served by a Vulkan build. Card telemetry (GET /api/inference/cards) reads them; a slot naming an unknown card, a card with no Uuid, and one unit serving two slots are logged at start-up as [GPU-CARDS]. scripts/render-gpu-env.py refuses to pin a unit whose card is missing, has no Uuid, or is absent from sysfs. Aetherium:Gpus:Builds:{Hip,Vulkan} names each build's llama-server binary (default ~/llama-cpp/current/llama-server and ~/llama-cpp/vulkan-current/llama-server) and is read by the script only. Host (user@address; unset = the box the Engine runs on) puts a card in another machine: the arbiter starts and stops the GPU services on its slots over SSH through that machine's services/aetherium-gpu-node wrapper — a host that does not answer counts its services as stopped and is not asked again for 30 s — and scripts/deploy.sh validates and renders the card's pins on that machine, against its sysfs, and restarts the card's routers there. ListenAddress (read by the script only) is the address that card's routers and GPU services bind, written into each pin file as AETHERIUM_LISTEN; it defaults to 127.0.0.1, and a card with a Host must set it |
Aetherium:Gpus:RemoteSshKey |
string? | (unset) | code (absent from files) | Live — the identity file the Engine uses for SSH to a card's Host (~/ is expanded). On the GPU host its authorized_keys line forces services/aetherium-gpu-node, which runs only is-active, start, stop and write-env on the service units the deploy published there, and cards (that machine's GPUs from sysfs, for GET /api/inference/cards). Use a dedicated key, not one of ssh's default identity files: deploy.sh's own SSH uses those, and a default key forced to the wrapper would land the deploy in it. Unset: ssh's own defaults and ~/.ssh/config |
Devices (Aetherium:Devices:SteamFrame)¶
Where the Steam Frame headset is and how the Engine's host reaches it, for the device tools (frame_status, frame_install, frame_run, frame_grab). The section is absent from the shipped files, so every key is its code default until set in appsettings.Local.json. Each host pairs with the headset itself; a key copied from another host is never used.
| Key | Type | Default | Layer | Live? |
|---|---|---|---|---|
Aetherium:Devices:SteamFrame:Host |
string | a LAN address | code (absent from files) | Live — the headset's address; an IP, since the headset's name does not resolve on every host |
Aetherium:Devices:SteamFrame:KeyPath |
string | ~/.config/steamos-devkit/devkit_rsa |
code (absent from files) | Live — this host's SteamOS Devkit key, written when the host paired with the headset |
Aetherium:Devices:SteamFrame:KnownHostsPath |
string | ~/.config/aetherium/frame/known_hosts_frame |
code (absent from files) | Live — the headset's public host key |
Aetherium:Devices:SteamFrame:StateDir |
string | ~/.local/share/aetherium/frame |
code (absent from files) | Live — per-package state kept off every workspace: the app's last saves, and a run's files until they are moved into the workspace |
Aetherium:Devices:SteamFrame:Adb |
string | adb |
code (absent from files) | Live — adb on this host |
Aetherium:Devices:SteamFrame:BuildsRoot |
string? | /srv/share/steam-frame/builds |
code (absent from files) | Live — a folder of builds an APK may also come from, besides the agent's own workspace; unset, the workspace only |
Aetherium:Devices:SteamFrame:DefaultPackage / DefaultProject / DefaultActivity |
string | com.YourCompany.PaintTheWorld / PaintTheWorld / com.epicgames.unreal.SplashActivity |
code (absent from files) | Live — the app when a call names none |
Aetherium:Devices:SteamFrame:ScriptsDir |
string? | (unset) | code (absent from files) | Live — the scripts the tools run; unset, device/steam-frame beside the published app |
Finishing (Aetherium:Finishing)¶
| Key | Type | Default | Layer | Live? |
|---|---|---|---|---|
Aetherium:Finishing:AutoTrigger |
bool | false |
appsettings | Live — when true, a build run reaching Completed opens a finishing gate for that project. Off by default and deliberately so: turning it on starts unattended design work on every project that finishes a build. "Completed" here means no failed step, no 0/N steps succeeded, no circuit-breaker stop and no infrastructure failure — it is not a plan-level verifier verdict |
The models the two phases run on are SystemSettings rows rather than appsettings keys, so they can be changed without a restart — see Read but not seeded.
Image finishing (Aetherium:ImageFinishing)¶
| Key | Type | Default | Layer | Live? |
|---|---|---|---|---|
Aetherium:ImageFinishing:Enabled |
bool | false |
code (absent from files) | Restart — re-render every Completed Qwen-Image team's images at 2K after the run. Off since 2026-10-12: 2K images are made in a finishing run, by request |
Aetherium:ImageFinishing:PromptDir |
string | (beside the enhancer model files) | code | Restart — where the prompt enhancer's system prompts are read from; they are not vendored, so set this per box |
Aetherium:ImageFinishing:T2IModel / I2IModel |
string | qwen-image-pe-t2i / qwen-image-pe-i2i |
code | Restart — the enhancer presets for the re-render and the upscale |
Aetherium:ImageFinishing:Steps |
int | 40 |
code | Restart |
Aetherium:ImageFinishing:MegaPixels |
double | 4.0 |
code | Restart — the re-render's size, about 2K |
Aetherium:ImageFinishing:UpscaleGrid |
int | 2048 |
code | Restart — the upscale's long-side grid |
Aetherium:ImageFinishing:MaxImagesPerRun |
int | 12 |
code | Restart |
See finish_image for what the two passes do.
Decision layer (Aetherium:Decision)¶
| Key | Type | Default | Layer | Live? |
|---|---|---|---|---|
Aetherium:Decision:Enabled |
bool | false |
appsettings | Per session — when true, the worker tool loop gains a semantic-repetition guard that asks a resident model, through IDecisionModel, whether a looping agent is repeating itself, and may bounce it |
Aetherium:Decision:Shadow |
bool | false |
appsettings | Per session — the guard asks and writes its decision_logs row but never bounces, so runs are unchanged. Implies Enabled |
Research (Aetherium:Research)¶
| Key | Type | Default | Layer | Live? |
|---|---|---|---|---|
Aetherium:Research:SearchMinIntervalMs |
int | 1500 |
appsettings | Live — minimum spacing between outbound web_search calls, process-wide. Clamped to 0–30000; 0 keeps the gate in place and only removes the wait, so a parallel wave of researchers still queues rather than bursting every engine at once |
Pacing is half the remedy for engines that suspend themselves under agent-rate search; the other half is the engine set in the SearXNG config. The engines SearXNG reports as unresponsive are now read back and logged per search, so a degraded search set is visible rather than silently returning fewer results.
Knowledge seeding¶
| Key | Type | Default | Layer | Live? |
|---|---|---|---|---|
Aetherium:KnowledgeSeeding:Enabled |
bool | true |
appsettings | Live |
Aetherium:KnowledgeSeeding:InitialDelaySeconds |
int | 30 |
appsettings | Live |
Aetherium:KnowledgeSeeding:MaxConcurrentJobs |
int | 1 |
appsettings | Inert — bound, never read |
Aetherium:KnowledgeSeeding:EnableLayer2Research |
bool | true |
appsettings | Live |
Aetherium:KnowledgeSeeding:ResearchSubagentLimit |
int | 4 |
appsettings | Live |
Aetherium:KnowledgeSeeding:ResearchTimeoutMinutes |
int | 10 |
appsettings | Live |
Knowledge / ChromaDB¶
The ChromaDb section sits at the configuration root, not under Aetherium. The Aetherium:Embeddings keys, listed with it, do sit under Aetherium. Chunking has no keys: chunker v2 packs heading sections into 400–1,600 characters with a breadcrumb title, windows code by 60 lines with 8 lines of overlap, and caps every chunk at 2,000 characters.
| Key | Type | Default | Layer | Live? |
|---|---|---|---|---|
ChromaDb:Endpoint |
string | http://localhost:8000 |
appsettings | Live |
ChromaDb:DefaultCollection |
string | aetherium_knowledge |
appsettings | Inert — bound, never read; collection names are hardcoded |
ChromaDb:ScoreFloor |
float | 0.525 |
code | Live — lowest cosine similarity a retrieved chunk may have; distances from every collection space are converted to cosine first. 0.525 is where the old 0.05 on 1 − d over an L2 space sat for unit vectors |
ChromaDb:Image |
string | chromadb/chroma:1.5.2@sha256:3a9bcbc6… |
code | Live — reported by the health rag block; Chroma's own version endpoint answers 1.0.0 whatever runs |
ChromaDb:IndexBuildLogs |
bool | false |
appsettings | Live |
Aetherium:Embeddings:ModelId |
string | text-embedding-nomic-embed-text-v1.5 |
code (absent from files) | Live — the model id the knowledge index embeds with (and the availability canary names). LM Studio answers any name from whichever embedding model it holds; a llama-server router answers 400 for a name that is not one of its presets. The canary asks the slot of the Embeddings role placement first (only that slot when pinned), then every other enabled slot — Primary, Secondary, then the rest; one canary is shared by every request for a minute |
Aetherium:Embeddings:RawModelId |
string | text-embedding-nomic-embed-text-v1.5 |
code | Live — the model the untemplated path names; lessons embed through it, so moving ModelId leaves stored lesson vectors comparable |
Aetherium:Embeddings:PrefixScheme |
string | nomic-v1 |
code | Live — names the template pair; ModelId|PrefixScheme is the stamp every collection and chunk carries |
Aetherium:Embeddings:QueryTemplate |
string | search_query: {text} |
code | Live |
Aetherium:Embeddings:DocumentTemplate |
string | search_document: {title}\n\n{text} |
code | Live — without a title the title line is dropped |
Aetherium:Embeddings:BatchSize |
int | 32 |
code | Live — inputs per request (array input) |
Aetherium:Embeddings:MaxInputChars |
int | 6000 |
code | Live — longest templated input sent |
AI_KnowledgeInjection |
string | unset (on) | SystemSettings | Live — false turns off knowledge injection into prompts; benchmark mode always turns it off |
Knowledge_ActiveGeneration |
int | unset (0) |
SystemSettings | Live — the generation reads and writes use: 0 is the unsuffixed names, N is aetherium_global_gN, aetherium_domain_<name>_gN, aetherium_project_<id>_gN. Read every 10 s, so a flip takes effect without a restart |
Aetherium:KnowledgeIndex:WorkRoot |
string | /var/lib/aetherium/kb-rebuild |
code | Live — one g<N>-<utc>/ directory per rebuild: export-old.jsonl, lessons-g<N>.jsonl, manifest-g<N>.jsonl, report.json |
Aetherium:KnowledgeIndex:KbRoot |
string | /var/lib/aetherium/kb |
code | Live — candidates/p<project>/<id>.md holds each promotion candidate's text until it is approved or rejected (a workspace cleanup can delete the project's own copy); domains/<domain>/<id>.md holds each approved document; media/<hash>.json caches each picture's caption and each recording's transcript by the file's content, and a media candidate or document keeps its file beside its text |
Aetherium:KnowledgeIndex:LeaseWaitMinutes |
int | 60 |
code | Live — how long a build waits for its batch lease on the embedding slot |
Aetherium:KnowledgeIndex:PausePollSeconds |
int | 15 |
code | Live — between write batches a build pauses while a run lease holds the embedding slot, re-checking at this interval |
Aetherium:KnowledgeIndex:MaxDocsPerWrite / MaxCharsPerWrite |
int | 40 / 200000 |
code | Live — size of one embedding write batch |
Aetherium:KnowledgeIndex:SelfRetrievalGate |
double | 0.9 |
code | Live — per pack, the share of sampled chunks that find themselves first (SelfRetrievalSamplesPerPack, 50) |
Aetherium:KnowledgeIndex:KnownItemGate |
double | 0.9 |
code | Live — hit@5 of KnownItemQueries (60) document-title lookups, each asked within the pack set of every shipped template that names the document's pack |
Aetherium:KnowledgeIndex:OverlapGate |
double | 0.95 |
code | Live — overlap@10 of the HNSW index against exact search, on packs up to OverlapMaxPackChunks (5000) chunks |
Aetherium:KnowledgeIndex:FingerprintGate |
double | 0.999 |
code | Live — cosine of the probe embedding against the stamp's fingerprint |
Aetherium:Media:CaptionModel |
string | qwen/qwen3.5-9b |
code | Live — the vision model that captions a picture for the knowledge base, as its slot's router names it |
Aetherium:Media:CaptionSlot |
string | Worker |
code | Live — the slot the caption model loads on, under one batch lease (media-captions) per batch of pictures |
Aetherium:Media:SttCommand |
string | /data/stt/stt-local.py |
code | Live — takes one audio file and prints its transcript; runs on the CPU, no lease |
Aetherium:Media:SegmentSeconds / MaxAudioSeconds |
int | 600 / 3600 |
code | Live — a longer recording is cut into pieces of this length; a recording over the maximum is not transcribed |
Aetherium:Media:MaxImageBytes |
long | 20971520 |
code | Live — a larger picture is not captioned |
Tiers¶
The Aetherium:Tiers block sets per-tier retention and container resource limits. An unknown tier name falls back to Staging.
| Key (per tier) | Production | Staging | Temp |
|---|---|---|---|
…:{Tier}:RetentionDays |
-1 (never) |
30 |
7 |
…:{Tier}:MaxProjects |
-1 (unlimited) |
50 |
20 |
…:{Tier}:ArchiveBeforeDelete |
true |
true |
true |
…:{Tier}:Resources:CpuLimit |
4.0 |
2.0 |
1.0 |
…:{Tier}:Resources:MemoryLimit |
8g |
4g |
2g |
…:{Tier}:Resources:DiskQuota |
50g |
20g |
5g |
All live via the tier-policy service. Resources:CpuShares (default 1024) has no appsettings key — it is default-only.
The Resources values are applied when a project's team container is created: a Production project's container gets 4.0 CPU / 8g, a Temp project's 1.0 CPU / 2g. A team that declares its own default_resources overrides the tier, but only when that declaration differs from the defaults — a block restating 2.0/4g asks for what it would already get and defers to the tier.
Containers (runtime lifecycle)¶
The lifecycle timers are SystemSettings rows (re-read each cleanup cycle), not the same-named Aetherium:Cleanup keys, which are shadowed (see below).
| Key | Type | Default | Layer | Live? |
|---|---|---|---|---|
Container_IdleTimeoutMinutes |
int | 60 |
SystemSettings | Live |
Container_DestroyGracePeriodMinutes |
int | 30 |
SystemSettings | Live |
Container_CleanupIntervalMinutes |
int | 5 |
SystemSettings | Live |
Container_AutoSnapshotBeforeCleanup |
bool | true |
SystemSettings | Live — Production and Staging tiers only; Temp-tier projects are never snapshotted |
Container_MaxSnapshotsPerProject |
int | 5 |
SystemSettings | Live |
Container_SnapshotRetentionDays |
int | 30 |
SystemSettings | Live |
Cleanup (Aetherium:Cleanup) — mostly shadowed¶
Only two keys here are read. The rest are shadowed by the Container_* SystemSettings rows above — change those, not this block.
| Key | Type | Default | Live? |
|---|---|---|---|
Aetherium:Cleanup:ArchiveRetentionDays |
int | 30 |
Live |
Aetherium:Cleanup:CompressArchives |
bool | true |
Live |
Aetherium:Cleanup:IdleTimeoutMinutes |
int | 60 |
Inert (shadowed) |
Aetherium:Cleanup:DestroyGracePeriodMinutes |
int | 30 |
Inert (shadowed) |
Aetherium:Cleanup:CleanupIntervalMinutes |
int | 5 |
Inert (shadowed) |
Aetherium:Cleanup:AutoSnapshotBeforeCleanup |
bool | true |
Inert (shadowed) |
Aetherium:Cleanup:MaxSnapshotsPerProject |
int | 5 |
Inert (shadowed) |
Aetherium:Cleanup:SnapshotRetentionDays |
int | 30 |
Inert (shadowed) |
Bridge / OpenClaw / webhooks¶
| Key | Type | Default | Layer | Live? |
|---|---|---|---|---|
Aetherium:Bridge:Enabled |
bool | true |
appsettings | Live |
Aetherium:Bridge:PublicHost |
string | <deployment host/IP> |
appsettings | Live — set per deployment |
Aetherium:Bridge:AppUrl |
string? | (unset) | appsettings | Live — the app's address (https://<host>/app) that operator notices link to; unset, a notice names the page instead |
Aetherium:Bridge:PortAllocation:RangeStart |
int | 3000 |
appsettings | Live |
Aetherium:Bridge:PortAllocation:RangeEnd |
int | 3499 |
appsettings | Live |
Aetherium:Bridge:PortAllocation:ScanTimeout |
int | 5 |
appsettings | Live |
Aetherium:Bridge:PortAllocation:Tiers:Production:Start / :End |
int | 3000 / 3099 |
appsettings | Live |
Aetherium:Bridge:PortAllocation:Tiers:Staging:Start / :End |
int | 3100 / 3199 |
appsettings | Live |
Aetherium:Bridge:PortAllocation:Tiers:Temp:Start / :End |
int | 3200 / 3499 |
appsettings | Live |
Aetherium:Bridge:Webhooks:Targets[]:Name |
string | <target-name> |
appsettings | Live |
…:Targets[]:Url |
string | http://localhost:18790/hooks/agent |
appsettings | Live |
…:Targets[]:Secret |
string | REPLACE_ME_VIA_LOCAL_OVERRIDE |
appsettings | Live — HMAC secret; never commit a real value |
…:Targets[]:Events |
string (JSON array) | ["build.*", "tool.approval.requested"] |
appsettings | Live |
…:Targets[]:Enabled |
bool | true |
appsettings | Live |
…:Targets[]:Kind |
string? | (unset) | appsettings | Live — unset for a webhook listener; telegram posts each event's message to the Bot API ({Url}/bot{Secret}/sendMessage, Url defaulting to https://api.telegram.org) and skips events without one |
…:Targets[]:ChatId |
string? | (unset) | appsettings | Live — the chat a telegram target sends to |
Webhook targets are dual-sourced: this config block plus the webhook_targets table, merged at read time.
Maintenance (self-maintenance source mount)¶
| Key | Type | Default | Layer | Live? |
|---|---|---|---|---|
Aetherium:Maintenance:AetheriumSourcePath |
string | ${AETHERIUM_SOURCE_PATH} (env-expanded; environment overlays may pin an absolute path) |
appsettings | Live |
Aetherium:Maintenance:ReadOnly |
bool | false |
appsettings | Live |
The host path of the Engine's own source checkout, mounted at /aetherium-src inside containers whose template sets mount_aetherium_source: true (the maintenance and quick-fix templates). A blank or unresolved path skips the mount with a warning rather than guessing — a wrong path would mount a stale tree and silently break self-maintenance. ReadOnly: false is deliberate: the upgrade engineer edits the tree, and the restricted-command approval gate is the safety layer for anything that builds or deploys from it.
Security & permissions¶
| Key | Type | Default | Layer | Live? |
|---|---|---|---|---|
Aetherium:PermissionRulesPath |
string | {BaseDir}/config/permissions.yaml |
code (absent from files) | Live |
Aetherium:ToolCatalog:FailOnDrift |
bool | true |
code (absent from files) | Live |
ToolApproval_TimeoutSeconds |
int | 300 |
SystemSettings | Live (unseeded) |
allowed_domains |
list | (empty → DLP only) | Template (per team) | Live |
| Permission rules | YAML | rules: [] (empty) |
config/permissions.yaml |
Live (hot-reloaded; always_deny enforced) |
Inert sections (defined, never read)¶
Setting any key in these sections has no effect today.
| Section | Keys | Why inert |
|---|---|---|
Gemini:ApiKey |
(single key) | Present in appsettings.json, never read anywhere. |
Removed sections
Aetherium:Docker:* and Aetherium:Pools:* were bound but never read; both sections and their option classes were removed on 2026-09-06. A key left under either name in a local override is now simply ignored.
Hardcoded literals (no config key)¶
Values that look configurable but are baked into code. Candidates for promotion to keys; flagged so you don't hunt for a knob that doesn't exist.
| Value | Location | Notes |
|---|---|---|
Default container image python:3.11-slim |
Models/DockerImageCatalog.cs |
No key; a template's base_image overrides it per project |
Approval timeout 300 s |
Services/ToolApprovalService.cs |
Default when ToolApproval_TimeoutSeconds is unset |
| Egress always-allowed hosts | Services/EgressPolicyService.cs |
localhost, 127.0.0.1, ::1, host.docker.internal — no key |
Audit retention is unbounded
The tool-security audit service writes every gated call to PostgreSQL with no retention, purge, or config key — the audit table grows without limit. Prune it yourself if you run the Engine continuously.
Option classes¶
The POCO classes that bind configuration sections, for code-side lookup.
| Class | Section | Status |
|---|---|---|
AetheriumOptions |
Aetherium |
Live (Tiers; Cleanup partial) |
StorageOptions |
Aetherium:Storage |
Live (+ post-configure path expansion) |
TierOptions / ResourceLimits |
Aetherium:Tiers[*] |
Live |
CleanupOptions |
Aetherium:Cleanup |
Partial — only ArchiveRetentionDays + CompressArchives |
ChromaDbOptions |
ChromaDb (root) |
Partial — DefaultCollection inert |
LMStudioOptions |
Aetherium:LMStudio |
Live |
VramOrchestrationOptions |
Aetherium:VramOrchestration |
Live |
ImageFinishingOptions |
Aetherium:ImageFinishing |
Live |
KnowledgeSeedingOptions |
Aetherium:KnowledgeSeeding |
Partial — MaxConcurrentJobs inert |
SignalR and the named HttpClients are configured inline in Program.cs with hardcoded values (SignalR client timeout 10 min, keep-alive 15 s, max receive 10 MB; webhook HttpClient 60 s; LM Studio management 120 s, with read-only slot listings cut off at Aetherium:LMStudio:ProbeTimeoutSeconds; ChromaDB 30 s; the /v1 inference relay has no timeout, so a streamed reply is bounded by the client) — not key-driven.
Where to go next¶
- §09 — Configuration — how a value resolves and why the model chain is shaped the way it is.
- Schema & Data Model — template-level config (
circuit_breaker,context_window). - API · System & Operations — the
POST /api/settingsupsert surface forSystemSettings.