Skip to content

Configuration

Atomic Agent reads its settings from three places: a config file you can edit by hand, a set of environment variables for deployment and tuning, and a secrets file that keeps API keys and tokens out of your config. This page explains what lives where, and which one wins when they disagree.

If you just want the agent to run, you don’t need to touch any of this — Atomic Agent writes a sensible config.json on first launch. Come back here when you want to point at a different model, enable a feature, or wire up a secret.

The three sources at a glance

config.json

User-facing settings: model, memory, skills, MCP servers, Telegram. Versioned and auto-migrated. Lives at <stateDir>/config.json. Edit by hand or with atomic-agent config set <key> <value>.

ATOMIC_AGENT_* env vars

Deployment and operational settings: paths, timeouts, retries, loop thresholds, the task queue, and all browser settings. Read at bootstrap. Ideal for CI and containers.

.env secrets

API keys and tokens. Lives at <stateDir>/.env, mode 0600, never written into config.json, scrubbed from logs.

Where everything lives

Everything sits under one state directory. By default that is ~/.atomic-agent; override it with ATOMIC_AGENT_STATE_DIR.

<stateDir>/ # default: ~/.atomic-agent
├── config.json # user-facing settings (versioned schema)
├── .env # secrets: API keys, tokens (chmod 0600)
├── sessions.sqlite # conversation transcripts
├── memory.sqlite # profile facts, notes, lessons, procedures
├── tasks.sqlite # durable task queue
├── traces/ # NDJSON trace files (one per session)
├── skills/ # globally installed skills
└── models/ # downloaded GGUF models (managed mode)

The config file is <stateDir>/config.json; atomic-agent config path prints the exact location. The config command reads and edits it one key at a time or as a whole document:

Terminal window
atomic-agent config get # whole file as JSON
atomic-agent config get <key> # one value, e.g. log.level
atomic-agent config set <key> <value> # one value, rest of the file untouched
atomic-agent config set '<json>' # replace the whole file (missing keys get defaults)
atomic-agent config unset <key> # restore one key to its default
atomic-agent config list # every key as key = value
atomic-agent config path # where config.json lives

Keys are dotted paths. Values are typed by the schema (false, 40 and info become a boolean, a number and a string) and validated before anything is written, so a typo or an out-of-range value is refused rather than saved. config list marks every value that differs from its default.

Terminal window
atomic-agent config set log.level debug
atomic-agent config set analytics.enabled false
atomic-agent config unset log.level # back to "info"

List-valued keys such as projects.roots have no single-value form. Set them with the whole-file JSON form (below) or by editing the file; unset works on them.

How the three sources combine

At bootstrap, loadConfig() reads .env, then config.json, then the process environment, merges them under fixed precedence rules, validates the result, and caches one immutable config object for the whole runtime lifetime.

flowchart TD
    A["createAgentRuntime → getConfig()"] --> B{cached?}
    B -->|yes| Z["return cached config"]
    B -->|no| C["resolve stateDir<br/>(ATOMIC_AGENT_STATE_DIR or ~/.atomic-agent)"]
    C --> D["load <stateDir>/.env<br/>into process.env<br/>(skips already-set keys)"]
    D --> E["read <stateDir>/config.json<br/>migrate if old version"]
    E --> F["merge: file wins for user keys,<br/>env wins for operational keys"]
    F --> G["resolve ~ paths, asset dirs,<br/>LLM provider API keys"]
    G --> H["validate (ConfigValidationError on bad input)"]
    H --> I["freeze + cache"]
    I --> Z

Precedence rules

The merge is not a single “env always wins” or “file always wins” rule — it splits by what kind of setting it is.

config.json

config.json is a versioned JSON document. When Atomic Agent starts:

  • Missing file → it writes defaults and warns on stderr.
  • Older schema version → it parses your file, fills any new fields from defaults, rewrites the file atomically, and warns that it migrated.
  • Current version → it parses and uses it as-is.

Key blocks

These are the keys you will edit most often. Defaults shown are the shipped defaults; atomic-agent config list prints every key with its current value.

KeyTypeDefaultWhat it does
versionint72Schema version (managed by migration; don’t hand-edit).
localModels.urlstringhttp://127.0.0.1:8080HTTP endpoint for the local llama.cpp server in external mode. In managed mode the URL is http://127.0.0.1:<localModels.managed.port>.
localModels.modemanaged | externalexternalexternal expects a llama-server you run yourself; managed runs the daemon for you. atomic-agent models use <id> flips this to managed.
localModels.managed.modelIdstring | nullnullActive model in managed mode. Nothing is active until you pull a model — qwen-3.5-4b is only what the setup wizard suggests.
localModels.managed.portint19091Port for the managed chat daemon. The embedding daemon uses 19092.
localModels.completionMaxTokensint16384Max new tokens per completion. 0 means no client-side cap. ATOMIC_AGENT_LLAMA_MAX_TOKENS overrides it per process.
localModels.thinkingauto | on | offautoThe chat template’s thinking switch for local models whose template reads it. auto keeps the template’s own default.
localModels.reasoningBudgetTokensint1500How many tokens a local reasoning model may spend thinking before a tool call. 0 leaves it unbounded.
log.leveldebug|info|warn|errorinfoLog verbosity.
agent.tokenBudgetint3000Max prompt tokens budgeted per step.
agent.maxStepsint25Steps per leg. After a leg the agent reports progress and continues, while agent.task.autoContinue is on.
agent.task.maxStepsint1000Hard step ceiling for one task. --max-steps on run / tui overrides it.
agent.task.maxDurationMsint7200000Wall-clock ceiling for one task (2 hours).
agent.task.autoContinuebooleantrueContinue into the next leg automatically instead of stopping after agent.maxSteps.
agent.approvalLevelint 1–51How much the agent may do without asking. See the ladder below.
agent.readScopeworking-dir | unrestrictedworking-dirWhere file reads may go without asking. Reads outside the working directory need approval (or level 5).
agent.providerWait.enabled / .maxWaitMsboolean / inttrue / 300000Wait for a provider that is temporarily unavailable, up to 5 minutes.
agent.conversationMaxTokensint0 (auto)Conversation-history token cap. 0 sizes it from the model’s context window (32000 when the window is unknown).
agent.worldSnapshotMaxTokensint8000Cap for the browser ARIA snapshot section.
tools.shell.defaultTimeoutMsint600000How long a shell command may run (10 minutes) when the model set no timeout. At this limit the command is moved to a background job rather than killed.
tools.shell.jobMaxMsint3600000Hard limit for a background shell job (1 hour).
tools.shell.maxJobsint3Concurrent background shell jobs.
http.enabledbooleantrueLet the agent make outbound HTTP requests.
http.approvalModenever | writes | alwaysneverExtra approval mode for HTTP on top of the ladder. never means the ladder alone decides.
http.hostAllowliststring[] | nullnullWhen set, the HTTP request tool only reaches these hosts. null means no host restriction.
web.search.enabledbooleantrueEnable the web search tool.
web.search.providerstringexaSearch backend. exa works without a key through its keyless endpoint; set EXA_API_KEY for the full API. DuckDuckGo is the fallback if a search fails.
git.remoteSyncbooleanfalseAllow network git (push, pull, fetch, clone, adding a remote). While false, those are refused before any approval prompt. When true, each one still goes through the approval ladder.
projects.rootsstring[][]Directories whose direct children are your projects. See below.
vision.enabledbooleantrueRegister the vision.describe tool (also requires a vision-capable model).
analytics.enabledbooleantrueAnonymous usage stats. See below.
skills.disabledstring[][]Skill names to hide from the registry.
skills.tapsstring[]3 reposGitHub repositories skill browse / skill search read from.
skills.clawhub.enabledbooleantrueUse the ClawHub skill registry.
skills.clawhub.apiBasestringhttps://clawhub.aiClawHub endpoint.
skills.catalogTokenBudgetint512Token budget for the skill list in the prompt. Skills past it are left out. ATOMIC_AGENT_SKILLS_CATALOG_BUDGET overrides it per process.
tracing.trace.enabledboolean | nullnullWrite NDJSON traces per session. null defers to the entry point (run, tui and serve trace; task and the sidecar do not). See Traces and replay.
tracing.trace.maxBytesPerSessionint10485760Size cap for one session’s trace (10 MiB). Past it, the oldest events are dropped.
tui.themestring | autoautoTUI colour theme.
tui.whileBusySubmitsteer | queuesteerWhat Enter does while a turn is running: fold the message into that turn, or queue it as the next turn.
tui.mousebooleantrueTerminal mouse support in the TUI. tui --no-mouse turns it off for one run.

The approval ladder

agent.approvalLevel replaced the old boolean agent.approvalRequired in schema v37. It runs from 1 to 5 and is cumulative: each level stops asking about everything the level below it stopped asking about.

LevelNameStops asking about
1paranoidNothing — every gated action asks first. This is the default.
2workspaceFile writes, edits, and patches strictly inside the session working directory.
3homeAdds file writes anywhere under your home directory, moves to Trash, archive extraction, and HTTP requests.
4operatorAdds guarded shell commands, skill scripts, process kills, network git (push, pull, fetch, clone, adding a remote), publishing (PRs, issues), and Fusion fan-out.
5full trustEverything, including browser navigation to non-web URLs, writes to the agent’s own trust config, reads outside the working directory, and sending email.

--no-approval forces level 5 for a single process. The flag is one-directional — it can only lower strictness for one run, never raise it. Hardline shell-guard rules sit outside the ladder and block at every level.

Analytics

analytics.enabled defaults to true. Analytics are anonymous usage events: install and app open, onboarding steps, the provider and model you configure and use, and per-message shape (duration, step count, outcome, token counts, estimated cost). Each event is tagged with your OS, the app version, and a random install id generated on your machine. Message content, file paths and tool arguments are not included, and the IP address is replaced with 0.0.0.0 so it is not stored. The same flag also governs crash reporting. Crash reports are built from an allowlist of fields: the error type and category, error codes and HTTP status, the tool name or host involved, and stack frames reduced to file basenames; an error message is included only when it is one of a fixed set of static messages. Turning the flag off disables both.

Turning it off is one command:

Terminal window
atomic-agent config set analytics.enabled false

which sets { "analytics": { "enabled": false } } and leaves the rest of the file alone. You can also toggle it from the TUI’s Privacy tab (/privacy), which persists the same key.

Project roots

projects.roots lists directories whose direct children are your projects. It is what lets the os.fs.locate_project tool turn a fuzzy name like “my raylib thing” into a real path, so you can refer to a project by name instead of typing its full path.

{ "projects": { "roots": ["~/code", "~/work/clients"] } }

Entries may be absolute or start with ~. Relative paths are rejected. Because this key is a list, config set projects.roots … does not accept it; edit config.json directly, or use the whole-file config set '<json>' form starting from your current document.

The default is [], and that default is deliberate: nothing is scanned unless you declare it. With no roots, the tool falls back to the session working directory and its ancestors plus the working directories of recent sessions.

Memory fabric tuning (memory.*)

The memory subsystem is tuned under config.memory.*. Most memory features are on by default. Off by default: memory.embeddings.enabled (needs the embedding daemon), memory.reflection.typedNotes.enabled, memory.reflection.segmentation.enabled, and memory.reflection.anySpeaker.

KeyDefaultNotes
memory.profile.enabledtrueProfile facts and their tools.
memory.profile.maxTokens512Cap for the ### profile prompt section.
memory.notes.enabledtrueFreeform searchable notes.
memory.notes.maxEntries1000Hard row cap; FIFO eviction on overflow.
memory.dedup.enabledtruePhase 1A dedup on note write.
memory.embeddings.enabledfalseHybrid BM25 + cosine recall (needs embedding daemon).
memory.links.enabledtrueLink-graph BFS expansion.
memory.lessons.enabledtrueDistilled lessons (changes the stable prefix).
memory.procedures.enabledtrueAdvisory how-to procedures (changes the stable prefix).
memory.voting.enabledtrueVote curation of memory items.
memory.consolidation.enabledtrueCold-path clustering/distillation.
memory.reflection.enabledtrueAsync end-of-turn memory formation.
LLM provider registry (llm.*) and MCP servers (mcp.servers[])

Multi-provider LLM — when an llm block is present, the runtime switches from single-llama to a provider registry:

KeyWhat it does
llm.activeTextProviderSelected text provider id.
llm.activeEmbeddingProviderSelected embedding provider id.
llm.toolTransportauto | grammar | native_tools.
llm.providers[]Provider entries (id, kind, optional apiKey).
llm.fallbackCross-provider fallover chain used when the active provider fails.
llm.runModeRun mode: local, cloud, or fusion, plus Fusion settings.

MCP servers — external tool servers live under mcp.servers[]:

FieldWhat it does
nameUnique kebab-case namespace (max 32 chars, no dots).
enabledConnect at bootstrap.
transport{ kind: 'stdio' }, { kind: 'streamable_http' }, or { kind: 'sse' }.
trustapproval_gated (default, fail-closed) or pure_read (batches with other reads).
envPer-server env overrides for stdio transport.

Discovered tools register as mcp.<server>.<rawName>. Trust defaults to approval_gated whenever unspecified.

Environment variables

ATOMIC_AGENT_* variables handle deployment, paths, and operational tuning. They are read at bootstrap; some (like max tokens) can be overridden per-process without touching the file.

Core and paths

VariableDefaultWhat it does
ATOMIC_AGENT_STATE_DIR~/.atomic-agentRoot for config, secrets, DBs, traces, skills, models.
ATOMIC_AGENT_GRAMMARS_DIRbundledOverride the GBNF grammar asset directory.
ATOMIC_AGENT_TOOL_CALL_GRAMMARbundledPath to the tool-call.gbnf grammar file (not the directory). Takes priority over every other lookup.
ATOMIC_AGENT_RG_PATHbundledOverride the ripgrep binary path.
ATOMIC_AGENT_STABLE_PREFIX_SALTatomic-agent-v1Salt mixed into the prompt-prefix hash that maps a session to a llama-server KV-cache slot. Change it to force cache invalidation.

llama-server / inference

VariableDefaultNotes
ATOMIC_AGENT_LLAMA_API_KEYunsetBearer token for a protected llama-server (optional).
ATOMIC_AGENT_LLAMA_MAX_TOKENSlocalModels.completionMaxTokens (16384)Overrides the config key for this process, clamped 64 to 131072. It cannot express 0 (no cap).
ATOMIC_AGENT_LLAMA_HEALTH_TIMEOUT_MS3000Health-probe timeout.
ATOMIC_AGENT_LLAMA_HEALTH_RETRIES5Health-probe attempts.
ATOMIC_AGENT_LLAMA_HEALTH_BACKOFF_MS500Backoff between health probes.
ATOMIC_AGENT_LLAMA_REQUEST_TIMEOUT_MS300000Per-request idle timeout (5 minutes).
ATOMIC_AGENT_LLAMA_FIRST_TOKEN_TIMEOUT_MS1800000How long a local stream may wait for its first token (30 minutes). Raise it on very slow hardware.
ATOMIC_AGENT_LLAMA_STREAM_TOTAL_TIMEOUT_MS21600000Backstop on one streaming response (6 hours).
ATOMIC_AGENT_LLAMA_COMPLETION_RETRIES3Retry count on completion failure.
ATOMIC_AGENT_LLAMA_COMPLETION_RETRY_BACKOFF_MS150Backoff between retries.
ATOMIC_AGENT_LLAMA_DEFAULT_SLOT0Default llama-server KV-cache slot.
ATOMIC_AGENT_DOWNLOAD_CONNECTIONSlocalModels.download.connections (16)Parallel connections per model download. Lower it on a metered or flaky link.
ATOMIC_AGENT_LLAMA_TEMPERATURE / _TOP_P / _TOP_K / _SEEDunsetSampling overrides (parsed at module load, not per-request).

Browser

Browser settings live only here; there is no browser block in config.json. Boolean variables count 1, true, yes and on as true and any other value as false.

VariableDefaultNotes
ATOMIC_AGENT_BROWSER_ENABLEDtrueSet 0 to disable the browser tools.
ATOMIC_AGENT_BROWSER_CHANNELchromechrome | msedge | chromium.
ATOMIC_AGENT_BROWSER_HEADLESSfalse1 for headless.
ATOMIC_AGENT_BROWSER_EXECUTABLE_PATHauto-detectExplicit Chromium binary.
ATOMIC_AGENT_BROWSER_NO_SANDBOXfalse1 to pass --no-sandbox (containers/CI only).
ATOMIC_AGENT_BROWSER_CDP_URLunsetAttach to an existing browser over CDP.
ATOMIC_AGENT_BROWSER_LAUNCH_TIMEOUT_MS30000Launch timeout.

Agent loop tuning

VariableDefaultNotes
ATOMIC_AGENT_MAX_PARALLEL_TOOL_CALLS8Batch fan-out cap (1 to 16).
ATOMIC_AGENT_LOADED_TOOLS_CAP8Loaded-tool cap (1 to 64).
ATOMIC_AGENT_LOADED_TOOLS_MAX_TOKENS600Token ceiling for the ### loaded-tools prompt section. A safety cap, not a routine truncation point.
ATOMIC_AGENT_AUTO_EXPAND_RARE_ON_ERRORtrueAuto-load a rare tool’s schema on error.
ATOMIC_AGENT_BATCH_TOOL_RESULT_CHAR_CAP32000Combined character budget for all tool-result summaries in one batched step. Over budget, the oldest results in the batch get an extra truncation pass.
ATOMIC_AGENT_SHELL_TOOL_RESULT_CHAR_CAP16000Character cap on one shell command’s result summary.
ATOMIC_AGENT_SHELL_TOOL_RESULT_TAIL_LINES500Lines kept from the end of a shell command’s output.
ATOMIC_AGENT_SKILLS_CATALOG_BUDGETskills.catalogTokenBudget (512)Overrides the config key for this process.
ATOMIC_AGENT_LOOP_WARNING_THRESHOLD3Args-only repeat warn threshold.
ATOMIC_AGENT_LOOP_CRITICAL_THRESHOLD5No-progress streak veto threshold.
ATOMIC_AGENT_LOOP_BREAKER_VETO_STREAK3Consecutive vetoes before a forced graceful reply.
ATOMIC_AGENT_LOOP_HISTORY_SIZE30Size of the loop tracker’s history window.
ATOMIC_AGENT_LOOP_WANDERING_THRESHOLD / _ESCALATION6 / 12Distinct-args spread for wandering tools: redirect, then escalate to a graceful reply.

Tasks

The durable task queue has no config.json block. These variables are the only way to configure it.

VariableDefaultNotes
ATOMIC_AGENT_TASKS_ENABLEDtrueMaster task-queue switch.
ATOMIC_AGENT_TASKS_MAX_ATTEMPTS3Default retry budget per task.
ATOMIC_AGENT_TASKS_RUN_ON_CREATEtrueRun an unscheduled task immediately when it is created.
ATOMIC_AGENT_TASKS_SCHEDULER_ENABLEDtrueBackground ticker switch.
ATOMIC_AGENT_TASKS_SCHEDULER_TICK_MS5000Scheduler poll interval.
ATOMIC_AGENT_TASKS_SCHEDULER_BATCH10Max tasks drained per tick.
ATOMIC_AGENT_TASKS_MIN_INTERVAL_MS1000Floor on --every intervals.
ATOMIC_AGENT_TASKS_BACKOFF_INITIAL_MS / _BACKOFF_MAX_MS1000 / 60000Retry backoff bounds.
ATOMIC_AGENT_TASKS_STALE_AFTER_MS300000When a running task is considered stale (5 minutes).
ATOMIC_AGENT_TASKS_AGENT_TOOLS_ENABLEDtrueExpose the task tools to the agent itself.

HTTP and updates

VariableDefaultNotes
ATOMIC_AGENT_API_KEY—Bearer token for atomic-agent serve (fallback when --api-key is omitted).
ATOMIC_AGENT_UPDATE_CHECK_ON_STARTUPtrueCheck for a newer release at startup.
ATOMIC_AGENT_REPOAtomicBot-ai/atomic-agentOverride the GitHub repository the update check and atomic-agent update query.
ATOMIC_AGENT_DEBUG_ARGVunset1 logs process.argv to stderr.

Secrets: the .env file

Secrets never belong in config.json. They live in <stateDir>/.env as plain KEY=VALUE lines, with the file mode set to 0600.

<stateDir>/.env
TELEGRAM_BOT_TOKEN=123456:ABC-your-bot-token
OPENROUTER_API_KEY=sk-or-...
OPENAI_API_KEY=sk-...

At bootstrap, .env is parsed and merged into process.env — but only for keys not already set in the shell (see precedence above). Recognised secret keys include TELEGRAM_BOT_TOKEN, OPENROUTER_API_KEY, AIMLAPI_API_KEY, OPENAI_API_KEY, OPENAI_COMPAT_API_KEY, and ATOMIC_AGENT_OPENAI_API_KEY.

Worked examples

external is the shipped default, so a fresh install already expects a llama-server you run yourself. To point it at a different URL, set the two keys:

Terminal window
atomic-agent config set localModels.mode external
atomic-agent config set localModels.url http://127.0.0.1:8080

There is no environment variable for this. The URL is resolved from config alone — localModels.url in external mode, or localModels.managed.port in managed mode.

Gotchas to remember

  • Change one key with config set <key> <value>. config set '<json>' replaces the whole file and resets everything you left out.
  • Avoid PATCH /api/config in v0.6.5. It resets every block except localModels, log and agent, and removes your LLM providers.
  • No tasks or browser block in config.json. The task queue and the browser are environment variables only (ATOMIC_AGENT_TASKS_*, ATOMIC_AGENT_BROWSER_*).
  • Frozen snapshot. The agent runs against one immutable config object. Editing config.json won’t reload a running process.
  • Split precedence. File wins for user keys; env wins for operational keys; shell env always beats .env.
  • Migration moves forward. Auto-migration bumps the schema version on write; an older build still reads the newer file but ignores newer settings.
  • Relative paths resolve against cwd. A relative path in config.json (e.g. a dataDirOverride) is resolved against process.cwd(), not the state dir. ~ is expanded directly; $HOME is not interpolated.
  • Validation errors are precise. A bad value raises ConfigValidationError with a dotted field path like memory.reflection.timeoutMs.