I recently created a new R package called AgenticR that unifies the R console and an AI agent into a single prompt. You type R code and natural language in the same place. The router decides which path to take — code executes directly, natural language goes to an LLM agent that generates and executes R code in your current session. Unlike coding agents that put you inside their product and treat the console as a secondary channel, AgenticR makes normal code execution the default and the AI agent the fallback — no mode switch needed.
There are two entry points in every data analysis workflow today: the console for executing code, and the chat window for talking to AI. Despite the rise of vibe coding, the shell and console aren’t going away. I still write a lot of commands directly in the console even though I also ask a coding agent to run some for me. One reason: direct execution is faster and more transparent. Another: I often need exploration before I know what prompt to give. A third: I want to confirm the agent is doing things correctly.
I started wondering: do these two use cases really need to stay separate?
Users are deeply adapted to typing natural language in an agent and code in a console. They expect the console to behave with precision, in microseconds. They also expect that asking something ambiguous like “what’s the pattern in this data” will take a few seconds and produce a thoughtful answer. Merging the two means respecting both expectations.
If you route everything through the LLM, simple commands get punished with unnecessary latency.
The agent also needs to handle a wide spectrum of tasks. Sometimes you want to fix a single typo in a line of code. Sometimes you want to refactor an entire analysis script. The same prompt should handle both.
The Design: Conservative, Then Helpful
AgenticR’s router is deliberately conservative. When in doubt, assume the input is R code and try to execute it directly.
The classifier works in layers. First it checks for strong R syntax — <-, |>, %>%, function(, library(. If any of these are present, the input is R code, period. No ambiguity. Next it runs R’s own parser. If parse() succeeds, the expression is valid R — execute it immediately with the same latency as the standard console. Only when the parser fails and the input contains strong natural language signals (what, how, make, plot, explain, show me) does the router send the input to the LLM agent.
The philosophy is: false positives (R code incorrectly routed to LLM) hurt users more than false negatives (natural language executed as broken R code). If R code being routed to LLM, user will see the high latency. In the case when natural language being routed to R, the R execution fails and the repair path still routes it to the LLM anyway. The user almost feel nothings since their expectation on latency is much higher for natural langague input. It is much than R returning “invalid syntax” error.
This also means the agent has a complete picture. Since all input goes through one place, the conversation context includes both the direct R code you ran and the natural language queries you made. The LLM knows you just did head(df) and summary(df) before you asked “what should I investigate next?” That context is lost in the two-window world. In addition, once the user finished their early exploration stage in AgenticR, they don’t need to switch. AgenticR can also do later state executions with all the context.
The Chemistry
Here’s where it gets interesting. When a single prompt handles both code and natural language, the boundary between them starts to dissolve. You can write code using any grammar you like in a R console. See the examples below.
# Natural syntax data pipeline — agent translates to valid R
> mtcars | group by wt | mean(x) for x in (carb, gear, am)
Reasoning: ...
→ library(dplyr)
mtcars |>
group_by(wt) |>
summarise(across(c(carb, gear, am), mean))
Many people are used to use “|” to do command chaining so let’s use it. Some people are used to use “|” as condition operation in statistics. Let’s use both
# First | is a data pipe, second | is mathematical "given"
> mtcars | E(carb | gear=4) using linear regression
→ predict(lm(carb ~ gear, data = mtcars), newdata = data.frame(gear = 4))
# E[carb | gear=4] = 3.0
Here are some more real examples of code that shouldn’t parse but AgenticR handles correctly:
# ggplot described in pseudo-code
> mtcars, ggplot + point(wt, mpg) + curve(x1, y1, xend=x2, yend=y2, color="curve")
→ ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point() +
geom_curve(data = df, aes(x = x1, y = y1, xend = x2, yend = y2))
# Latex notation for regression
> y = x_1 * beta_1 + x_2 * beta_2 + epsilon where x_1 = cyl, x_2 = gear, y = am
→ summary(lm(am ~ cyl + gear, data = mtcars))
# β₁ = -0.0485, β₂ = 0.4792
I always have difficulty to remember all the grammar of ggplot2 and dplyr. Whether something goes inside or outside of aes(), which function name does what. Their grammar is elegant and this is a problem of mine. But with AgenticR, everyone can invent their own grammar that are easy for them, and the grammar can be sloppy, incomplete, or unconventional — the agent fills the gaps.
The Agent
While the design prioritizes normal R code execution, the AI agent underneath is a full-fledged coding agent with memory, context caching, token streaming, and skill/MCP support. It handles the full spectrum — from fixing a single typo to multi-step analysis spanning several turns.
execute_r_code— run R code in your session, one logical step at a timeget_dataframe_info— inspect columns, types, dimensions, first 5 rowssearch_variables— find variables by name patternget_function_help— look up R documentation for any functiongrep_search— search files with ripgrepfile_edit/file_write— modify files on diskinstall_package— request package installation with user confirmation
The agent loops: inspect the data, generate code, execute it, check the output, fix errors, explain results, continue to the next step. All in the current R session, with access to every variable you’ve created.
User input
│
▼
Router
│
├── R code ──► Execute directly ──► Done
│
└── NL / broken code
│
▼
LLM Agent ◄──────────────────────────┐
│ │
├── tool call ──► R executes ───┤
│ (code, data │
│ inspect, etc) │
│ │
└── done ──► Explain & summarize
Getting Started
install.packages("remotes")
remotes::install_github("panlanfeng/AgenticR")
library(agenticr)
agentic_setup() # interactive wizard: choose provider, enter key
agentic() # start the unified prompt
Fifteen LLM providers supported out of the box. Auto-detection from environment variables. You are welcome to send pull request or bug reports.
AgenticR is open source under the MIT license. github.com/panlanfeng/AgenticR