Configuration reference

Everything the Spec-Driven Agent reads from the environment. All of it is optional — the agent runs without any of it, minus the keyless tiers. These variables are read by the web editor backend and by besser/spec_driven_agent/; the live values of the caps and feature flags are reported by GET /besser_api/spec-driven/config, which is what a client should read rather than hardcoding the figures below.

Keyless tiers

The free tier is what makes the agent usable without an API key. It is off unless configured, in which case the editor offers it as the default and the config endpoint reports free_tier.available. See Providers and models.

  • BESSER_FREE_LLM_BASE_URL / BESSER_FREE_LLM_MODEL / BESSER_FREE_LLM_TOKEN – The keyless free tier’s primary endpoint. Both the URL and the model must be set for the tier to be offered.

  • BESSER_FREE_LLM_ALT_MODELS – Comma-separated extra model ids served by that same endpoint and token. They are offered as alternative free choices and used as the first step of the fallback chain.

  • BESSER_FREE_LLM_FALLBACK_BASE_URL / BESSER_FREE_LLM_FALLBACK_MODEL / BESSER_FREE_LLM_FALLBACK_TOKEN – A second, independently credentialed endpoint used as the last resort in the fallback chain.

  • BESSER_SPONSORED_LLM_BASE_URL / BESSER_SPONSORED_LLM_TOKEN / BESSER_SPONSORED_LLM_MODEL – An additional server-funded keyless tier, served through an OpenAI-compatible endpoint. The URL and token are required; the model is the default, and a request may name another model from that endpoint’s catalog in llm_model. With neither a default nor a request model the run is refused.

  • BESSER_DEMO_TOKEN – Shared secret authorising the sponsored tier. A request for that tier must carry the same value in demo_token or the server answers 403; the web editor sends it for tabs opened through a ?demo=<token> link. The check fails closed: leaving this unset refuses every sponsored run, so the tier cannot be left open by a half-finished configuration. Because the tier spends the deployment’s own credits, pair it with a hard spend cap on the provider account — that limit holds even if the link is shared further than intended.

  • BESSER_PILOT_LLM_MODEL – Keyless model that sessions opened with a research-study link start on. It must be one of the free-tier models the server offers, or it is ignored; unset, those sessions get the ordinary default. The study-mode variables (BESSER_TELEMETRY_ENABLED, BESSER_TELEMETRY_DIR, BESSER_TELEMETRY_ADMIN_TOKEN) are listed in Web Editor Backend API.

  • BESSER_LLM_PLANNING_MODEL – Override the small model used for the gap analysis call. Set it to primary to plan on the main model instead — necessary behind a gateway that does not serve the cheap sibling.

  • BESSER_LLM_ALLOW_CUSTOM_BASE_URL (off) – Permit a request to carry its own OpenAI-compatible base_url. Having the server open a user-supplied URL is an SSRF surface, so this is meant for local or single-tenant deployments. The local docker-compose.yml turns it on so the editor’s PIA (LIST’s private AI gateway, an OpenAI-compatible endpoint backed by AWS Bedrock) and Local (e.g. Ollama) providers work; it stays off in code and in docker-compose.prod.yml. For a backend started without Docker, set BESSER_LLM_ALLOW_CUSTOM_BASE_URL=true yourself.

Caps and limits

See Runs, budgets and modify mode for how the caps are enforced.

  • BESSER_LLM_MAX_COST_USD_HARD_CAP (5.0), BESSER_LLM_MAX_RUNTIME_SECONDS_HARD_CAP (2400), BESSER_LLM_MAX_TURNS_HARD_CAP (150) – Server-side ceilings a client request can never exceed.

  • BESSER_LLM_DEFAULT_MAX_COST_USD (5.0), BESSER_LLM_DEFAULT_MAX_RUNTIME_SECONDS (2400), BESSER_LLM_DEFAULT_MAX_TURNS (120) – Defaults when the request omits a cap. Each is clamped to its hard cap.

  • BESSER_LLM_MAX_CONCURRENT_RUNS (10) – Runs in flight before new requests get a 429. Starting or resuming a run that is already in flight answers 409.

  • BESSER_LLM_CALL_TIMEOUT_SECONDS (300) – Per-request SDK timeout, so a stalled provider call cannot hang a turn indefinitely.

  • BESSER_LLM_WATCHDOG_GRACE_SECONDS (120) – Extra time allowed on top of the request’s max_runtime_seconds before the runner-level watchdog force-cancels a run. It covers Phase 3 and packaging, which happen after the Phase 2 loop has already hit its own runtime check.

Feature flags

  • BESSER_LLM_ENABLE_SHELL_TOOLS (off) – Give the LLM run_command / install_dependencies. Arbitrary shell next to server secrets is an RCE and secret-exfiltration surface, so this stays off on any service that holds them. In production only the isolated besser-wme-smartgen worker sets it, in its own environment: block, never through the shared .env (see Production deployment); a local or on-prem install – one machine, one tenant – is exactly where to turn it on, and it is what lets the agent run its own tests and builds. It also gates Phase 3’s pip install --dry-run dependency check. There is no request field for it: it is read from the environment at start-up, and the config endpoint reports the resulting value as features.shell_tools_enabled so a deploy can be verified from outside the process. Read Shell tools before enabling it. On Linux every command runs in the run’s bubblewrap sandbox; on Windows and macOS, or with BESSER_LLM_SHELL_SANDBOX=off, commands run unconfined as the backend user.

  • BESSER_LLM_SHELL_SANDBOX (auto) – auto confines shell commands and the checks that execute generated code wherever the platform provides bubblewrap; when the sandbox cannot start, commands are refused and checks are reported as unverified. off runs them unconfined; use it only on a single-tenant Linux host whose kernel forbids unprivileged user namespaces.

  • BESSER_LLM_SHELL_RUNBOOK (on) – With shell tools enabled, add the boot-and-verify procedure and its .besser_probe.py helper to the prompt. 0 keeps the shell without the procedure.

  • BESSER_LLM_SHELL_SESSION_IDLE_SECONDS (900) – Close a run’s shell session after this many seconds without a command; its processes stop and the next command starts a new session. 0 never closes it early; it still ends with the run.

  • BESSER_LLM_SHELL_SESSION_MAX (10) – Live shell sessions per worker process. Past the cap a command runs in a one-off sandbox, so what it starts in the background stops when it returns; 0 gives every command its own sandbox. See Shell tools.

  • BESSER_LLM_SHELL_NETWORK (auto) – The network of a run’s shell session. private: a network namespace per session through pasta (passt), with outbound access only, so localhost is the run’s own and the worker’s ports and other runs are unreachable; when pasta or /dev/net/tun is missing, every command is refused. shared: the worker’s own network namespace. auto: private when a start-up selftest passes, otherwise shared with one logged warning. docker-compose.prod.yml sets private (see Production deployment).

  • BESSER_LLM_ENABLE_TOOLCHAIN_VALIDATION (off) – Run tsc / cargo / kotlinc in Phase 3. Costly on non-Python stacks. The cheap in-process checks (syntax, Dockerfile references, contracts, ruff) run regardless; the pip install --dry-run dependency check is gated on BESSER_LLM_ENABLE_SHELL_TOOLS instead. See Validation and auto-fix.

  • BESSER_LLM_ENABLE_AUTO_FIX (on) – Let Phase 3 spend LLM turns repairing blocker-severity findings.

  • BESSER_LLM_ENABLE_IMPORT_SMOKE_CHECK (on) – Let Phase 3 execute the generated backend: import the ORM module in a subprocess and run SQLAlchemy’s configure_mappers(), boot the app in isolation for the create-request probes, and replay test_api workflows. This is the only check that catches a relationship() pointing at nothing, which passes every static check and then fails every database request. The generated code runs in the bubblewrap sandbox with no network, a stripped environment and a timeout. On Linux, when the sandbox cannot start, the check is skipped and reported as unverified, unless BESSER_LLM_SHELL_SANDBOX=off; on Windows and macOS, which have no such sandbox, it runs unsandboxed and a warning is logged. It is kept separate from BESSER_LLM_ENABLE_SHELL_TOOLS so hosted deployments keep it. Set it to 0 (or false) to turn it off; a generated FastAPI backend is then reported as runtime unverified rather than as passing. See Validation and auto-fix.

  • BESSER_LLM_ENABLE_REQUIREMENTS_LEDGER (on) – Turn the user’s verbatim request into a numbered list of atomic requirements once per run (only behaviour the request states; a vague request yields few or none), show that list to Phase 2, and have every Phase 3 pass judge each requirement against the generated code. A requirement the code does not implement becomes a requirement: blocker for the auto-fix loop, and every verdict is written to the recipe. It costs extra calls to the planning model: one to extract the list, and one for each Phase 3 pass. Set it to 0 (or false) to turn it off; coverage of the request is then reported as validation unverified instead of being checked.

  • BESSER_LLM_ENABLE_TRACING (on) – Append the run’s phases, turns, tool calls and findings to .besser_trace.jsonl in the workspace.

  • BESSER_LLM_ENABLE_CHECKPOINTING (on, required for resume) – Write .besser_checkpoint.json after every tool-use turn.

  • BESSER_LLM_PER_WRITE_DIAGNOSTICS (on) – Parse every file the LLM writes immediately and feed findings back in the same tool result.

  • BESSER_LLM_INLINE_SCAFFOLD (off; set to 1 to enable) – Inline the small Phase-1 scaffold files into the Phase-2 prompt to save the first read_file turns. Off by default: the inlined copy never changes during the run, so once the model has edited a file the copy is stale and quoting from it makes modify_file miss. With it off, file text reaches the model only through read_file (always current; several reads batch in one turn).

  • BESSER_LLM_ROLLING_CACHE (on; 0 disables it) – Add a rolling prompt-cache breakpoint on the growing conversation, so the prior prefix is served from cache instead of re-billed each turn. Anthropic path only — the OpenAI-compatible path caches by prefix automatically.

Context and token budgets

  • BESSER_LLM_COMPACT_THRESHOLD (80000) – Estimated-token threshold at which the Phase 2 conversation is compacted, for models whose context window is unknown. Models with a known window (from the model catalogs of the free and sponsored tiers, or the built-in table of hosted frontier models) get an adaptive threshold derived from that window instead. Setting this variable also caps the adaptive value, so lowering it to cut cost applies to every model, not only unknown ones. Floored at 8,000.

  • BESSER_LLM_MAX_COMPACT_THRESHOLD (200000) – Ceiling on the adaptive threshold for models with a known window, so a million-token model does not send prompts of that size every turn. Models measured to serve a smaller window than they advertise are clamped below both values regardless.

  • BESSER_LLM_HISTORY_EVICTION (off) – Opt into the lossless alternative to compaction: large file bodies in older messages are replaced by stubs pointing back to disk, instead of whole messages being summarised.

  • BESSER_LLM_FROM_SCRATCH_MAX_TOKENS (32768) – Output-token ceiling for from-scratch generation (no Phase-1 scaffold to build on). Raising it costs nothing unless the extra output is actually produced; the truncation guard in How it works is what keeps a cut-off turn from landing half-written.

  • BESSER_LLM_MODIFY_MAX_TOKENS (defaults to the from-scratch value) – The same ceiling for modify and fix runs, which routinely rewrite a whole existing file in one turn.

Storage and lifecycle

  • BESSER_LLM_RUN_STORE_PATH – Path of the SQLite durable-run store (default: besser_spec_driven_runs.sqlite3 in the system temp directory). Point it at a persistent volume so runs survive a container restart.

  • BESSER_LLM_RUN_WORKSPACE_ROOT – Persistent parent directory for run workspaces and checkpoints (default: the OS temp directory).

  • BESSER_LLM_DOWNLOAD_TTL_SECONDS (1800) – How long an output stays downloadable.

  • BESSER_LLM_CANCEL_ABANDONED_RUNS (on) and BESSER_LLM_DISCONNECTED_GRACE_SECONDS (300) – How long a run with no subscribers keeps going before it is cooperatively cancelled through the checkpoint-aware path.

  • BESSER_LLM_COST_EMITTER_INTERVAL_SECONDS (2.0) – Cadence of the cost SSE tick.

  • BESSER_INCIDENT_LOG_DIR (/app/incidents) – Where provider-incident records (a failed endpoint, a fallback-chain switch) are written. When it is set explicitly, every run’s trace is also copied to its traces/ folder; when it is unset, only the traces of research-study runs are kept, in BESSER_TELEMETRY_DIR. Study mode is described alongside the telemetry endpoints in Web Editor Backend API.

Note

A run driven from Python rather than from the backend does not read most of these: LLMOrchestrator takes the flags as constructor arguments, and its own defaults differ from the web backend’s. See Using the agent.