# ATLAS Configuration Reference Reference for the environment variables, command-line flags, and configuration files across every ATLAS service. Hardware/runtime settings have safe defaults; model selection is explicit. Some variable groups are documented where they are used and only pointed to from here: bootstrap-only knobs (`ATLAS_BOOTSTRAP_*`, `ATLAS_REPO_URL`, `ATLAS_INSTALL_DIR`, `ATLAS_GO_VERSION`) in [SETUP.md](SETUP.md), TUI-only variables (`ATLAS_TUI_LOG`, `ATLAS_TUI_MOUSE`, `ATLAS_TUI_STARTUP_NOTE`) in [CLI.md](CLI.md)'s environment table. --- ## Quick Start ```bash atlas init # selects a registry model and writes .env docker compose up -d ``` For a manual/BYO install, copy `.env.example` and set `ATLAS_MODEL_FILE` and `ATLAS_MODEL_NAME`. Compose intentionally fails when either is missing instead of silently choosing a model family. --- ## 1. Docker Compose (.env) These variables are read by `docker-compose.yml` and control host-side port mappings and model paths. Copy `.env.example` to `.env` to configure: **Precedence** (highest first): process environment → `.env` file → in-code default. An empty value in a higher layer is treated as unset and falls through. `atlas.config_schema.resolve_typed()` is the single resolver that implements this. | Variable | Default | Description | |----------|---------|-------------| | `ATLAS_MODELS_DIR` | `./models` | Host path to directory containing GGUF model weights | | `ATLAS_MODEL_FILE` | **required** | Selected model filename (must exist in ATLAS_MODELS_DIR) | | `ATLAS_MODEL_NAME` | **required** | Selected model identifier; normally the filename without `.gguf`. `.env` must set it; individual services fall back to `local-model` if unset. | | `ATLAS_CTX_SIZE` | `131072` | Context window size in tokens, TOTAL across all parallel slots (mapped to `CONTEXT_LENGTH` inside the llama container). Sized per model + GPU by `atlas tier fit --write`. | | `ATLAS_PARALLEL_SLOTS` | `4` | Concurrent request slots. llama-server divides `ATLAS_CTX_SIZE` by this for per-slot context. | | `ATLAS_MAX_TOKENS` | `8192` | Per-turn generation ceiling (`max_tokens`). An agent turn is a tool call or a whole-file `write_file` (a few thousand tokens); 8192 covers a ~600-line generation and bounds a content runaway to a couple minutes. Raise only for genuinely large single-file writes. | | `ATLAS_MAX_COMPLETION_TOKENS` | `8192` | Ceiling the proxy forces onto **passthrough** generation requests (`/v1/chat/completions`, `/v1/completions`, `/completion`, `/completions`, `/infill`) that carry no completion bound, a non-positive one (`-1` means "unlimited" to llama-server), or one above the ceiling. Without it, a client that disconnects mid-stream leaves an unbounded zombie generation holding a llama slot until the context fills. The agent loop's own calls are governed by `ATLAS_MAX_TOKENS`, which they always send. | | `ATLAS_AGENT_HISTORY_BUDGET` | (unset) | Optional hard ceiling (tokens) on the kept conversation window. **Unset by default**: the window is sized to the slot — `per-slot context − ATLAS_MAX_TOKENS − 2048 − slot/8` (the `slot/8` term is tokenizer slack: the chars/4 estimate under-counts dense code and JSON-escaped tool results) — so it uses the whole slot rather than an artificial cap. Set this only to bound per-turn re-encode cost below slot capacity on SWA models (trades retained context for faster turns). The active file under edit is pinned in the trim regardless, so it never falls out of the window — and its size is counted against the budget. If llama-server still rejects a prompt as over-context, the loop force-trims to the minimum window and retries once instead of failing the session. | | `ATLAS_DEDUP_READS` | `1` | When `1`, a whole-file re-read of an unchanged file returns a compact pointer **only if the content is still in the live context**; if the content was trimmed out, the full file is re-served (so the model never edits blind). Set `0` to always serve the full re-read. | | `ATLAS_KV_TYPE_K` | `f16` | KV-cache K quantization (`f16`, `q8_0`, `q4_0`). Set by `atlas tier fit --write`. | | `ATLAS_KV_TYPE_V` | `f16` | KV-cache V quantization. Set by `atlas tier fit --write`. | | `ATLAS_UBATCH` | `1024` | llama-server micro-batch size (`-ub`). Drives the compute-buffer VRAM cost (~ubatch × n_embd × 280 bytes) — the term that OOMs first on tight cards. Set by `atlas tier fit --write`. | | `ATLAS_BATCH` | `1024` | llama-server logical batch size (`-b`). Must be no larger than `ATLAS_UBATCH` because self-embeddings are always enabled. Set by `atlas tier fit --write`. | | `ATLAS_EMBED_POOLING` | `mean` | Pooling mode for the self-embedding `/embedding` endpoint (`--pooling`). The Geometric Lens `C(x)`/`G(x)` artifacts are trained on one convention; serving another silently invalidates every score. Leave at `mean` unless your lens artifacts were trained on `none` (per-token). L2 normalization is requested per-call by the lens (`embd_normalize` in the `/embedding` body — llama-server has no server-side normalize flag). The lens enforces both via `model_identity.json`'s `embedding_contract` and refuses to serve on mismatch. | | `ATLAS_PROJECT_DIR` | (cwd at `compose up`) | Host directory bind-mounted to `/workspace` inside the atlas-proxy container. Switch projects by re-creating the proxy container with this var set. | | `ATLAS_LENS_HOST_DIR` | `./lens_training` | Host path of the lens training-data corpus, bind-mounted into atlas-proxy at `/data/lens_training` (see `ATLAS_LENS_DATA_DIR`, § 2) so `atlas lens retrain` on the host reads the corpus the proxy writes. | | `ATLAS_GHCR_OWNER` | `itigges22` | GHCR namespace to pull images from. Set to your own GitHub username if you've published forked images. | | `ATLAS_IMAGE_TAG` | `latest` | Image tag to pull (`latest` for main, `dev` for the dev branch, `3.1.3` or `sha-...` for pinned releases). Registry semver tags carry no leading `v`; `atlas upgrade`/`atlas rollback` strip one if given. | | `ATLAS_LLAMA_PORT` | `8080` | llama-server host port | | `ATLAS_LENS_PORT` | `8099` | Geometric Lens host port | | `ATLAS_V3_PORT` | `8070` | V3 Pipeline service host port | | `ATLAS_SANDBOX_PORT` | `30820` | Sandbox host port (container listens on 8020) | | `ATLAS_SANDBOX_MEM` | `4g` (compose fallback); `atlas init` writes ~75% of host RAM | Memory cap on the sandbox container (`docker` `mem_limit`). The sandbox runs untrusted model-authored shell, so this caps RAM to stop a runaway from OOMing the host. The compose fallback is a conservative `4g` so a raw `docker compose up` is never unlimited; `atlas init` detects total host RAM and writes ~75% (2 GiB floor) — the recommended setting. Accepts `docker` size strings (`8g`, `512m`) or bytes; `0` removes the cap entirely (unlimited — not recommended). | | `ATLAS_SANDBOX_PIDS` | `1024` | PID cap on the sandbox container (`docker` `pids_limit`) — a kernel-level fork-bomb stop, far above any normal build and far below a bomb. Constant across hosts, so it defaults inline in compose; override only if a legitimate build needs more concurrent processes. | | `ATLAS_SANDBOX_MAX_EXECUTION_TIME` | `300` | Per-call execution ceiling (seconds) inside the sandbox executor. Compose maps this onto the executor's `MAX_EXECUTION_TIME`; the 300s default matches the proxy's `run_command` cap so long builds/tests aren't cut off by the executor's internal 60s default. | | `ATLAS_SANDBOX_TMP_SIZE` | `2G` | Size ceiling for the sandbox's `/tmp` tmpfs (pip download/build dir, mktemp targets). tmpfs sizes are ceilings, not reservations — raising them costs nothing until bytes are written, and written bytes count against `ATLAS_SANDBOX_MEM`, which stays the real backstop. | | `ATLAS_SANDBOX_PIP_SIZE` | `1G` | Size ceiling for the sandbox's `~/.local` tmpfs (`pip install --user` target). Heavy scientific installs need room: pandas+pyarrow+numpy alone unpack ~400 MB, which overflowed the previous fixed 256 MB with `No space left on device`. | | `ATLAS_SANDBOX_CACHE_SIZE` | `512M` | Size ceiling for the sandbox's `~/.cache` tmpfs (pip wheel cache, ruff, mypy). | | `ATLAS_PROXY_PORT` | `8090` | atlas-proxy host port (TUI and OpenAI-compat clients connect here) | | `ATLAS_BACKEND` | `cuda` | Inference backend. `cuda` (NVIDIA, V3.1.0+), `rocm` (AMD, V3.1.1, x86_64 only), `vulkan` (universal fallback), `metal` (Apple Silicon hybrid: native llama-server + Docker for the rest — see [SETUP_MACOS.md](SETUP_MACOS.md)), `sycl` (Intel Arc, roadmap). Set by `atlas init`; the entrypoint scripts read this to pick per-vendor env vars. ROCm + Vulkan + Metal also require bringing up the stack with `-f docker-compose.rocm.yml`, `-f docker-compose.vulkan.yml`, or `-f docker-compose.macos.yml` respectively (the wizard prints the right command). On aarch64 hosts (DGX Spark, Snapdragon X Elite, Jetson, Pi 5) `atlas init` filters out `rocm` since AMD has no arm64 release — see [SETUP.md § arm64](SETUP.md#arm64). | | `ATLAS_MACOS_PREFIX` | `~/.atlas/macos` | macOS Metal only. Native llama.cpp install root shared by setup, launcher, and doctor. Set this when setup used `--prefix`. | | `ATLAS_LLAMA_HOST` | `127.0.0.1` | macOS Metal only. Bind address for the native llama-server launched by `scripts/atlas-llama-macos.sh`. Loopback by default; override only when access from another host is intentionally required. | | `ATLAS_GPU_INDEX` | (unset — all GPUs visible; the ROCm/Vulkan overlay files default it to `0`) | Vendor-local index of the GPU ATLAS should use on multi-GPU hosts. Compose passes it into the llama-server container; the entrypoint maps it to `CUDA_VISIBLE_DEVICES` (NVIDIA) or the HIP/Vulkan equivalent, and skips the export when empty. `docker-compose.rocm.yml` and `docker-compose.vulkan.yml` pin it to `0` when unset. | | `ATLAS_GFX_TARGET` | `gfx1100;gfx1101;gfx1102;gfx1030;gfx90a` | **ROCm only.** AMD compute target(s), semicolon-separated. Forwarded to `Dockerfile.rocm` as `AMDGPU_TARGETS` at build time. Trim to your GPU for a smaller image — see [SETUP.md § AMD GPU Targets](SETUP.md#amd-gpu-targets-dockerfilerocm). | | `ATLAS_ROCM_TAG` | `6.2-complete` | **ROCm only.** Base image tag for `rocm/dev-ubuntu-22.04`. Bump when you want to test a newer ROCm release. | | `ATLAS_UBUNTU_TAG` | `24.04` | **Vulkan only.** Base image tag for the `ubuntu` build stage of `Dockerfile.vulkan` (compose build arg). | | `ATLAS_HSA_OVERRIDE_GFX_VERSION` | (unset) | **ROCm only.** Force a specific HSA gfx version at runtime — workaround for "officially unsupported" GPUs (e.g., older Vega) that still work with a compatible target. Example: `10.3.0` makes RDNA1 cards masquerade as RDNA2 for HIP kernel selection. | | `ATLAS_CONFIG_SCHEMA_VERSION` | (stamped by `atlas config migrate`) | Schema-version stamp that `atlas config migrate` writes into `.env` — leave it in place. | Docker Compose also sets inter-service URLs using Docker networking (e.g., `http://llama-server:8080`). These are fixed inside the Docker network and usually do not need to be configured by users. On macOS Metal, `docker-compose.macos.yml` keeps the container-side URL at `llama-server:8080` but forwards it to the native host-side `${ATLAS_LLAMA_PORT:-8080}`, so the port can move when 8080 is already occupied. **Runtime-tuning passthrough.** `.env.example` carries a commented "Runtime tuning" section, and compose passes each key through to the owning container as an empty-default env var — so setting any of `ATLAS_V3_TIMEOUT`, `ATLAS_MAX_TOKENS`, `ATLAS_MAX_COMPLETION_TOKENS`, `ATLAS_AGENT_HISTORY_BUDGET`, `ATLAS_LENS_RETRAIN_MIN`, `ATLAS_KEEP_LLAMA_WARM`, `ATLAS_FRESH_SLOT_PER_SESSION`, the repetition-sampling keys (`ATLAS_DRY_MULTIPLIER`, `ATLAS_DRY_BASE`, `ATLAS_DRY_ALLOWED_LENGTH`, `ATLAS_DRY_PENALTY_LAST_N`, `ATLAS_REPEAT_PENALTY`, `ATLAS_REPEAT_LAST_N`) (proxy, § 2), `ATLAS_SANDBOX_MAX_EXECUTION_TIME` (sandbox, § 5), or `ATLAS_GPU_INDEX` / `ATLAS_GRAMMAR_MODE` in `.env` reaches the container without a compose edit. (`ATLAS_BACKEND` is not in the passthrough: it drives the host-side overlay choice, and the ROCm/Vulkan overlays set their own container-side value.) An empty/unset key means the in-code default applies. **Restart policy.** Every service in `docker-compose.yml` runs with `restart: unless-stopped`, so the stack comes back up after a host reboot or a container crash without a manual `docker compose up`. **Removed variables (`.env`).** These keys from older installs are ignored on read; `atlas config validate` flags them and `atlas config migrate` drops them: `ATLAS_REGISTRY` (the model registry is in-package), `ATLAS_REDIS_MAXMEMORY` and `ATLAS_REDIS_MEM` (lens state moved from Redis to SQLite — `SQLITE_DB_PATH`, § 4; [ADR 0007](adr/0007-sqlite-state-store.md)), `ATLAS_ENABLE_TRAINING` (training is always available), and `ATLAS_RPG_PLANNING` (RPG planning was removed — [issue #148](https://github.com/itigges22/ATLAS/issues/148) is the record). K3s-side removals are listed in § 8.10. `PARALLEL_SLOTS` and `KV_CACHE_TYPE_K/V` are accepted as fallbacks, but the canonical `ATLAS_*` names take precedence and are what `atlas init` and `atlas tier fit --write` write. #### Backend-vs-Compose-override matrix | `ATLAS_BACKEND` | Required compose invocation | |---|---| | `cuda` (default) | `docker compose up -d` | | `rocm` | `docker compose -f docker-compose.yml -f docker-compose.rocm.yml up -d` | | `vulkan` | `docker compose -f docker-compose.yml -f docker-compose.vulkan.yml up -d` | | `metal` (hybrid) | `./scripts/atlas-llama-macos.sh` + `docker compose -f docker-compose.yml -f docker-compose.macos.yml up -d` | | `sycl` | Not yet packaged — Intel Arc users should use `vulkan` for now | `atlas init` prints the right invocation as part of its "Next steps" summary. CLI-managed Compose operations also resolve the overlay from `ATLAS_BACKEND`; `atlas-bootstrap.sh` picks the Linux override automatically from its hardware probe. ### Adding your own model (drop-in / unregistered) `atlas init` and `atlas model install ` only know models in the built-in registry. To run a model that *isn't* registered (a brand-new release, a custom quant), wire it up by hand. `atlas onboard` automates the safe parts of this and stops at the one step only you can do (the rebuild); the manual flow is: 1. **Place the GGUF in `ATLAS_MODELS_DIR`** (default `./models`). Either drop the file in yourself, or fetch it with `atlas model install --url ` (downloads into the models dir; no SHA pin since it's unregistered). 2. **Point `.env` at it** — set both keys: ```dotenv ATLAS_MODEL_FILE=your-model-Q4_K_M.gguf ATLAS_MODEL_NAME=your-model-Q4_K_M ``` 3. **Size the runtime for this model + your GPU**: ```bash atlas tier fit # preview: ctx / KV type / ubatch + the VRAM budget atlas tier fit --write # apply to .env ``` This reads the GGUF header (layer count, KV-head geometry, sliding-window layout) and your GPU's VRAM, and solves for the largest context that keeps inference **fully on-GPU**. Different models have wildly different KV footprints — a budget tuned for one model can OOM or silently spill to CPU (5× slower) on another. The server runs with `--fit off`, so an oversized config refuses to start rather than spilling. If it reports the model doesn't fit, it names the largest quant file size that would — see [TROUBLESHOOTING.md § What fits on my GPU?](TROUBLESHOOTING.md#what-fits-on-my-gpu) for pre-download sizing guidance. 4. **Restart inference only** (don't tear the stack down — that triggers a long CUDA rebuild): `docker compose up -d llama-server --no-deps --force-recreate`. 5. **Confirm the engine recognizes the architecture.** `docker compose logs -f llama-server` — a healthy load ends in `server is listening`. If you instead see `error loading model: unknown (model) architecture ''`, your `atlas-llama` image's bundled llama.cpp predates that architecture and **you must rebuild the inference image**: ```bash # The image pins llama.cpp (LLAMA_CPP_REV in inference/Dockerfile.v31) so # the hidden-states patch applies cleanly — a plain rebuild reuses that same # pinned revision and will NOT pick up newer architectures. Override the # pin with a llama.cpp commit that knows your model's architecture: docker compose build --build-arg LLAMA_CPP_REV= llama-server # ~70 min on CUDA ``` > ⚠️ **Do not strip ATLAS's custom llama.cpp features when rebuilding.** The > build re-applies `inference/patches/expose-hidden-states.patch` (the > per-layer `hidden_states` extension the Geometric Lens relies on) to the > freshly-cloned source. If upstream has drifted, the `git apply` step can fail > and the build aborts — **rebase the patch, don't delete it or remove the > `git apply` line from `inference/Dockerfile.v31`**, or you'll silently lose > the lens plumbing. See [TROUBLESHOOTING.md](TROUBLESHOOTING.md) "Rebuilding > llama.cpp for a new model architecture". 6. **Retrain the Geometric Lens for the new model.** The lens `C(x)` is dimension-coupled to the model's hidden size, so artifacts trained for one model won't load against another. `atlas lens check` reports the mismatch. Build the new model's *own* candidates and retrain — all through the CLI. Connectivity (ports, model file) resolves automatically from your deployment's config (`.env` on Docker, `atlas.conf` on K3s): ```bash # 1. Generate + self-label this model's solutions (hours on a large model). # Results land in benchmark/results//v3_lcb/per_task/ (code + passed). atlas bench --run-id mymodel_lens --tasks 200 # 2. Retrain C(x) on those candidates. --force overwrites existing # artifacts (writes geometric-lens/geometric_lens/models/cost_field.pt) atlas lens build --force --from-results benchmark/results/mymodel_lens/v3_lcb/per_task # 3. ASA control vector; defaults to 75% of the loaded model's layer count atlas asa build ``` (The candidate sandbox executes locally via a `python3` subprocess — only llama-server + geometric-lens are network dependencies. `atlas lens build` also writes a `provenance.json` manifest — dataset, sample counts, metrics, hyperparameters, per-file hashes — into every activated bundle.) **Per-model calibration.** The learned C(x) energy scale and G(x) score distribution are model-specific. `atlas lens build` therefore writes `model_identity.json` (the loaded model name and embedding dimension), `cx_normalization.json` (sigmoid midpoint/steepness derived from this model's PASS/FAIL energies) and `gx_thresholds.json` beside the weights. One model's grounded G(x) writes may cluster at 0.05, another's at 0.45, so a single hardcoded off-rails/regression cutoff fires for one model and never for another. A threshold file looks like: ```json { "off_rails": 0.15, "low": 0.30, "severe": 0.05 } ``` The lens service loads it per-model and returns the values in every score response; the proxy uses them for its run-of-N / severe regression checks. `off_rails` is the per-token "stop generating" cutoff; `low` is the aggregate `gx_min` that counts as a low-quality write (run-of-2 → corrective); `severe` is the single-write cutoff that intervenes immediately. **If the file is absent or invalid, scores remain visible as uncalibrated telemetry but threshold-based intervention is disabled.** ATLAS never borrows another model's cutoffs. Calibrate them from the same labeled candidates used in step 2. The build uses the 5th, 10th, and 20th percentiles of this model's passing scores for `severe`, `off_rails`, and `low`, then writes `gx_thresholds.json` into `geometric-lens/geometric_lens/models/`. It publishes/downloads with the rest of the lens artifacts. (`atlas lens build` emits this file automatically, calibrated from the run's `pass` percentiles.) The Lens service also requires `model_identity.json` to match `ATLAS_MODEL_NAME`. Matching embedding dimensions alone are not sufficient: two different models can share a hidden width while having unrelated representation geometry. Missing or mismatched identity keeps Lens scoring unavailable and is surfaced by readiness, `atlas lens check`, and the TUI. **Retraining from your own use (`atlas lens retrain`).** Instead of a bench run, the lens can learn from the workloads you actually run: each agent file write is collected, and your verification (per-file accept/deny + a pass 👍/👎) labels and weights it (see `ATLAS_LENS_DATA_DIR` / `ATLAS_LENS_RETRAIN_MIN`). Once enough balanced samples accumulate, `atlas lens retrain` runs the same build pipeline on that corpus (weighted G(x) — a 👎 pass down-weights even its accepted files; a denial is a full-weight negative) and emits fresh, calibrated `gx_thresholds.json`. This makes the lens representative of *your* work (e.g. Dockerfiles/config the algorithmic-bench lens never learned). Do **not** reuse another model's solution set — both lens halves are dimension-coupled to the model: `C(x)` must learn *this* model's cost geometry, and `G(x)`'s PCA projection is shaped to the embedding width. `atlas lens build` trains both from the same samples in one run. After the build, restart the lens service so it loads the new artifacts: `docker compose restart geometric-lens`. 7. **Verify:** `atlas doctor` should come back green (lens dim now matches, model loads, e2e smoke passes). > **Templating is not a per-model chore.** The chat template ships *inside* the > GGUF and is rendered via llama-server's `--jinja` — you never hand-write one. > For reasoning models, ATLAS sends `enable_thinking: false` and falls back to > `reasoning_content` if a model emits its answer there, so most models work > without any template work. --- ## 2. atlas-proxy The Go proxy that runs the agent loop, routes tool calls, and orchestrates the ATLAS pipeline (llama-server + Lens + V3 + sandbox). ### Environment Variables | Variable | Default | Description | |----------|---------|-------------| | `ATLAS_PROXY_PORT` | `8090` | Port to listen on | | `ATLAS_INFERENCE_URL` | `http://localhost:8080` | llama-server endpoint for generation | | `ATLAS_LLAMA_URL` | (falls back to ATLAS_INFERENCE_URL) | llama-server endpoint for grammar-constrained calls | | `ATLAS_LENS_URL` | `http://localhost:8099` | Geometric Lens scoring endpoint | | `ATLAS_SANDBOX_URL` | `http://localhost:30820` | Sandbox code execution endpoint | | `ATLAS_V3_URL` | `http://localhost:8070` | V3 Pipeline service endpoint | | `ATLAS_LENS_DATA_DIR` | `/data/lens_training` | Where collected lens-training samples are written (per-model `samples.jsonl`). Each agent file-write becomes a candidate sample; a `/feedback` call (per-file accept/deny + pass 👍/👎) labels and weights it. Backed by the `${ATLAS_LENS_HOST_DIR:-./lens_training}` host bind mount (§ 1) so it persists across proxy restarts, accumulates toward a retrain, and is readable by the host CLI. Consumed by `atlas lens retrain`. | | `ATLAS_LENS_RETRAIN_MIN` | `2000` | Labeled-sample count at which the TUI surfaces the "retrain available" prompt (`/v1/lens/training-status`). A balance guard also requires ≥ 25% of this in the minority class, so the corpus isn't all-pass or all-fail. Raise for a larger, more representative corpus before retraining. | | `ATLAS_V3_TIMEOUT` | `180` | Interactive wall-clock cap (seconds) on a single V3 pipeline call from the agent path (`write_file` / `edit_file`). On timeout the proxy falls back to the model's own content (still syntax- and structural-gated) instead of hanging the session — bounds the long-tail Phase-3 repair stall (observed ~11 min on a 103-line write). The v3-service reads the same value for its refinement budget gate: when the remaining budget cannot afford one refinement iteration it skips straight to the fallback (`refinement_skip` event) instead of starting work the bridge would abandon. Set `0` to disable the cap (uncapped behavior for offline bench runs). | | `ATLAS_MAX_READ_BYTES` | (derived) | Byte cap on a single `read_file` result (`readFileByteCap` in `proxy/tools.go`). Unset, the cap is half the per-slot context budget treated as a worst-case 1-token/char (dense content: G-code, minified JS, base64), clamped to [2 KB, 200 KB] — one read can never overflow the slot. Any positive integer overrides the derived value. A capped read reports the real `EndLine` of the shown range and records only the shown bytes for dedup. | | `ATLAS_MODEL_NAME` | `local-model` | Neutral fallback request identifier; `/v1/models` reports llama-server's loaded model when available | | `ATLAS_KEEP_LLAMA_WARM` | `1` | Set to `0` to disable the keep-warm goroutine that pings llama-server every 45s with a 1-token completion. Keeping warm avoids the cold-start path that fires after 1-2 min idle. Disable for CPU-only or tightly power-budgeted setups. | | `ATLAS_FRESH_SLOT_PER_SESSION` | `1` | Set to `0` to disable per-session llama.cpp KV-slot erase. With it enabled (default), the proxy POSTs `/slots/0?action=erase` at the start of each agent loop invocation, giving each turn a clean cache. Adds ~1-2s to the first turn but prevents cross-session token-state leakage (e.g. filenames hallucinated from prior sessions). | | `ATLAS_MAX_TURNS` | (unset) | Operator override for the agent-loop turn cap. Any positive int caps all tiers; unset / `0` / invalid falls through to tier defaults (T0=5, T1/T2/T3=uncapped). | | `ATLAS_REASONING_BUDGET` | `6144` | Per-turn reasoning-token budget (estimated at 4 chars/token). When a generation accumulates this much `reasoning_content` without emitting any content tokens, the proxy cuts the stream and re-prompts. Bounds reasoning spirals. `0` disables. Forwarded to the proxy container by `docker-compose.yml`, so a `.env` setting takes effect on the Docker deployment. | | `ATLAS_DRY_MULTIPLIER` | `0.8` | DRY sampling strength. llama-server ships every repetition control off (`repeat_penalty=1.0`, `dry_multiplier=0.0`, `frequency_penalty=0.0`, `presence_penalty=0.0`), so nothing bounded how long a generation could repeat itself — the stream-level repeating-tail cut is a backstop that fires after the loop has begun. DRY is used rather than `repeat_penalty` because it scores repeated *sequences*: `repeat_penalty` scores individual token reoccurrence, which penalizes the indentation, keywords, and closing braces that source code repeats legitimately. `0` disables DRY entirely. | | `ATLAS_DRY_BASE` | `1.75` | DRY penalty base (llama.cpp default). | | `ATLAS_DRY_ALLOWED_LENGTH` | `6` | Longest sequence that may repeat unpenalized. Raised above llama.cpp's default of `2` because 3-token runs are ordinary in source. | | `ATLAS_DRY_PENALTY_LAST_N` | `2048` | DRY lookback window (tokens). Bounded rather than llama.cpp's `-1` (whole context), which would make every earlier turn's text count as a repetition source. | | `ATLAS_REPEAT_PENALTY` | `1.0` (off) | Classic repetition penalty. Off by default: it scores individual tokens and degrades code generation. Available for the pure repeated-whitespace degeneration that DRY's newline sequence-breaker cannot see. Setting any value other than `1.0` also sends `ATLAS_REPEAT_LAST_N`. | | `ATLAS_REPEAT_LAST_N` | `64` | Lookback for `ATLAS_REPEAT_PENALTY`. Only sent when that penalty is enabled. | | `ATLAS_PERMISSION_TIMEOUT_SEC` | `600` | Fail-safe timeout (seconds) on interactive permission requests (`proxy/permissions.go`). If no decision arrives at `POST /v1/permission` and the client neither disconnects nor cancels, the tool call is denied rather than hanging the turn. Compose forwards it from `.env` to the proxy container. | | `ATLAS_ALLOW_CREDENTIAL_READS` | unset | By default the agent refuses to read known sensitive configuration files into model context (`.env`, `.env.*` except `.env.example`, `.netrc`, `.npmrc`, `.pypirc`, `*.pem`, `*.key`, SSH private keys, `.aws/credentials`, `.kube/config`, `.docker/config.json`, `secrets/service-token`, `secrets/api-keys.json`) — their contents would otherwise flow into prompts, logs, and session files. Set `1` to explicitly include them when you know a file is non-sensitive; the refusal message names this override. Compose forwards it from `.env` to the proxy container. | | `ATLAS_GRAMMAR_MODE` | `strict` | Schema-constrained JSON sampling. Default `strict` ships the full tool-call schema in `response_format` so llama-server's C-side sampler converts it to internal GBNF and the token decoder can ONLY emit our `tool_call/text/done` union. Set to `loose` to send a `{"type":"json_object"}` payload instead (valid JSON, no shape enforcement). **`loose` is REQUIRED for Gemma-family models** — strict schema-GBNF makes them spam `done` instead of calling tools. | | `ATLAS_CONTROL_VECTOR` | `/models/ast_edit_steering.gguf` | Path to the ASA control-vector GGUF (the filename is intentionally stable — the registry SHA-pins it and the `.model` marker sits beside it — even though the tool it steers is now called `structural_edit`). The proxy reads this only for the `/v1/calibration/status` presence/marker probe; the vector itself is loaded by the llama-server entrypoint (see § 6 for `ATLAS_CONTROL_VECTOR_SCALE`, `_LAYER_RANGE`, `_ALLOW_UNVERIFIED` — those are entrypoint-consumed, not proxy). Compose forwards all four `ATLAS_CONTROL_VECTOR*` keys from `.env` to the llama-server container, which is what loads the vector; the proxy container keeps its in-code default path for the probe. | | `ATLAS_CALL_GRAPH` | `0` | Structural call-graph reasoning. When enabled (`1`/`true`/`yes`/`on`), the proxy attaches intra-file call edges to `read_file`/`outline_file` output and symbol-index snippets; v3-service reads the same flag for its graph-based veto and repair context. Default off. | | `ATLAS_WORKSPACE_DIR` | (proxy's container `/workspace`) | Working-dir override that the proxy substitutes for the TUI-supplied `working_dir` field. Set inside the container so file tools always resolve under `/workspace` regardless of what the client sends. | | `ATLAS_VERIFY_IN` | `sandbox` | Where `run_command` and the V3 verify path execute: `sandbox` (default) routes through the sandbox container; `host` runs commands directly on the proxy host (only safe when the proxy itself is local, not containerized). Per-project override: `[execution] target = "host"` in `.atlas/config.toml`. | ### Internal Settings (not configurable via env) | Setting | Value | Description | |---------|-------|-------------| | Max turns (T0 Conversational) | 5 | Text-only chat — shape constraint, not runaway protection | | Max turns (T1 / T2 / T3) | `0` (uncapped) | The 8 stuck-pattern detectors (parse-error, tool-repeat, reasoning-repeat, lens-regression, exploration-budget, path-aware error-loop, action-gate, verification-gate) are the safety net. Operator can re-cap any tier with `ATLAS_MAX_TURNS=`. | | Exploration budget warning | 4 consecutive reads | Injects "write your changes now" | | Exploration budget skip | 5+ consecutive reads | Skips the read, returns warning | | Error loop breaker | 3 consecutive failures on the **same path** | Path-aware — same `(tool, path)` 3× breaks the loop; rotating failure paths do not trip it | | T2 trigger (V3 activation) | `lines ≥ 10` AND (`hasLogicIndicators` ≥ 2 family matches OR known code/markup extension) | `classifyFileTier` in `proxy/tools.go`. Config files / data exts / styles / prose / shell scripts always T1; under 10 lines always T1; recognized code/markup extensions auto-T2 even without logic-indicator matches. | | write_file rejection | Existing files > 5 lines | Forces `structural_edit` (whole node, .py/.html/.htm) or `edit_file` (surgical). Skipped when the existing file looks corrupted on disk (self-heal). | | Session file manifest | Fires on the write that takes the session's created-file set to ≥ 2, once per new file | `[system note]` listing every file the session created, so later files reference earlier ones (`render_template`, `