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 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.
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.
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:
atomic-agent config get # whole file as JSONatomic-agent config get <key> # one value, e.g. log.levelatomic-agent config set <key> <value> # one value, rest of the file untouchedatomic-agent config set '<json>' # replace the whole file (missing keys get defaults)atomic-agent config unset <key> # restore one key to its defaultatomic-agent config list # every key as key = valueatomic-agent config path # where config.json livesKeys 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.
atomic-agent config set log.level debugatomic-agent config set analytics.enabled falseatomic-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.
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
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 is a versioned JSON document. When Atomic Agent starts:
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.
| Key | Type | Default | What it does |
|---|---|---|---|
version | int | 72 | Schema version (managed by migration; don’t hand-edit). |
localModels.url | string | http://127.0.0.1:8080 | HTTP 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.mode | managed | external | external | external expects a llama-server you run yourself; managed runs the daemon for you. atomic-agent models use <id> flips this to managed. |
localModels.managed.modelId | string | null | null | Active 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.port | int | 19091 | Port for the managed chat daemon. The embedding daemon uses 19092. |
localModels.completionMaxTokens | int | 16384 | Max new tokens per completion. 0 means no client-side cap. ATOMIC_AGENT_LLAMA_MAX_TOKENS overrides it per process. |
localModels.thinking | auto | on | off | auto | The chat template’s thinking switch for local models whose template reads it. auto keeps the template’s own default. |
localModels.reasoningBudgetTokens | int | 1500 | How many tokens a local reasoning model may spend thinking before a tool call. 0 leaves it unbounded. |
log.level | debug|info|warn|error | info | Log verbosity. |
agent.tokenBudget | int | 3000 | Max prompt tokens budgeted per step. |
agent.maxSteps | int | 25 | Steps per leg. After a leg the agent reports progress and continues, while agent.task.autoContinue is on. |
agent.task.maxSteps | int | 1000 | Hard step ceiling for one task. --max-steps on run / tui overrides it. |
agent.task.maxDurationMs | int | 7200000 | Wall-clock ceiling for one task (2 hours). |
agent.task.autoContinue | boolean | true | Continue into the next leg automatically instead of stopping after agent.maxSteps. |
agent.approvalLevel | int 1–5 | 1 | How much the agent may do without asking. See the ladder below. |
agent.readScope | working-dir | unrestricted | working-dir | Where file reads may go without asking. Reads outside the working directory need approval (or level 5). |
agent.providerWait.enabled / .maxWaitMs | boolean / int | true / 300000 | Wait for a provider that is temporarily unavailable, up to 5 minutes. |
agent.conversationMaxTokens | int | 0 (auto) | Conversation-history token cap. 0 sizes it from the model’s context window (32000 when the window is unknown). |
agent.worldSnapshotMaxTokens | int | 8000 | Cap for the browser ARIA snapshot section. |
tools.shell.defaultTimeoutMs | int | 600000 | How 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.jobMaxMs | int | 3600000 | Hard limit for a background shell job (1 hour). |
tools.shell.maxJobs | int | 3 | Concurrent background shell jobs. |
http.enabled | boolean | true | Let the agent make outbound HTTP requests. |
http.approvalMode | never | writes | always | never | Extra approval mode for HTTP on top of the ladder. never means the ladder alone decides. |
http.hostAllowlist | string[] | null | null | When set, the HTTP request tool only reaches these hosts. null means no host restriction. |
web.search.enabled | boolean | true | Enable the web search tool. |
web.search.provider | string | exa | Search 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.remoteSync | boolean | false | Allow 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.roots | string[] | [] | Directories whose direct children are your projects. See below. |
vision.enabled | boolean | true | Register the vision.describe tool (also requires a vision-capable model). |
analytics.enabled | boolean | true | Anonymous usage stats. See below. |
skills.disabled | string[] | [] | Skill names to hide from the registry. |
skills.taps | string[] | 3 repos | GitHub repositories skill browse / skill search read from. |
skills.clawhub.enabled | boolean | true | Use the ClawHub skill registry. |
skills.clawhub.apiBase | string | https://clawhub.ai | ClawHub endpoint. |
skills.catalogTokenBudget | int | 512 | Token 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.enabled | boolean | null | null | Write 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.maxBytesPerSession | int | 10485760 | Size cap for one session’s trace (10 MiB). Past it, the oldest events are dropped. |
tui.theme | string | auto | auto | TUI colour theme. |
tui.whileBusySubmit | steer | queue | steer | What Enter does while a turn is running: fold the message into that turn, or queue it as the next turn. |
tui.mouse | boolean | true | Terminal mouse support in the TUI. tui --no-mouse turns it off for one run. |
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.
| Level | Name | Stops asking about |
|---|---|---|
1 | paranoid | Nothing — every gated action asks first. This is the default. |
2 | workspace | File writes, edits, and patches strictly inside the session working directory. |
3 | home | Adds file writes anywhere under your home directory, moves to Trash, archive extraction, and HTTP requests. |
4 | operator | Adds guarded shell commands, skill scripts, process kills, network git (push, pull, fetch, clone, adding a remote), publishing (PRs, issues), and Fusion fan-out. |
5 | full trust | Everything, 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.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:
atomic-agent config set analytics.enabled falsewhich 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.
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.*)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.
| Key | Default | Notes |
|---|---|---|
memory.profile.enabled | true | Profile facts and their tools. |
memory.profile.maxTokens | 512 | Cap for the ### profile prompt section. |
memory.notes.enabled | true | Freeform searchable notes. |
memory.notes.maxEntries | 1000 | Hard row cap; FIFO eviction on overflow. |
memory.dedup.enabled | true | Phase 1A dedup on note write. |
memory.embeddings.enabled | false | Hybrid BM25 + cosine recall (needs embedding daemon). |
memory.links.enabled | true | Link-graph BFS expansion. |
memory.lessons.enabled | true | Distilled lessons (changes the stable prefix). |
memory.procedures.enabled | true | Advisory how-to procedures (changes the stable prefix). |
memory.voting.enabled | true | Vote curation of memory items. |
memory.consolidation.enabled | true | Cold-path clustering/distillation. |
memory.reflection.enabled | true | Async end-of-turn memory formation. |
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:
| Key | What it does |
|---|---|
llm.activeTextProvider | Selected text provider id. |
llm.activeEmbeddingProvider | Selected embedding provider id. |
llm.toolTransport | auto | grammar | native_tools. |
llm.providers[] | Provider entries (id, kind, optional apiKey). |
llm.fallback | Cross-provider fallover chain used when the active provider fails. |
llm.runMode | Run mode: local, cloud, or fusion, plus Fusion settings. |
MCP servers — external tool servers live under mcp.servers[]:
| Field | What it does |
|---|---|
name | Unique kebab-case namespace (max 32 chars, no dots). |
enabled | Connect at bootstrap. |
transport | { kind: 'stdio' }, { kind: 'streamable_http' }, or { kind: 'sse' }. |
trust | approval_gated (default, fail-closed) or pure_read (batches with other reads). |
env | Per-server env overrides for stdio transport. |
Discovered tools register as mcp.<server>.<rawName>. Trust defaults to approval_gated whenever unspecified.
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.
| Variable | Default | What it does |
|---|---|---|
ATOMIC_AGENT_STATE_DIR | ~/.atomic-agent | Root for config, secrets, DBs, traces, skills, models. |
ATOMIC_AGENT_GRAMMARS_DIR | bundled | Override the GBNF grammar asset directory. |
ATOMIC_AGENT_TOOL_CALL_GRAMMAR | bundled | Path to the tool-call.gbnf grammar file (not the directory). Takes priority over every other lookup. |
ATOMIC_AGENT_RG_PATH | bundled | Override the ripgrep binary path. |
ATOMIC_AGENT_STABLE_PREFIX_SALT | atomic-agent-v1 | Salt mixed into the prompt-prefix hash that maps a session to a llama-server KV-cache slot. Change it to force cache invalidation. |
| Variable | Default | Notes |
|---|---|---|
ATOMIC_AGENT_LLAMA_API_KEY | unset | Bearer token for a protected llama-server (optional). |
ATOMIC_AGENT_LLAMA_MAX_TOKENS | localModels.completionMaxTokens (16384) | Overrides the config key for this process, clamped 64 to 131072. It cannot express 0 (no cap). |
ATOMIC_AGENT_LLAMA_HEALTH_TIMEOUT_MS | 3000 | Health-probe timeout. |
ATOMIC_AGENT_LLAMA_HEALTH_RETRIES | 5 | Health-probe attempts. |
ATOMIC_AGENT_LLAMA_HEALTH_BACKOFF_MS | 500 | Backoff between health probes. |
ATOMIC_AGENT_LLAMA_REQUEST_TIMEOUT_MS | 300000 | Per-request idle timeout (5 minutes). |
ATOMIC_AGENT_LLAMA_FIRST_TOKEN_TIMEOUT_MS | 1800000 | How long a local stream may wait for its first token (30 minutes). Raise it on very slow hardware. |
ATOMIC_AGENT_LLAMA_STREAM_TOTAL_TIMEOUT_MS | 21600000 | Backstop on one streaming response (6 hours). |
ATOMIC_AGENT_LLAMA_COMPLETION_RETRIES | 3 | Retry count on completion failure. |
ATOMIC_AGENT_LLAMA_COMPLETION_RETRY_BACKOFF_MS | 150 | Backoff between retries. |
ATOMIC_AGENT_LLAMA_DEFAULT_SLOT | 0 | Default llama-server KV-cache slot. |
ATOMIC_AGENT_DOWNLOAD_CONNECTIONS | localModels.download.connections (16) | Parallel connections per model download. Lower it on a metered or flaky link. |
ATOMIC_AGENT_LLAMA_TEMPERATURE / _TOP_P / _TOP_K / _SEED | unset | Sampling overrides (parsed at module load, not per-request). |
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.
| Variable | Default | Notes |
|---|---|---|
ATOMIC_AGENT_BROWSER_ENABLED | true | Set 0 to disable the browser tools. |
ATOMIC_AGENT_BROWSER_CHANNEL | chrome | chrome | msedge | chromium. |
ATOMIC_AGENT_BROWSER_HEADLESS | false | 1 for headless. |
ATOMIC_AGENT_BROWSER_EXECUTABLE_PATH | auto-detect | Explicit Chromium binary. |
ATOMIC_AGENT_BROWSER_NO_SANDBOX | false | 1 to pass --no-sandbox (containers/CI only). |
ATOMIC_AGENT_BROWSER_CDP_URL | unset | Attach to an existing browser over CDP. |
ATOMIC_AGENT_BROWSER_LAUNCH_TIMEOUT_MS | 30000 | Launch timeout. |
| Variable | Default | Notes |
|---|---|---|
ATOMIC_AGENT_MAX_PARALLEL_TOOL_CALLS | 8 | Batch fan-out cap (1 to 16). |
ATOMIC_AGENT_LOADED_TOOLS_CAP | 8 | Loaded-tool cap (1 to 64). |
ATOMIC_AGENT_LOADED_TOOLS_MAX_TOKENS | 600 | Token ceiling for the ### loaded-tools prompt section. A safety cap, not a routine truncation point. |
ATOMIC_AGENT_AUTO_EXPAND_RARE_ON_ERROR | true | Auto-load a rare tool’s schema on error. |
ATOMIC_AGENT_BATCH_TOOL_RESULT_CHAR_CAP | 32000 | Combined 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_CAP | 16000 | Character cap on one shell command’s result summary. |
ATOMIC_AGENT_SHELL_TOOL_RESULT_TAIL_LINES | 500 | Lines kept from the end of a shell command’s output. |
ATOMIC_AGENT_SKILLS_CATALOG_BUDGET | skills.catalogTokenBudget (512) | Overrides the config key for this process. |
ATOMIC_AGENT_LOOP_WARNING_THRESHOLD | 3 | Args-only repeat warn threshold. |
ATOMIC_AGENT_LOOP_CRITICAL_THRESHOLD | 5 | No-progress streak veto threshold. |
ATOMIC_AGENT_LOOP_BREAKER_VETO_STREAK | 3 | Consecutive vetoes before a forced graceful reply. |
ATOMIC_AGENT_LOOP_HISTORY_SIZE | 30 | Size of the loop tracker’s history window. |
ATOMIC_AGENT_LOOP_WANDERING_THRESHOLD / _ESCALATION | 6 / 12 | Distinct-args spread for wandering tools: redirect, then escalate to a graceful reply. |
The durable task queue has no config.json block. These variables are the only way to configure it.
| Variable | Default | Notes |
|---|---|---|
ATOMIC_AGENT_TASKS_ENABLED | true | Master task-queue switch. |
ATOMIC_AGENT_TASKS_MAX_ATTEMPTS | 3 | Default retry budget per task. |
ATOMIC_AGENT_TASKS_RUN_ON_CREATE | true | Run an unscheduled task immediately when it is created. |
ATOMIC_AGENT_TASKS_SCHEDULER_ENABLED | true | Background ticker switch. |
ATOMIC_AGENT_TASKS_SCHEDULER_TICK_MS | 5000 | Scheduler poll interval. |
ATOMIC_AGENT_TASKS_SCHEDULER_BATCH | 10 | Max tasks drained per tick. |
ATOMIC_AGENT_TASKS_MIN_INTERVAL_MS | 1000 | Floor on --every intervals. |
ATOMIC_AGENT_TASKS_BACKOFF_INITIAL_MS / _BACKOFF_MAX_MS | 1000 / 60000 | Retry backoff bounds. |
ATOMIC_AGENT_TASKS_STALE_AFTER_MS | 300000 | When a running task is considered stale (5 minutes). |
ATOMIC_AGENT_TASKS_AGENT_TOOLS_ENABLED | true | Expose the task tools to the agent itself. |
| Variable | Default | Notes |
|---|---|---|
ATOMIC_AGENT_API_KEY | — | Bearer token for atomic-agent serve (fallback when --api-key is omitted). |
ATOMIC_AGENT_UPDATE_CHECK_ON_STARTUP | true | Check for a newer release at startup. |
ATOMIC_AGENT_REPO | AtomicBot-ai/atomic-agent | Override the GitHub repository the update check and atomic-agent update query. |
ATOMIC_AGENT_DEBUG_ARGV | unset | 1 logs process.argv to stderr. |
.env fileSecrets never belong in config.json. They live in <stateDir>/.env as plain KEY=VALUE lines, with the file mode set to 0600.
TELEGRAM_BOT_TOKEN=123456:ABC-your-bot-tokenOPENROUTER_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.
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:
atomic-agent config set localModels.mode externalatomic-agent config set localModels.url http://127.0.0.1:8080There 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.
Run in a container with no display, no sandbox, and no approval prompts:
export ATOMIC_AGENT_STATE_DIR=/work/.atomic-agentexport ATOMIC_AGENT_BROWSER_HEADLESS=1export ATOMIC_AGENT_BROWSER_NO_SANDBOX=1atomic-agent run --no-approval --max-steps 40--no-approval forces approval level 5, which approves every gated action. Use it only in trusted, isolated environments.
Expose the OpenAI-compatible endpoint behind a bearer token:
export ATOMIC_AGENT_API_KEY="sk-local-secret"atomic-agent serve --host 127.0.0.1 --port 8787The key can also be passed inline with --api-key; the env var is the fallback when the flag is omitted. /health and /v1/models are reachable without auth.
config set <key> <value>. config set '<json>' replaces the whole file and resets everything you left out.PATCH /api/config in v0.6.5. It resets every block except localModels, log and agent, and removes your LLM providers.tasks or browser block in config.json. The task queue and the browser are environment variables only (ATOMIC_AGENT_TASKS_*, ATOMIC_AGENT_BROWSER_*).config.json won’t reload a running process..env.config.json (e.g. a dataDirOverride) is resolved against process.cwd(), not the state dir. ~ is expanded directly; $HOME is not interpolated.ConfigValidationError with a dotted field path like memory.reflection.timeoutMs.