Phrase the task well
One clear goal per turn. The model classifies your task and adjusts its own guidance.
Atomic Agent runs on small, quantized models on your own machine. That is the whole point: your prompts and files stay on your machine when you run a local model (anonymous usage analytics are on by default and can be turned off in the Privacy tab), there are no per-token fees, and you can hack any layer. But a 4B model running through llama.cpp is not GPT-4. It needs a little help to do its best work.
This page is a practical checklist. Each tip maps to something the runtime actually does, so you can lean into the grain of the system instead of fighting it.
Phrase the task well
One clear goal per turn. The model classifies your task and adjusts its own guidance.
Let memory do the remembering
Don’t paste history back in. Pin facts; let recall and reflection handle the rest.
Keep prompts cache-friendly
A stable prompt prefix means fast steps. Avoid forcing changes that bust the KV-cache.
Pick a model that fits
Bigger models reason better; smaller models are faster. Match the model to the work.
The agent runs a loop: it reads your message, calls tools, and repeats until it replies or finishes. Small models do best when each turn has one clear objective.
./src and summarize each” beats “clean up my code”.reply or finish.Under the hood, the prompt builder runs renderTaskPolicy(), which classifies your message into debug, code_edit, broad_exploration, or multi_step and emits tailored guidance. Clear phrasing helps it classify correctly, which directly improves the plan.
Local models have small context windows. Atomic Agent keeps prompts tight (default budget agent.tokenBudget, with conversation clamped to the model’s real context window) by storing knowledge outside the prompt and recalling only what’s relevant.
Three memory channels do the heavy lifting:
| Channel | What it holds | Tool |
|---|---|---|
| Profile facts | Durable key/value facts about you | memory.profile.set / .list |
| Notes | Searchable freeform notes (FTS5/BM25) | memory.notes.store / .recall |
| Lessons & procedures | Distilled patterns from past sessions | memory.lessons.recall / memory.procedures.recall |
Best practices:
memory.profile.set { key, value, pinned: true } renders every turn. Use it for things like your name, stack, or conventions.pinned: false with keywords, and the fact only appears when your message mentions one of those keywords (memory.profile.contextualKeywordGate is on by default). This keeps the prompt lean.By default, recall uses BM25 keyword search. If you want paraphrase-tolerant recall (“the auth bug” finding a note that said “login token failure”), enable embeddings:
// <stateDir>/config.json{ "memory": { "embeddings": { "enabled": true } }}Embeddings run on a second llama.cpp daemon. If it fails to start, recall silently falls back to BM25, so nothing breaks. Expanding recall to linked notes (memory.links.enabled) is already on by default; set it to false to turn it off.
Each step reuses a byte-stable prompt prefix (persona, tools, skills, capabilities) via llama.cpp’s KV-cache. A cache hit is what makes multi-step sessions affordable on local hardware. Anything that changes the prefix forces a full recompute.
What keeps the cache warm:
skill.view is fine — it lands in the variable tail, not the prefix, and is cached for the rest of the session.flowchart LR
A["Stable prefix<br/>persona · tools · skills"] -->|cached once| KV["KV-cache slot"]
B["Variable tail<br/>conversation · memory · world"] -->|rebuilt per step| Step["LLM step"]
KV -->|reused every step| Step
Step -->|reply / finish| Done["Turn ends"]
Step -->|more tools| B
The tail is deliberately ordered slowest-to-fastest changing, so hot sections (conversation) never invalidate slower ones (profile, loaded skills) sitting above them in the cache.
In a single step the model can emit one or many tool calls as a JSON array. The executor groups them by resource class:
pure_read tools run in parallel (e.g. several os.fs.read calls at once).reply, finish) run last, after every other tool in the batch settles.You don’t configure this per call, but you can influence it:
ATOMIC_AGENT_MAX_PARALLEL_TOOL_CALLS environment variable (default 8, max 16) if you want wider read fan-out on a fast machine. It is an environment variable only; there is no config.json key for it.Dangerous operations (shell, os.fs.write, os.http.request, archive extraction, process kill, skill scripts) pause for approval by default.
Approvals are a five-step ladder (agent.approvalLevel), not an on/off switch. Level 1 is the default and asks about everything; each step up silences a broader category:
| Level | Stops asking about |
|---|---|
| 1 | nothing — every gated action asks first (default) |
| 2 | file writes inside the session working directory |
| 3 | writes anywhere under your home directory, Trash moves, archive extraction, HTTP requests |
| 4 | guarded shell commands, skill scripts, process kills, network git (push, pull, fetch, clone, adding a remote), publishing to GitHub, Fusion fan-out |
| 5 | everything, including writes to the agent’s own trust config, reads outside the working directory, sending email, and non-web browser URLs |
Hardline shell-guard rules sit outside the ladder: they fire before the gate and block at every level.
In the TUI, the coding-mode chip in the composer (/mode, or Ctrl+G then M) switches the current session between default, plan (read-only: tools that would change something are refused), auto (level 2 or higher) and bypass permissions (level 5) without editing config. It applies to that session only; the persisted level is agent.approvalLevel in config.json.
Approval prompts appear in your terminal, TUI, Telegram, Discord, or HTTP /api/events stream. This is the safe default for everyday use.
atomic-agent runFor test runs, sandboxes, or fully autonomous tasks, bypass the gate. Every dangerous tool auto-approves and you lose visibility into what ran — use only where you trust the workload.
atomic-agent run --no-approvalatomic-agent serve --no-approval--no-approval forces level 5 for that run. Equivalent config:
{ "agent": { "approvalLevel": 5 } }If you only want to loosen part of the gate, set an intermediate level instead: 3 lets the agent write files and make HTTP requests unprompted while still asking before it runs a shell command.
Small models sometimes get stuck repeating a tool call or wandering through searches. Atomic Agent has a per-turn loop detector that handles this gracefully:
The takeaway: if the agent gets stuck, it will recover on its own and reply. You rarely need to intervene. If you see frequent loops on a specific task, that’s usually a sign the task is underspecified — tighten the phrasing or break it into smaller turns.
The thresholds are tunable if a particular workload needs more or less patience:
ATOMIC_AGENT_LOOP_WARNING_THRESHOLD # warn after N arg-only repeatsATOMIC_AGENT_LOOP_CRITICAL_THRESHOLD # veto after N no-progress repeatsATOMIC_AGENT_LOOP_WANDERING_ESCALATION # force reply when search args spread too wideLong tasks continue automatically: every agent.maxSteps (default 25) steps the agent checks it is still making progress and carries on, up to agent.task.maxSteps (default 1000) or agent.task.maxDurationMs (default 2 h). If a turn ends with max_steps instead of a real reply, it hit one of those ceilings.
--max-steps N on atomic-agent run sets a hard ceiling for one task, so use it to cap a run, not to extend one. --max-steps 40 stops the task at 40 steps instead of 1000.agent.task.maxSteps or agent.task.maxDurationMs in config.Managed local models are swappable. A few rules of thumb:
qwen-3.5-4b) is quicker and cheaper on VRAM.mmproj projector.atomic-agent models list # see installed + availableatomic-agent models pull <id> # download a modelatomic-agent models use <id> # switch the active modelatomic-agent models status # check the daemonWhen a session behaves oddly, traces are your best tool. They record every prompt, completion, tool call, and memory operation as append-only NDJSON.
atomic-agent trace list # recent sessionsatomic-agent trace show <sessionId> # full chronologyatomic-agent trace replay <sessionId> # detect prompt-stack drifttrace replay is especially useful after upgrading: it rebuilds the stable prefix with your current tools/skills and flags whether anything drifted since recording — a common cause of “it used to work.”
| Goal | Lever |
|---|---|
| Remember something durably | memory.profile.set { pinned: true } |
| Surface a fact only when relevant | memory.profile.set { pinned: false, keywords: [...] } |
| Fuzzy recall | memory.embeddings.enabled = true |
| Wider parallel reads | ATOMIC_AGENT_MAX_PARALLEL_TOOL_CALLS (env var only) |
| Longer tasks | agent.task.maxSteps / agent.task.maxDurationMs |
| Cap one run | --max-steps N |
| Autonomous runs | --no-approval / agent.approvalLevel (1–5, default 1) / /mode for one TUI session |
| More/less loop patience | ATOMIC_AGENT_LOOP_* thresholds |
| Better reasoning | atomic-agent models use <larger-model> |
| Debug a bad session | atomic-agent trace show / replay |