A subprocess execution backend for running R code generated by LLMs. Uses callr for process isolation, timeout protection, output capture, and structured result handling.
Details
RCodeRunner provides:
Process isolation: Code runs in a separate R process via callr
Timeout protection: Configurable timeout prevents runaway execution
Output capture: Captures stdout, stderr, messages, and warnings
Context passing: Inject data into the execution environment
Output limiting: Truncates large outputs to prevent memory issues
Security note: r_code_runner() is a trusted-input-only backend. callr
starts a separate process, but that process retains the host user's file,
network, environment, and operating-system permissions. Pattern checks are
only defense in depth and can be bypassed. For untrusted inputs, provide a
runner backed by a container or OS-level sandbox.
Code-executing modules accept any runner that implements the dsprrr runner
protocol. A runner must expose execute(code, context = list()) and
policy() methods. policy() returns a uniquely named list containing at
least backend (character), trust (character), and sandboxed (logical).
execute() returns one uniquely named list. success and result are
required. Missing stdout, stderr, messages, and warnings fields are
normalized to empty strings, and missing duration_ms is normalized to
NA. A failed result must include a non-empty error. Its error_type is
either "execution" for code the model may repair or "interpreter" for a
terminal process/protocol failure. Legacy runners that omit error_type are
treated as reporting a repairable execution failure. A successful result
may omit error and error_type.
Code-executing modules accept exactly one runtime source. Passing runner
retains that caller-owned object and never closes it. Passing a zero-argument
interpreter_factory creates one fresh invocation-owned runner. Factory
results may implement idempotent start() and must implement idempotent
terminal shutdown() or its compatibility alias close(). dsprrr starts a
factory runner before use and shuts it down exactly once on success, error,
or interrupt.
Examples
if (FALSE) { # \dontrun{
runner <- r_code_runner(timeout = 5)
# Simple execution
result <- runner$execute("1 + 1")
result$result
# [1] 2
# With context
result <- runner$execute(
"mean(.context$data$mpg)",
context = list(data = mtcars)
)
result$result
# [1] 20.09062
# Timeout handling
result <- runner$execute("Sys.sleep(100)")
result$success
# [1] FALSE
result$error
# "Execution timed out after 5 seconds"
} # }