r_code_runner() creates a runner that executes R code in a separate R
process with callr, with a timeout and captured
output. Code-writing modules such as program_of_thought(), code_act()
and rlm_module() use a runner to execute the code the model writes.
The runner is for trusted input only. The child process has the same file,
network, environment and operating-system permissions as your R session;
the package allow-list and pattern checks are not a security boundary. For
untrusted input, use a sandboxed runner such as mcp_repl_runner().
Arguments
- timeout
Maximum execution time per call, in seconds (default 30).
- max_output_chars
Maximum number of characters kept from
stdoutandstderr; longer output is truncated (default100000L).- allowed_packages
Packages the code may load (default
base,stats,utilsandmethods). This check is a convenience, not a security boundary.- prelude
Character vector of R code run before each call's code, for example to set options.
- persistent
Whether to reuse one R session, and its variables, across calls to
execute()(defaultFALSE, a fresh process per call).
Details
Using a runner
runner$execute(code, context = list()) runs code and returns a list
with success, result (the value of the last expression), stdout,
stderr, messages, warnings, error, error_type, retryable and
duration_ms. Errors and timeouts are returned in error rather than
thrown. Values in context are available to the code as .context.
runner$policy() describes the trust boundary, and runner$shutdown()
stops a persistent session.
With persistent = TRUE, one R session is reused, so variables survive
between calls. runner$prepare_context(context) stages large objects once
for later calls, and runner$reset() clears the session.
Writing your own runner
Code-executing modules accept any object with execute(code, context = list()) and policy() methods. policy() returns a named list with at
least backend (character), trust (character) and sandboxed (logical).
execute() returns a named list with at least success and result;
missing stdout, stderr, messages and warnings become empty strings
and a missing duration_ms becomes NA. A failed result needs a non-empty
error and an error_type of "execution" (code the model may fix) or
"interpreter" (a process or protocol failure that ends the run).
Modules take exactly one of runner or interpreter_factory. A runner
belongs to you: it is reused across calls and never shut down by dsprrr. An
interpreter_factory is a function with no arguments that returns a fresh
runner; dsprrr starts it (calling start() when the runner has one) and
calls its shutdown() method exactly once when the call ends, whether it
succeeds, fails or is interrupted.
See also
Other code execution:
code_act(),
mcp_repl_runner(),
program_of_thought(),
rlm(),
rlm_module()
Examples
runner <- r_code_runner(timeout = 10)
runner
#>
#> ── RCodeRunner
#> • Timeout: 10 seconds
#> • Max output: 100000 chars
#> • Allowed packages: "base", "stats", "utils", and "methods"
#> ! Trust: "trusted-input-only"; not sandboxed
result <- runner$execute("sqrt(16)")
result$success
#> [1] TRUE
result$result
#> [1] 4
# Pass data in through `.context`
runner$execute(
"mean(.context$data$mpg)",
context = list(data = mtcars)
)$result
#> [1] 20.09062
# Errors are returned, not thrown
runner$execute("stop('boom')")$error
#> [1] "boom"
# A persistent runner keeps variables between calls
session <- r_code_runner(persistent = TRUE)
invisible(session$execute("x <- 21"))
session$execute("x * 2")$result
#> [1] 42
session$shutdown()