Deputy turns an ellmer chat into a governed R Agent. In this guide you will create a bounded, read-only Agent; inspect its result; then deliberately grant one workspace-scoped write capability and protect it with a checkpoint.
Before you begin
Install Deputy and the development version of ellmer:
You also need credentials for one ellmer provider. The examples use OpenAI, but the Deputy API is provider-independent.
1. Define the workspace and limits
Make the authority for the first run visible before creating a chat.
The minimal preset contains only read_file,
read_markdown, and list_files. The permission
policy denies writes, code execution, shell commands, web requests, and
package installation. The run also has finite request and tool-call
budgets.
library(deputy)
workspace <- normalizePath(getwd(), winslash = "/")
first_tools <- tools_preset("minimal")
first_permissions <- permissions_readonly()
first_limits <- UsageLimits(max_requests = 6, max_tool_calls = 8)
stopifnot(
dir.exists(workspace),
length(first_tools) == 3L,
identical(first_permissions$mode, "readonly"),
identical(first_limits$max_tool_calls, 8L)
)Run this from the package you want to inspect, or replace
getwd() with that package’s absolute path.
working_dir gives relative paths a stable base. It does
not create a read sandbox: a read permission can reach files that the R
process can reach. Keep the task narrow and run Deputy with
operating-system isolation when the input or generated code is
untrusted.
2. Create the Agent
An Agent combines an ellmer chat with the runtime policy
you just defined. This chunk makes a provider request, so it is
displayed but not run while the vignette is built.
chat <- ellmer::chat("openai")
agent <- Agent$new(
chat = chat,
tools = first_tools,
permissions = first_permissions,
usage_limits = first_limits,
working_dir = workspace,
agent_name = "package-scout"
)The chat owns provider interaction. Deputy owns the tool loop around it: permissions, hooks, limits, events, persistence, checkpoints, and delegation.
3. Run one useful task
Ask for an answer that the available tools can support. A read-only package inspection is a better first task than an open-ended request to “improve the code.”
result <- agent$run_sync(
paste(
"Read DESCRIPTION and list the R/ directory.",
"Explain the package purpose and identify three good entry-point files.",
"Cite the paths you used. Do not propose or make edits."
)
)run_sync() blocks until the run stops and returns an
AgentResult. The result keeps the outcome and the evidence
needed to understand how it happened:
cat(result$response)
result$stop_reason
result$usage
result$session_id
result$run_id
result$tool_calls()
result$tool_results()Check stop_reason before treating the response as
complete. A request, tool-call, token, or cost limit produces a typed
stop reason rather than a silently truncated success.
4. Grant writes deliberately
When a task really needs to create a file, replace the read-only policy with an explicit one. This policy permits Deputy’s native file tools to write only inside the canonical workspace. R execution, shell access, web access, and package installation remain off.
write_permissions <- Permissions$new(
mode = "standard",
file_read = TRUE,
file_write = workspace,
bash = FALSE,
r_code = FALSE,
web = FALSE,
install_packages = FALSE
)
stopifnot(
identical(write_permissions$file_write, workspace),
identical(write_permissions$r_code, FALSE),
identical(write_permissions$bash, FALSE)
)Create a separate Agent with file checkpointing enabled. The checkpoint marks the state before the task. Deputy records preimages when its native write, edit, and multi-edit tools change files.
write_agent <- Agent$new(
chat = chat$clone()$set_turns(list()),
tools = tools_file(),
permissions = write_permissions,
usage_limits = first_limits,
enable_file_checkpointing = TRUE,
working_dir = workspace,
agent_name = "summary-writer"
)
checkpoint_id <- write_agent$checkpoint("before package summary")
write_result <- write_agent$run_sync(
paste(
"Read DESCRIPTION and write a concise package summary to deputy-summary.md.",
"Do not modify any other file."
)
)Review the result and the actual diff. If the native file-tool changes are not useful, rewind them:
write_result$stop_reason
write_agent$list_checkpoints()
write_agent$rewind_files(checkpoint_id)Checkpoints do not capture changes made outside Deputy’s native file tools, and they do not replace Git or backups.
Common failures
The provider cannot authenticate
Configure the credential expected by your chosen ellmer provider, then verify a plain ellmer chat before adding Deputy. Deputy does not manage provider keys.
A tool call is denied
Treat the denial as useful policy feedback. Check that the tool
belongs in this task, that it is registered, and that the relevant
capability is explicit. Do not switch to permissions_full()
merely to make the error disappear.
The run stops at a limit
Inspect result$stop_reason and
result$usage. Tighten the task first; increase only the
specific limit whose additional work is justified.
Where next?
| If you want to… | Continue with… |
|---|---|
| Choose or build tools | vignette("tools", package = "deputy") |
| Design permission policy | vignette("permissions", package = "deputy") |
| Observe and intervene in runs | vignette("hooks", package = "deputy") |
| Stream an Agent into an app | vignette("example-shiny-chat", package = "deputy") |
| Delegate to specialist Agents | vignette("multi-agent", package = "deputy") |
| Require typed model output | vignette("structured-output", package = "deputy") |
| Build a concrete workflow | vignette("example-data-analysis", package = "deputy") |
The durable pattern is the same in every host: make the workspace, tools, permissions, and limits explicit; run one bounded job; then inspect the typed result before granting more authority.