Skip to contents

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 object

Every 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, leaderboards

What’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.

Verbosity

verbose = TRUE (default) streams the narration, each code chunk, its output, and constraint-check tables to the console. verbose = FALSE runs silently - everything still checkpoints, so report.md and code.R tell the story afterwards.