Permission modes control the overall behavior of tool permission checking:
"standard"- Check each tool against the configured capabilities"plan"- Allow annotated read-only tools within configured capabilities plus human approval prompts"readonly"- Deny all write/execute tools"full"- Allow all tools (dangerous, use with caution)
Tool Annotations
Permissions use tool annotations (from ellmer::tool_annotations()) to
determine tool behavior. Available annotations:
read_only_hint (logical, default: FALSE)
Indicates the tool only reads data and doesn't modify state. Annotations are
descriptive metadata, not an authority grant: "readonly" mode allows known
Deputy read tools or explicit allowlist entries, subject to destructive and
open-world capability checks. Examples: tool_read_file, tool_list_files,
tool_search.
destructive_hint (logical, default: TRUE)
Indicates the tool may cause destructive/irreversible changes.
Tools with destructive_hint = TRUE require explicit permission.
Examples: tool_write_file, tool_delete_file, tool_run_bash
open_world_hint (logical, default: TRUE)
Indicates the tool may interact with external systems.
Used for network calls, package installation, etc.
Examples: tool_web_search, tool_install_package
idempotent_hint (logical, default: FALSE)
Indicates repeated calls produce the same result. This annotation alone does not authorize automatic retries.
Missing annotations remain absent on the tool. For custom tools, permission
checks assume modification, possible destruction, external access, and no
idempotence unless stated otherwise. If read_only_hint = TRUE, an omitted
destructive annotation is ignored; an explicit TRUE still denies read-only
use. Native tools continue to require their named capabilities. A custom
permission callback can explicitly authorize a tool in standard mode;
full mode bypasses annotation checks but still honors tool gating.
Creating Tools with Annotations
# 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
)
)