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 inllm_model. With neither a default nor a request model the run is refused.BESSER_DEMO_TOKEN– Shared secret authorising thesponsoredtier. A request for that tier must carry the same value indemo_tokenor 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 toprimaryto 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-compatiblebase_url. Having the server open a user-supplied URL is an SSRF surface, so this is meant for local or single-tenant deployments. The localdocker-compose.ymlturns 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 indocker-compose.prod.yml. For a backend started without Docker, setBESSER_LLM_ALLOW_CUSTOM_BASE_URL=trueyourself.
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 a429. Starting or resuming a run that is already in flight answers409.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’smax_runtime_secondsbefore 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 LLMrun_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 isolatedbesser-wme-smartgenworker sets it, in its ownenvironment: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’spip install --dry-rundependency 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 asfeatures.shell_tools_enabledso 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 withBESSER_LLM_SHELL_SANDBOX=off, commands run unconfined as the backend user.BESSER_LLM_SHELL_SANDBOX(auto) –autoconfines 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.offruns 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.pyhelper to the prompt.0keeps 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.0never 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;0gives 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 throughpasta(passt), with outbound access only, solocalhostis the run’s own and the worker’s ports and other runs are unreachable; whenpastaor/dev/net/tunis missing, every command is refused.shared: the worker’s own network namespace.auto:privatewhen a start-up selftest passes, otherwisesharedwith one logged warning.docker-compose.prod.ymlsetsprivate(see Production deployment).BESSER_LLM_ENABLE_TOOLCHAIN_VALIDATION(off) – Runtsc/cargo/kotlincin Phase 3. Costly on non-Python stacks. The cheap in-process checks (syntax, Dockerfile references, contracts,ruff) run regardless; thepip install --dry-rundependency check is gated onBESSER_LLM_ENABLE_SHELL_TOOLSinstead. 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’sconfigure_mappers(), boot the app in isolation for the create-request probes, and replaytest_apiworkflows. This is the only check that catches arelationship()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, unlessBESSER_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 fromBESSER_LLM_ENABLE_SHELL_TOOLSso hosted deployments keep it. Set it to0(orfalse) to turn it off; a generated FastAPI backend is then reported asruntime unverifiedrather 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 arequirement: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 to0(orfalse) to turn it off; coverage of the request is then reported asvalidation unverifiedinstead of being checked.BESSER_LLM_ENABLE_TRACING(on) – Append the run’s phases, turns, tool calls and findings to.besser_trace.jsonlin the workspace.BESSER_LLM_ENABLE_CHECKPOINTING(on, required for resume) – Write.besser_checkpoint.jsonafter 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 to1to enable) – Inline the small Phase-1 scaffold files into the Phase-2 prompt to save the firstread_fileturns. 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 makesmodify_filemiss. With it off, file text reaches the model only throughread_file(always current; several reads batch in one turn).BESSER_LLM_ROLLING_CACHE(on;0disables 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.sqlite3in 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) andBESSER_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 thecostSSE 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 itstraces/folder; when it is unset, only the traces of research-study runs are kept, inBESSER_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.