This article builds an agent that explains an R package by reading its source. You will run a task, check what the agent did, and then give a second agent permission to write a file, and undo what it wrote.
Setup
Install Deputy from GitHub:
# install.packages("pak")
pak::pak("JamesHWade/deputy")The examples use OpenAI’s gpt-6-luna, so they need
OPENAI_API_KEY. Put it in your .Renviron file
(usethis::edit_r_environ() opens it) and restart R. Keep
keys out of scripts and version control.
To use another provider, pass a different ellmer chat. Deputy uses whatever model and settings that chat has.
Create an agent
Start R in the package you want to explain, then run:
library(deputy)
workspace <- normalizePath(getwd(), winslash = "/", mustWork = TRUE)
agent <- Agent$new(
chat = ellmer::chat("openai/gpt-6-luna"),
tools = tools_preset("minimal"),
permissions = permissions_readonly(),
usage_limits = UsageLimits(max_requests = 6, max_tool_calls = 8),
working_dir = workspace
)-
chatis the ellmer chat that talks to the model. -
toolsgives the model three tools:read_file,read_markdownandlist_files. -
permissionsallows reading and denies writing files, running code and using the web. -
usage_limitsends the run after six model requests or eight tool calls. -
working_diris where relative paths start. To explain a package somewhere else, use its path instead ofgetwd().
Read-only stops the agent from changing anything. It does not limit what it reads: the agent can open any file your R session can open. Permissions decide which tools may run; they are not a filesystem sandbox.
Run a task
Ask a question that the files can answer:
result <- agent$run_sync(
"Read DESCRIPTION and list R/. Explain what this package does and cite the files you used."
)
result$responserun_sync() blocks until the run ends and returns an
AgentResult. Before you use the response, check why the run
ended:
result$stop_reason
result$usage"complete" means the model finished. A limit stops the
run with a reason such as "request_limit" or
"tool_call_limit", and the response may be partial.
result$usage counts requests, tool calls and tokens, and
the cost when ellmer can price the model.
To see which files the agent read, list its tool calls and their results:
result_tool_calls(result)
result_tool_results(result)?AgentResult describes the other fields, including the
full event log and run identifiers.
Other ways to run a task
run_sync() suits scripts. An agent also works like an
ellmer chat, so pick the method that fits your program:
| Method | Returns | Use it for |
|---|---|---|
chat() |
the final text | quick questions at the console |
run() |
a generator of events | printing progress as it happens |
run_async() |
a promise of an AgentResult
|
async code and background work |
stream_async() |
an ellmer stream | Shiny apps |
Every method goes through the same permission checks, hooks and limits.
Watch tool calls as they happen
Add a hook that prints each tool call:
agent$add_hook(hook_log_tools())
result <- agent$run_sync("Read DESCRIPTION and name the package's imported dependencies.")Hooks shows how to write your own.
Let an agent write a file
An agent’s permissions can be narrowed after it is created but never
widened, so writing needs a new agent. This one may write inside
workspace. It still can’t run R or shell code, use the web
or install packages.
write_agent <- Agent$new(
chat = ellmer::chat("openai/gpt-6-luna"),
tools = tools_file(),
permissions = Permissions(file_write = workspace),
usage_limits = UsageLimits(max_requests = 6, max_tool_calls = 8),
enable_file_checkpointing = TRUE,
working_dir = workspace
)
checkpoint_id <- write_agent$checkpoint("before package summary")
write_result <- write_agent$run_sync(
"Read DESCRIPTION and write a short package summary to deputy-summary.md. Leave other files unchanged."
)
write_result$stop_reason
result_tool_calls(write_result)Open deputy-summary.md and check the change in Git.
Because checkpointing is on, Deputy saved the old contents of every file
its write tools touched after the checkpoint. To undo the agent’s
changes:
write_agent$rewind_files(checkpoint_id)Checkpoints cover Deputy’s write_file,
edit_file and multi_edit tools. Changes made
by R code, shell commands or other programs are not recorded.
Troubleshooting
- The provider can’t authenticate
- Check the credential your ellmer provider expects. Try a plain ellmer chat before adding Deputy.
- A tool call is denied
- Check that the tool is registered and that the permission it needs is on. Custom tools also need accurate annotations; see Tools.
- The run stops early
-
Look at
result$stop_reasonandresult$usage, then give the agent a smaller task or raise the limit it hit."tool_loop"means the model made the same tool call with the same result three times in a row."cost_unavailable"means you set a cost limit but ellmer couldn’t price the model, so Deputy stopped rather than guess.?UsageLimitslists every limit.
Next steps
- Tools: choose built-in tools or turn your own R functions into tools.
- Permissions and limits: decide what an agent may do and how much it may spend.
- Structured output: get lists and data frames back instead of text.
- Shiny chat: put an agent in an app.
- Subagents: split a task across specialists.