hal connects to a coding agent with built-in tools for reading code, editing files, searching your codebase, and running commands. You can also register your own R functions as tools.
Built-in tools
Always available — no registration needed:
| Tool | What it does |
|---|---|
| view / Read | Read file contents |
| grep / Grep | Search file contents |
| glob / Glob | Find files by pattern |
| edit / Edit | Edit an existing file |
| create / Write | Create a new file |
| bash / Bash | Run shell commands |
The agent decides when to use them — you just describe what you want:
eval_r — the R bridge
hal registers eval_r in every session so the agent can
run R code in your live session, not a subprocess copy:
df <- mtcars
hal("Which rows in df have mpg above the median?", use_env = TRUE)use_env = TRUE injects names + types from your
environment into the prompt; the agent reads actual values via
eval_r. The default (use_env = NULL)
auto-detects when prompt tokens match env objects.
Plot vision
When eval_r code draws a plot, hal captures it as a PNG
and attaches it to the tool result as an image — the model sees the
rendered chart, not just the code that made it:
df <- mtcars
hal("Plot mpg vs wt and tell me what stands out")
#> i hal: plot captured for the model.| Backend | Plot vision |
|---|---|
| vscode | Yes (bundled hal-bridge >= 0.1.4; if the selected model rejects images, the bridge falls back to text automatically) |
| claude | Yes (MCP image content blocks) |
| copilot | No — text-only until the CLI’s image forwarding is verified |
Details worth knowing:
- Returned
ggplot/latticeobjects are printed to your device first, so they appear in your plots pane as usual — and a ggplot that fails to render reports the error to the model instead of failing silently. - One image per eval: the final page (a
par(mfrow = ...)grid is one page and is captured whole). - Plots written to file devices your code opens itself
(
png(),pdf()) are not echoed. - Disable globally with
hal_configure(plot_vision = FALSE).
Register your own tools
Turn any R function into a tool:
hal_register_tool(
fun = function(city) paste("Sunny, 72F in", city),
name = "get_weather",
description = "Get current weather for a city",
types = list(city = "string")
)
hal("What's the weather in Austin?")Register tools before the first hal() /
$chat() call — they’re passed to the CLI at startup.
Bulk registration:
hal_register_package_tools("dplyr")
hal_register_tool_specs(winston::timelog_tool_specs())ellmer ToolDef objects are accepted directly:
chat <- hal_chat()
chat$register_tool(my_ellmer_tool)Permissions, in brief
Read tools auto-allow. Writes and shell commands trigger a permission request. The default policy auto-allows everything; switch to auto-deny for read-only behavior:
hal_configure(permission_policy = "auto-deny")For custom logic (logging, interactive approval, selective allow),
pass a function. See ?hal_configure for the full
reference.
Next
-
?hal_register_tool— full tool builder reference -
?hal_configure— governance settings (policies, denylist, timeout)
