PermissionMode lists the modes a Permissions policy can use. In every
mode, tool_denylist and tool_allowlist are checked first, and the
approval prompt tool (permission_prompt_tool_name) is then allowed
without further checks.
"standard": checks built-in tools against the capability flags (file_read,file_write,bash,r_code,web,install_packages) and custom tools against their annotations."readonly": allows the built-in file-reading tools, the web tools whenweb = TRUE, the agent's own delegation tools and tools ontool_allowlist. It denies writes, code execution, destructive tools and, unlessweb = TRUE, open-world tools."plan": allows only tools annotated as read-only, plus the approval prompt tool and the agent's own delegation tools. Open-world tools also needweb = TRUE."full": allows every call. Capability flags and annotations are not checked.
An agent's own delegation tools are a LeadAgent's delegate_to_agent
tool and the tools that call its retained agents, from delegation_tool()
and $retain_agent_graph(). Another tool doesn't qualify by using the same
name. Each tool call a subagent or retained agent makes is also checked
against the policy of the agent that delegated to it, so in read-only or
plan mode that agent's delegates are held to the same mode. A LeadAgent's
subagents also can't use a less strict mode than their lead.
If the policy has a can_use_tool callback, it is called in every mode for
each call the rest of the policy allows. It can deny the call or pause it
for approval, but it can't allow a call the policy denies.
A denied call can still be allowed by a PermissionRequest hook (see HookEvent).
Tool annotations
Custom tools are checked through their annotations, set with
ellmer::tool_annotations(). A missing annotation takes a cautious
default:
read_only_hint(defaultFALSE): the tool only reads data. Plan mode allows only these tools.destructive_hint(defaultTRUE, orFALSEwhenread_only_hint = TRUE): the tool may make irreversible changes. Destructive tools are denied in plan and readonly modes, and in standard mode when bothfile_writeandbashare off.open_world_hint(defaultTRUE): the tool may reach external systems. Open-world tools are denied unlessweb = TRUE, except in full mode.idempotent_hint(defaultFALSE): repeated calls have the same effect. Permission checks don't use it.
So in standard mode an unannotated custom tool needs web = TRUE, and plan
and readonly modes deny it. Built-in tools such as write_file and
run_bash are checked against their capability flag instead.
Annotating tools
Set annotations when you create a tool:
# Read-only tool
tool_search <- ellmer::tool(
fun = function(pattern) grep(pattern, files),
name = "search",
description = "Search for pattern",
arguments = list(pattern = ellmer::type_string("Search pattern")),
annotations = ellmer::tool_annotations(
read_only_hint = TRUE,
destructive_hint = FALSE,
open_world_hint = FALSE
)
)
# Destructive tool
tool_delete <- ellmer::tool(
fun = function(path) unlink(path),
name = "delete",
description = "Delete a file",
arguments = list(path = ellmer::type_string("File path")),
annotations = ellmer::tool_annotations(
read_only_hint = FALSE,
destructive_hint = TRUE,
open_world_hint = FALSE
)
)