Skip to content

Commit 6a96f20

Browse files
committed
add_budget(): explicitly extend an exhausted session's hard budget
The mechanical caps bind follow-up tell() calls too - by design - so a finished session can refuse more work. add_budget(steps, seconds) is the explicit remedy; the refusal message now tells the agent to point the user at it, and the extension persists to meta for resumes.
1 parent 7f90ac5 commit 6a96f20

5 files changed

Lines changed: 103 additions & 16 deletions

File tree

R/session.R

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,38 @@ atlas_session <- R6::R6Class("atlas_session",
307307
)
308308
},
309309

310+
#' @description Grant the agent more mechanical budget. The hard caps
311+
#' (`max_steps`, `max_runtime`) protect unattended runs, but they also
312+
#' bind follow-up `$tell()` calls on a finished session - top the
313+
#' budget up explicitly when you want more work done:
314+
#' `res$session$add_budget(steps = 25)`.
315+
#' @param steps Additional code executions to allow.
316+
#' @param seconds Additional wall-clock seconds to allow.
317+
add_budget = function(steps = 0, seconds = 0) {
318+
stopifnot(is.numeric(steps), steps >= 0,
319+
is.numeric(seconds), seconds >= 0)
320+
private$max_steps <- private$max_steps + steps
321+
if (seconds > 0 && !is.null(private$deadline)) {
322+
private$deadline <- max(private$deadline, Sys.time()) + seconds
323+
private$max_runtime <- private$max_runtime + seconds
324+
}
325+
private$meta$max_steps <- private$max_steps
326+
private$meta$max_runtime <- private$max_runtime
327+
saveRDS(private$meta, file.path(self$dir, "meta.rds"))
328+
if (private$verbose) {
329+
cli::cli_alert_info(sprintf(
330+
"Budget extended: %s steps remaining%s.",
331+
if (is.finite(private$max_steps))
332+
format(private$max_steps - private$steps) else "unlimited",
333+
if (!is.null(private$deadline))
334+
sprintf(", %.0f minutes on the clock",
335+
as.numeric(difftime(private$deadline, Sys.time(),
336+
units = "mins")))
337+
else ""))
338+
}
339+
invisible(self)
340+
},
341+
310342
#' @description Compact the conversation to save tokens: archive the
311343
#' transcript to the run directory, clear the context window, and
312344
#' re-orient the agent with a state briefing on the next message. The
@@ -523,7 +555,9 @@ atlas_session <- R6::R6Class("atlas_session",
523555
"blocked - this is enforced mechanically, do not retry. ",
524556
"Objects already in the session (atlas_models, ",
525557
"atlas_leaderboard) remain in place. Write your final report ",
526-
"now from what you already know."))
558+
"now from what you already know, and tell the user they can ",
559+
"grant more budget with session$add_budget(steps = ...) if ",
560+
"they want this work completed."))
527561
}
528562
private$steps <- private$steps + 1
529563
code <- trimws(code)

example.R

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
library(atlas)
1212

1313
# 1. Load your data ------------------------------------------------------
14-
csv_path <- "path/to/your_data.csv" # <- edit me
15-
outcome <- "DEFAULTED" # <- edit me: the column to predict
14+
csv_path <- "~/Desktop/Hastings/HastingsCredit/Data/app_approved_train.csv"
15+
outcome <- "DEFAULTED" # <- edit me: the column to predict
1616

1717
data <- read.csv(csv_path, stringsAsFactors = TRUE)
1818
str(data, list.len = 25)
@@ -24,29 +24,31 @@ atlas_leakage_screen(data, outcome)
2424
# Columns that won't exist at prediction time in deployment (IDs,
2525
# post-outcome fields, decline reasons...) - they are removed before the
2626
# agent ever sees the data:
27-
exclude <- c() # <- e.g. c("QUOTE_DECLINE_REASON")
27+
exclude <- c("default_rate", "Total_Loan_Amount", "PREDICTED_LOSS", "APR") # <- e.g. c("QUOTE_DECLINE_REASON")
2828

2929
# 3. Interactive build ----------------------------------------------------
3030
# The agent explores, proposes a plan, and STOPS IN THE CONSOLE for your
3131
# approval - answer "yes", or steer it ("only GLMs, no trees").
3232
# Watch for the live tally lines as models and tweaks are scored:
3333
# [tally #4 | gbm1: auc = 0.81 | best: glm2 = 0.79 | flat: 0/3 -> KEEP]
3434
res <- atlas(
35-
data, outcome,
36-
n_models = 3, # maximum candidates
37-
goal = "prioritise interpretability",
38-
exclude = exclude,
39-
test_prop = 0.2, # held out; agent never sees these rows
40-
max_steps = 120 # hard safety cap on code executions
35+
data,
36+
outcome,
37+
n_models = 1, # maximum candidates
38+
goal = "prioritise interpretability",
39+
exclude = exclude,
40+
test_prop = 0.2, # held out; agent never sees these rows
41+
max_steps = 10, # hard safety cap on code executions
42+
dir = "~/Desktop/Code"
4143
)
4244

4345
# 4. What you got back ----------------------------------------------------
44-
res # leaderboards, tally summary, constraints, report, cost
45-
res$tally # every attempt: KEEP / DISCARD, best-so-far
46-
res$test_leaderboard # final ranking on the held-out rows (the one to trust)
47-
res$models # fitted models: predict(res$models[[1]], newdata)
48-
res$dir # run directory: report.md, code.R, tally.csv, plots
49-
cat(res$code, sep = "\n\n") # the full script the agent wrote
46+
res # leaderboards, tally summary, constraints, report, cost
47+
res$tally # every attempt: KEEP / DISCARD, best-so-far
48+
res$test_leaderboard # final ranking on the held-out rows (the one to trust)
49+
res$models # fitted models: predict(res$models[[1]], newdata)
50+
res$dir # run directory: report.md, code.R, tally.csv, plots
51+
cat(res$code, sep = "\n\n") # the full script the agent wrote
5052

5153
# The session is still live - ask it anything:
5254
res$session$tell("why did the winning model win?")

man/atlas_session.Rd

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-session.R

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,22 @@ test_that("max_steps is a mechanical stop, not a suggestion", {
216216
expect_no_match(new_session()$chat$get_system_prompt(), "Hard budget")
217217
})
218218

219+
test_that("add_budget() unblocks an exhausted session", {
220+
dir <- temp_dir()
221+
s <- atlas_session$new(mtcars, "mpg", chat = real_chat(), dir = dir,
222+
max_steps = 1)
223+
run_tool <- s$chat$get_tools()$run_r_code
224+
run_tool("1 + 1")
225+
blocked <- run_tool("2 + 2")
226+
expect_match(blocked, "BUDGET EXHAUSTED")
227+
expect_match(blocked, "add_budget") # the agent relays the remedy
228+
229+
s$add_budget(steps = 2)
230+
expect_match(run_tool("2 + 2"), "^\\[1\\] 4")
231+
# the extension is persisted for resumes
232+
expect_equal(readRDS(file.path(dir, "meta.rds"))$max_steps, 3)
233+
})
234+
219235
test_that("max_runtime blocks execution after the deadline", {
220236
s <- new_session(max_runtime = 0.01) # expires almost immediately
221237
Sys.sleep(0.05)

vignettes/autonomous.Rmd

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ gone.
8181
Use them together: generous statistical rules so the agent explores, and a
8282
mechanical ceiling so "overnight" can't become "over the weekend".
8383

84+
The budget binds the whole session, follow-up `$tell()` calls included - a
85+
finished run with nothing left in the tank will refuse further work. That
86+
is deliberate (a cap you can talk your way past is not a cap), and the
87+
remedy is explicit: grant more, then continue.
88+
89+
```{r}
90+
res$session$add_budget(steps = 25)
91+
res$session$tell("remove the worst predictor and re-evaluate")
92+
```
93+
8494
## The live tally
8595

8696
Every model and every tweak is scored the moment it is evaluated. The agent

0 commit comments

Comments
 (0)