A model build is a long-running, interruptible collaboration, so Atlas treats session state as something that lives on disk first and in memory second. The design has one load-bearing idea: the code log is the source of truth. Arbitrary R environments can’t be reliably serialised, but a recorded script re-run against the same data reconstructs one exactly. Atlas therefore checkpoints every code chunk the moment it executes, and “resuming” means replaying that script - models included - then restoring the conversation.
The session object
atlas() is a one-call wrapper around an
atlas_session (R6), which holds the ellmer conversation,
the R environment the agent works in, and the run directory:
library(atlas)
s <- atlas_session$new(mtcars, outcome = "mpg", n_models = 3,
goal = "prioritise interpretability")
res <- s$build() # the full lifecycle, approval included
s$tell("swap model 2 for a GAM") # follow-up, same context
s$check() # re-verify constraints right now
s$results() # assemble a fresh results objectEvery results object carries its live session as
res$session, so a follow-up is never more than one call
away.
One semantic worth internalising: a results object is a
snapshot, the session is live. results() copies
the models, leaderboards, and tally into a plain list at the moment you
call it; nothing in that list updates when the session moves on. After a
follow-up changes the models, refresh:
res$session$tell("remove the worst predictor and re-evaluate")
res <- res$session$results() # new snapshot: updated models, leaderboardsWhat’s on disk
Each session owns a run directory - by default a timestamped folder
under getOption("atlas.dir", ".atlas"); set that option
once, or pass dir per run.
| file | contents |
|---|---|
data.rds |
the training data, so a resume works cold |
test.rds |
the protected test rows (when test_prop > 0) |
meta.rds |
outcome, stopping rules, goal, constraints |
code.R |
every chunk the agent ran - a readable script |
code.rds |
the same chunks, exactly as recorded, for replay |
turns.rds |
the current conversation window |
turns-archive-*.rds |
compacted-away conversation history |
tally.csv |
the attempt ledger: KEEP / DISCARD per attempt |
report.md |
the agent’s report |
leaderboard.csv |
validation metrics per model |
test_leaderboard.csv |
held-out test metrics (when test_prop > 0) |
models.rds |
the fitted models |
message.txt |
a pending [atlas_message()] not yet delivered |
Checkpoints are written after every tool call and every reply, so at
worst a crash loses the single in-flight step. code.R
doubles as the reproducibility artifact: plain R you can read, audit, or
run without Atlas - or an API key.
Resuming
s <- atlas_resume(".atlas/20260703-141500")
s$tell("continue where you left off")atlas_resume() reloads the data, replays the recorded
code log top to bottom (agent code is seeded, so the replay is exact),
and restores the conversation turns. The agent comes back knowing
everything it did, with every fitted object rebuilt. The one cost to
budget for: replay refits the models, so resuming a session whose models
take minutes to fit takes those minutes again.
Interrupting a build
You don’t have to wait for the agent to ask you something. Interrupt
with Ctrl+C / Esc, then resume with the new
information - the work up to the interruption was already
checkpointed:
res <- atlas(big_data, "claim_cost") # Ctrl+C mid-build...
s <- atlas_resume(".atlas/20260703-141500")
s$tell("stop trying tree models; the deployment target only supports GLMs")You can also steer without interrupting at all:
atlas_message(dir, text) writes a message into the run
directory from any other R session or terminal, and it is delivered with
the agent’s next code execution, marked as its highest-priority
instruction. This works for every session, autonomous ones included.
Programmatic front-ends get a lower-level hook too: the
interject constructor argument, a function()
polled after every tool call. Return a message to have it delivered the
same way; return NULL when there is nothing to say.
Staying inside the token budget
The conversation is the only part of a session that grows - and LLM APIs re-read the whole context on every request, so an unmanaged conversation costs more with every step and eventually overflows the model’s window. atlas manages it automatically, exploiting the same design idea as resume: the conversation is not the real memory.
Past compact_at input tokens (default 100,000, cached
tokens included), the next message triggers compaction: the transcript
is archived to the run directory (turns-archive-01.rds,
-02, …- nothing is deleted), the window is cleared, and the
message is prefixed with a re-orientation briefing built from ground
truth - the models in atlas_models, the current
leaderboard, the number of code chunks run. Because the briefing is
assembled deterministically from session state, it costs no extra LLM
call and cannot hallucinate, unlike a model-written summary.
You can also compact by hand before a long follow-up:
res$session$compact()
res$session$tell("now write an extended technical appendix for the report")Cost stays visible: res$cost (and the last line of
print(res)) is the session’s cumulative dollar cost,
straight from ellmer’s token accounting.
Questions from the agent
The agent asks for plan approval - and anything else it genuinely
needs - through its ask_user tool. In an interactive
session the question is printed and the console blocks on your answer;
treat it as a conversation, not a yes/no gate, since whatever you type
is folded into the plan. In non-interactive contexts
(Rscript, CI) the tool tells the agent to use its best
judgment and record the decision in the report, so scripted runs never
hang. Front-ends can supply their own handler via
on_ask.