A native R interface to agentic coding assistants. Talks directly to local command line interfaces ('GitHub Copilot', 'Anthropic' 'Claude Code') over standard input/output and to a local 'vscode.lm' bridge inside 'Positron', providing multi-model chat, tool calling, and in-session R evaluation. Supports a single stateful hal() session as well as disposable hal_ask() / hal_do() pipeline verbs, plot capture so models can see graphics, verified data transforms, and user-defined tools.

A coding agent for R. Not a chatbot – an agent that reads your code, searches your codebase, edits files, and runs commands. Zero API keys, three pluggable backends:
vscode.lm via the
bundled hal-bridge extension. No CLI install, no extra auth beyond
your Positron Copilot sign-in, and eval_r round-trips through R
directly without an MCP subprocess. The lowest-friction path.hal_quota(), plugs into the Claude Code skills /
hooks / MCP ecosystem.If you’re in Positron, do nothing – hal_setup() installs the bridge
from inst/extdata/ in one call and you’re off. If you’re elsewhere,
hal_setup() installs the Copilot CLI. Switch any time with
hal_configure(backend = "...").
Bullet features:
eval_r + custom tools on vscodeuse_env = TRUE lets the agent read live R
objects via eval_rhal.md persists context across sessionshal_do() replaces itself in your script with
generated codehal_excel() turns an .xlsx into a
verified tidyverse script, checked cell-for-cell against Excel’s own
cached valueseval_r are captured and sent to the
model as images (vscode + claude backends), so it can see and iterate
on your actual chartshal_do() reports row/column/NA deltas
after every transform and warns when output looks suspicious# install.packages("pak")
pak::pak("ArcLite-Red/hal")
library(hal)
hal_setup() # auto-picks the right backend for your host
hal_setup() walks you through the appropriate path. In Positron it
installs the hal-bridge extension from the VSIX bundled with hal (no
download, no GitHub auth); elsewhere it installs the Copilot CLI.
Pick a different backend explicitly if you want:
hal_configure(backend = "vscode") # Positron + hal-bridge extension
hal_configure(backend = "copilot") # GitHub Copilot CLI (ACP)
hal_configure(backend = "claude") # Anthropic Claude Code CLI
Verify (one traffic-light report, ends with the next step if anything is missing):
hal_status()
#> -- hal status ------------------------------------------------------
#> i hal 0.1.4 | backend: "vscode" (auto: Positron detected)
#> v hal-bridge 0.1.4 responding on port 51234.
#> i No active session (one starts on your first hal() call).
#> v Ready. Try: hal("Hello!")
Multi-turn conversation with a coding agent. Session persists across calls.
hal("What are the top 3 dplyr verbs and when would I use each?")
hal("Show me a filter example")
hal("Now group_by and summarise")
Pipe any object into hal_ask(). Your data flows through unchanged.
mtcars |>
hal_ask("What patterns stand out in fuel efficiency? 3 bullets.")
hal_do() generates R code, executes it, and returns the result – then
verifies the transform and reports what structurally changed:
mtcars |>
hal_do("group by cylinder count, summarize mean mpg and mean hp")
#> i hal_do: 32 -> 3 rows | -9 cols (...) | +2 cols (mean_mpg, mean_hp)
The full report lives at attr(result, "hal_verify"); suspicious output
(identical to input, 0 rows) warns. Report-only – it never changes your
data.
In RStudio or Positron, hal_do() replaces itself in your editor with
the generated code:
hal_excel() reads an .xlsx, treats the non-formula columns as data,
and translates each formula column into a tidyverse expression – then
verifies every translation against the values Excel itself cached,
row for row. Columns that match go into a live mutate() pipeline;
anything that doesn’t is emitted as a commented stub to review. The
result is a runnable R script that replaces the workbook:
hal_excel("sales_model.xlsx")
#> v revenue: verified (120/120 rows match Excel)
#> v margin: verified (120/120 rows match Excel)
#>
#> data <- openxlsx2::read_xlsx("sales_model.xlsx", sheet = "Sheet1", ...)
#> result <- data |>
#> dplyr::mutate(
#> revenue = units * unit_price,
#> margin = (revenue - cost) / revenue
#> )
code <- hal_excel("sales_model.xlsx")
attr(code, "hal_excel") # per-column verification report
writeLines(code, "sales_model.R")
If you run it from an open script, it replaces the hal_excel() call
with the generated code – the spreadsheet-to-script migration is one
line.
On the vscode and claude backends, plots drawn by eval_r are captured
and sent to the model as images – it critiques what the chart actually
looks like, not what the code suggests it might:
df <- mtcars
hal("Draw a scatter of mpg vs wt and describe the relationship you see")
#> i hal: plot captured for the model.
# ... the model references the actual visual: clusters, outliers, curvature
hal("Make it publication-ready: labels, theme, annotate the outliers")
# It sees each iteration and refines against the rendered result.
Returned ggplot objects are printed to your device too, so everything
shows up in your plots pane as usual. Disable with
hal_configure(plot_vision = FALSE).
# Give the agent access to live objects in your R session
df <- mtcars
hal("Which rows in df have above-median mpg?", use_env = TRUE)
# Mid-pipe transform with retry on failure
iris |>
hal_do("z-score each numeric column, ignoring Species", .retries = 2)
# Switch models on the fly (Copilot; Claude resets via hal_reset())
hal("Summarize this codebase", model = "claude-haiku-4.5")
hal("Now review it for edge cases", model = "claude-opus-5")
# Register custom tools
hal_register_tool(
fun = function(ticker) paste("$142.50 for", ticker),
name = "stock_price",
description = "Get current stock price",
types = list(ticker = "string")
)
hal("What's the stock price of AAPL?")
# Track usage and (on Claude) the 5-hour quota window
hal_usage()
hal_quota()
# R6 API for multiple sessions, Shiny, or full control
chat <- hal_chat(model = "claude-sonnet-5", echo = "all")
chat$chat("Read DESCRIPTION and list the dependencies")
chat$switch_model("gpt-4.1")
chat$chat("Are any of those dependencies unnecessary?")
R session
|
v
hal (R6 + S3) one API, pluggable transport
|
+------------+------------+
| | |
v v v
vscode Copilot Claude
localhost ACP -p / resume
HTTP server per-turn
| | |
v v v
hal-bridge GitHub Anthropic
vscode.lm Copilot Claude
(Positron) 17 models 3 models
All three transports converge on the same hal_response / hal_turn /
hal_tool_call S3 objects, so your code doesn’t care which backend you
pick. vscode speaks HTTP to the localhost bridge; Copilot and Claude
speak NDJSON / stream-json over stdio.
hal’s Copilot path uses the ACP transport (not the HTTP proxy). Multi-turn Claude via HTTP has a known format-translation bug; via ACP it works correctly.
vignette("getting-started") – setup, configuration, backends, full
walkthroughvignette("backends") – vscode / Copilot / Claude trade-offs, costs,
quotavignette("agent-tools") – built-in tools, eval_r, permissions,
custom MCP tools