Environment variables

Configuration is environment variables, never code. There is no config file to load, no Ecko.configure(...) call, and no way for a library to change your provider behind your back. A program's behaviour is a function of its source and its environment.

A project can give defaults in the environment block of ecko.json. The shell wins: an entry applies only to a variable you have not set, so what you export is what runs - the same env-over-file rule as std.config. (Before 0.58 the block overrode the shell.)

Three rules hold for every variable on this page:

  • An invalid value is an error, not a default. ECKO_LIMIT_DEPTH=lots stops ecko before the program starts, naming the variable and what it should be.
  • For a timeout, 0 means off. For a safety limit - depth, allocation, unpacked size, connection caps - 0 is refused: off would mean unbounded.
  • Names are ECKO_<AREA>_<SETTING>. The names used before 0.58 are not read since 0.61; one that is still set says which name replaced it - see No longer read.

The global flags --provider, --model, --trace and --cache, written before the file, are one-run overrides of the matching variable. The API key is ECKO_AI_API_KEY only; it has no flag.

AI and LLM

variablemeaning
ECKO_AI_API_KEYProvider API key. Unset means mock mode.
ECKO_AI_PROVIDERopenai (default), openrouter, ollama
ECKO_AI_MODELModel name; each provider has a default
ECKO_AI_BASE_URLOverride the provider endpoint, for a proxy or a compatible server. Must be https, or http to a loopback host, since the API key travels on it; ECKO_NET_ALLOW_HTTP=1 overrides
ECKO_AI_EMBED_MODELEmbedding model; defaults per provider
ECKO_AI_MAX_RETRIESRetries for typed and contract calls. Default 3
ECKO_AI_MAX_CALLSHard cap on paid provider calls - ai and embeddings alike, so rag and db count too. Per run for a script; per request (or WebSocket message) in a server. Unset means unlimited
ECKO_AI_REASONINGHow hard a reasoning model thinks, for every call without its own reasoning:: none, minimal, low, medium, high, xhigh or max. Unset leaves it to the provider. See model()
ECKO_AI_READ_TIMEOUT_MSHow long an AI request may go without receiving anything before it fails (and is retried). A limit on silence, not on the whole request: a long reply that keeps arriving is never cut off. Default 120000; 0 turns it off
ECKO_AI_OLLAMA_FORMAT0 stops sending a typed call's schema to Ollama as format, for an Ollama older than 0.5. Default: sent
ECKO_AI_MOCK1 forces mock mode whatever the keys, provider or a call's via say. ecko test sets it
OPENROUTER_API_KEY, OPENAI_API_KEYThe key for a call that switches to that provider with via. ECKO_AI_API_KEY is never sent to a provider other than ECKO_AI_PROVIDER
ECKO_AI_MAX_TOOL_ROUNDSTool-call rounds per ai ... using. Default 8
ECKO_AI_TOOL_TIMEOUT_MSPer-round tool timeout. Default 30000; 0 disables
ECKO_AI_TOOL_MAX_RESULTMax bytes of one tool result fed back to the model. Default 32768; 0 disables
ECKO_AI_CACHEDirectory for the prompt cache
ECKO_AI_TRACE1 traces to stderr; a path writes JSONL
ECKO_RETRY_BASE_MSBase backoff for retry. Default 50

ECKO_AI_MAX_CALLS is the one to set in anything unattended. It is a hard budget: the call that would exceed it fails rather than spending. In a server it limits each request rather than the server's lifetime - see budgeting.

Concurrency

variablemeaning
ECKO_LIMIT_PARALLELConcurrent workers in pmap and tool rounds. Default: all cores
ECKO_LIMIT_TASKSCap on running async tasks. Default 256
ECKO_LIMIT_QUEUED_TASKSMax tasks waiting for a slot. Default 4096; past it spawning is refused rather than queued

HTTP and WebSocket server

Each of these is also an option to http.serve, named after the variable: http.serve(port, app, max_body: 1048576, max_conns: 256). The option is the program's own setting; the variable, when set, overrides it, so an operator can still change a deployed server without editing it.

variablehttp.serve optionmeaning
ECKO_HTTP_WORKERSworkers:How many http.serve handler invocations run at once. Default 8. Worth tuning: leave it alone for handlers that compute, raise it for handlers that wait on a database, a service or ai - see sizing the pool
ECKO_HTTP_MAX_STREAMSmax_streams:Concurrent streaming/SSE responses. Default 1024; past it a stream is refused with 503
ECKO_HTTP_REQUEST_TIMEOUT_MSrequest_timeout_ms:Per-request handler timeout: past it the client gets 503, whether the handler is computing or waiting. 0 or unset means unlimited
ECKO_HTTP_MAX_BODYmax_body:Max request body in bytes; a larger one is answered 413 rather than truncated. Default 10 MiB
ECKO_HTTP_DRAIN_MSdrain_ms:How long a stopping http.serve (SIGTERM, Ctrl-C or http.stop()) waits for in-flight requests before closing what is left. Default 25000, inside the 30 s a container orchestrator allows before SIGKILL
ECKO_HTTP_READ_TIMEOUT_MSread_timeout_ms:The slow-client deadline: how long a connection may take to send its request headers, and thereafter how long it may go silent mid-body before the server answers 408. Applied per body frame, so a slow but steady upload of any size completes. Default 30000; 0 turns the defence off
ECKO_HTTP_MAX_CONNSmax_conns:Max concurrent HTTP connections. Default 1024. At the limit the connection idle longest (at least 1 s) is closed to make room for a new one; the new one is dropped before anything is read only if none is idle. Each connection is a file descriptor, so raising this past about 1,000 also needs a higher open-files limit (ulimit -n)
ECKO_HTTP_MAX_WS_CONNSmax_ws_conns:Max concurrent WebSocket connections. Default 1024

Network and database

variablemeaning
ECKO_NET_TIMEOUT_MSRead and write deadline on a std.net socket, so a silent peer surfaces an error rather than blocking forever. 0 disables it. Default 30000
ECKO_SQL_BUSY_TIMEOUT_MSHow long a std.sql connection waits for another connection's lock before failing with database is locked. A busy_timeout_ms passed to sql.open overrides it. 0 means do not wait. Default 5000

Runtime limits

variablemeaning
ECKO_LIMIT_DEPTHCall and recursion depth. Default 2000
ECKO_LIMIT_PARSE_DEPTHHow deep an expression tree may nest, by brackets or by chaining (a + b + c, `x> f> g, else if arms). Default 128`
ECKO_LIMIT_STEPSOpt-in loop and step budget. Unset means unlimited
ECKO_LIMIT_ALLOCBytes one value built from a count may hold, and the most one read from a stream may return: a string from *, repeat or the pad family, bytes from random.bytes or io.read_exact, a whole-file fs.read, a buffered HTTP body. Default 256 MiB
ECKO_LIMIT_UNPACKEDTotal bytes an std.archive extraction or a std.zlib decompression may produce. Default 1 GiB

These exist so that adversarial or accidental input degrades into a catchable error instead of exhausting the machine - see Resource limits.

Engine

variablemeaning
ECKO_VM_MODULES0 runs the code of imported modules on the slower tree-walking evaluator, as through 0.73, instead of the bytecode VM. A temporary escape hatch for a program that behaves differently since 0.74; it will be removed. Default on
ECKO_JIT_ENABLE1 compiles functions to machine code before they run (a module's when it is imported). Experimental. Every function that contains a loop compiles, and so does a function without one that calls itself or is called from compiled code, when it can be called directly (no default parameter values, no try, and only operations the compiler covers, so no calls to module functions such as math.sqrt); inside them, whole-number and float arithmetic, comparisons, local variables, field reads, for loops, calls between compiled functions (including a call through a variable or a value to a compiled function that captures no variables), calls to built-in functions and the checks of typed parameters and results run as machine code, and any other operation is handed to the bytecode VM and the function carries on after it. Other functions run on the VM as usual. ecko check --native shows what compiles, and why a function does not. Results, errors and exit codes are identical to the default engine; a difference is a bug, so please report it. Unset or 0 runs everything on the VM. Default off

Python

variablemeaning
ECKO_PY_TIMEOUT_MSHow long one py() call may take before it fails with kind timeout and the worker is restarted. Default 30000; 0 turns it off
ECKO_PY_ENVComma-separated variable names to forward to the Python worker, which otherwise inherits nothing

Logging and terminal

variablemeaning
ECKO_LOGLevel filter for the default std.log sink: debug < info < warn < error. Default info
ECKO_BACKTRACEShow Rust's own panic output when the interpreter hits an internal fault. Off by default: the fault is reported as a message and exit 70 either way, and this adds the raw detail for diagnosing it.
NO_COLORAny value disables terminal colour (the convention)
CLICOLOR_FORCEForce colour on even when stdout is not a TTY

Packages

variablemeaning
ECKO_PKG_MAX_BYTESMax download size for a package. Default 50 MiB
ECKO_PKG_MAX_UNPACKEDMax unpacked size. Default 200 MiB
ECKO_NET_ALLOW_HTTP1 or true permits plain-http package sources from non-loopback hosts
ECKO_PKG_GIT_BASEOverride the forge base URL, for a private mirror
ECKO_PKG_TOKENBearer token attached to package fetches, for a private repository. Sent only to the hosts below
ECKO_PKG_TOKEN_HOSTSComma-separated hosts that may receive the token. Defaults to ECKO_PKG_GIT_BASE's host, or github.com

The two size caps and the https default are there because fetching a package is the moment a program trusts something it did not write. ECKO_NET_ALLOW_HTTP exists for a local registry; needing it against a real host is a warning sign.

Scaffolding

variablemeaning
ECKO_TEMPLATES_REPOWhere ecko scaffold gets templates: an owner/repo, an archive URL, or a local directory read with no network and no cache. Default ecko-lang/templates
ECKO_TEMPLATES_DIRWhere they are cached. Default: the platform cache directory

No longer read

These were renamed in 0.58, and the old name was read, with a warning, through 0.60. From 0.61 it is not read at all: one that is still set prints which name replaced it, and the run goes ahead as if it were unset - so a limit set under an old name is not in force. Change your shell, CI and ecko.json.

oldnew
ECKO_API_KEYECKO_AI_API_KEY
ECKO_TRACEECKO_AI_TRACE
ECKO_MAX_DEPTHECKO_LIMIT_DEPTH
ECKO_MAX_STEPSECKO_LIMIT_STEPS
ECKO_MAX_ALLOCECKO_LIMIT_ALLOC
ECKO_MAX_PARSE_DEPTHECKO_LIMIT_PARSE_DEPTH
ECKO_MAX_TASKSECKO_LIMIT_TASKS
ECKO_MAX_QUEUED_TASKSECKO_LIMIT_QUEUED_TASKS
ECKO_MAX_PARALLELECKO_LIMIT_PARALLEL
ECKO_ARCHIVE_MAX_UNPACKEDECKO_LIMIT_UNPACKED
ECKO_MAX_CONNSECKO_HTTP_MAX_CONNS
ECKO_MAX_STREAMSECKO_HTTP_MAX_STREAMS
ECKO_MAX_WS_CONNSECKO_HTTP_MAX_WS_CONNS
ECKO_ALLOW_HTTPECKO_NET_ALLOW_HTTP
ECKO_NET_TOKENECKO_PKG_TOKEN
ECKO_NET_TOKEN_HOSTSECKO_PKG_TOKEN_HOSTS
ECKO_GIT_BASEECKO_PKG_GIT_BASE

Removed in 0.58: ECKO_LLM_API_KEY (a second spelling of the key - set ECKO_AI_API_KEY) and ECKO_FUZZ_TRACE (internal). If either is still set, ecko says so rather than ignoring it silently.

Secrets

Nothing here should hold a secret except ECKO_AI_API_KEY and ECKO_PKG_TOKEN, and neither belongs in a committed file. Put them in your shell, your CI secret store, or a secret manager. Inside a program, wrap sensitive values in secret so they cannot be printed or logged by accident, and reach for std.config when you want layered configuration with declared secret fields.