Getting started

gptr logo

gptr runs language model agents in your current R session. Agents can analyze objects already in memory and create R objects that you can inspect, reuse or modify. A dataset around 4 GB can stay in R while the model receives size-limited descriptions, previews and tool output that may include data values. You can revise one analysis step without exporting and reloading the dataset for each turn. The peter() function supports interactive chat at the console and programmatic calls in scripts, loops and if statements.

The Execution model explains what reaches the provider and what still costs memory and time. Choose a model in Language models; the examples below run offline with a scripted fake provider.

A first call

library(gptr)
session = peter("Fit mpg from wt in mtcars. Save the model as fit.", mtcars)
session |> peter("Use fit to predict mpg and plot the predictions.")

Peter writes and runs R through its tools. The pipe adds work to the same session, so the next turn can reuse fit.

Try it offline

This fake provider supplies a fixed sequence of tool calls and replies. It does not infer code from the prompt.

library(gptr)

fake = gptr_fake_provider(list(
  list(tool = "r", input = list(
    code = "fit = lm(mpg ~ wt, data = mtcars)\ngptr_return(coef(fit))"
  )),
  "Each 1000 lb of weight lowers mpg by about 5.3.",
  list(tool = "r", input = list(
    code = "predicted = predict(fit)\ngptr_return(head(predicted))"
  )),
  "The first six predictions are ready."
))

work = new.env()
session = peter("Fit mpg on wt in mtcars and return the coefficients.", mtcars,
  model = fake, envir = work, mode = auto)
session$text
#> [1] "Each 1000 lb of weight lowers mpg by about 5.3."
session$value
#> (Intercept)          wt
#>   37.285126   -5.344472

Prompts are always quoted strings. Identifiers such as the mode can be bare names, the way library(gptr) needs no quotes: mode = auto and mode = "auto" are the same. The auto mode lets ordinary agent actions run without asking, while retaining the critical and secret guards. This guide needs it because nobody is there to answer. At the console the default mode, manual, asks before changes unless a permission rule already allows them.

session$text is the final answer, and session$value is the value the tool selected with gptr_return(). The object the agent created is in work, like any object you created yourself:

ls(work)
#> [1] "fit"
coef(work$fit)
#> (Intercept)          wt
#>   37.285126   -5.344472

Reuse that fitted object in the next turn. No data file or repeated fit is needed:

session |> peter("Reuse fit to predict mpg and return the first six predictions.")
#> The first six predictions are ready.
#> idle . fake/fake-1 . 2 turns . 6.8k tokens . $0.0000 . s79b1e170d5
session$value
#>         Mazda RX4     Mazda RX4 Wag        Datsun 710    Hornet 4 Drive
#>          23.28261          21.91977          24.88595          20.10265
#> Hornet Sportabout           Valiant
#>          18.90014          18.79325
head(work$predicted)
#>         Mazda RX4     Mazda RX4 Wag        Datsun 710    Hornet 4 Drive
#>          23.28261          21.91977          24.88595          20.10265
#> Hornet Sportabout           Valiant
#>          18.90014          18.79325

peter() returned a session object. It holds the conversation, the model, the status and the token usage:

session$status
#> [1] "idle"
session$turns
#> [1] 2
gptr_usage(session, by = "model")
#>   group        requests  input  output  cache_read  cache_write  cost
#> 1 fake/fake-1  4         6797   51      0           0            0
#>
#> # 1 row
#> 4 requests, 6.8k tokens in, $0.0000

The pipe steers one session

Piping a session into peter() adds a turn to the same session: the same history, the same model and the same workspace. This continuation does not duplicate the dataset; ordinary R code can still allocate copies when it modifies objects or computes new results.

fake2 = gptr_fake_provider(list(
  list(tool = "r", input = list(
    code = "cyl_mpg = tapply(mtcars$mpg, mtcars$cyl, mean)"
  )),
  "The mean fuel economy per number of cylinders is stored in `cyl_mpg`.",
  "Four-cylinder cars are the most economical, at about 26.7 miles per gallon.",
  "Four."
))

session = peter("Compute the mean mpg for each number of cylinders.",
  model = fake2, envir = work, mode = auto)
session |> peter("Which group is the most economical?")
#> Four-cylinder cars are the most economical, at about 26.7 miles per gallon.
#> idle . fake/fake-1 . 2 turns . 5.1k tokens . $0.0000 . s4b1031a3d1
session$turns
#> [1] 2
session$text
#> [1] "Four-cylinder cars are the most economical, at about 26.7 miles per gallon."

A branch is always explicit. gptr_fork() copies the conversation into a new session whose workspace looks up objects in the parent environment and keeps ordinary assignments local:

branch = gptr_fork(session)
branch |> peter("Answer in one word.")
#> Four.
#> idle . fake/fake-1 . 3 turns . 1.7k tokens . $0.0000 . s101a409664
c(original = session$turns, branch = branch$turns)
#> original   branch
#>        2        3

The overlay separates ordinary bindings, rather than making a full data snapshot. Reference objects and arbitrary side effects require their usual care; see Execution model.

Stopping instead of guessing

In manual mode a change needs approval unless a permission rule applies (an allow rule never approves a critical action). When nobody can answer (an Rscript job, a knitted report), the run stops with a classed condition that says how to allow the action:

careful = gptr_fake_provider(list(
  list(tool = "r", input = list(code = "mtcars$kpl = mtcars$mpg * 0.425")),
  "Added the column kpl."
))
res = tryCatch(
  peter("Add fuel economy in km per liter to mtcars.", model = careful,
        envir = new.env(), mode = manual),
  gptr_error_permission = function(e) e
)
class(res)[1]
#> [1] "gptr_error_permission"
res$session$status
#> [1] "blocked"

mode = plan inspects and proposes while preserving the user workspace; level-1 R exploration can create intermediate objects in its scratch environment. mode = edits allows file edits inside the project, and gptr_permissions() adds rules such as "r(level<=1)" that allow low-risk code without a question.

At the console

Called without a prompt in an interactive console, peter() opens a chat. Piping a session into it continues that session, and /exit returns the session. Inside chat, /help lists commands, @name attaches an object and !code runs your own R immediately. Interactive console explains input syntax, permissions and interrupts.

peter()
session |> peter()

Connecting a real model

Choose one route: an API key, an existing CLI login or a local Ollama model. Put your provider’s key in a private .env file and load it with gptr_env(). Sign in to Claude Code or Codex outside R; CLI sign-in is separate from API credentials. Start Ollama separately and install a chat model that supports tool calls; gptr does not install Ollama or download models. Language models gives setup examples for each route.

gptr_env("~/keys/.env")
gptr_providers()
gptr_models(provider = "openai")
gptr_config(model = "openai/gpt-6-sol", .scope = "user")

session = peter("Describe the mpg and wt columns.", mtcars)
session$text

The first interactive use of a remote provider asks you to acknowledge the data it receives; this also applies to context = "none", which retains project instructions and environment details. For unattended calls, review ?gptr_egress and save consent first:

gptr_config(egress = utils::modifyList(gptr_config()$egress, list(openai = "ack")),
            .scope = "user")

Offline scripted providers and local loopback routes need no acknowledgment, with Ollama exempt only while local-only inference is on. For persistent storage, call gptr_init("/path/to/project") to create a .gptr/ workspace and review its separate trust decision. Without a project workspace, session storage is temporary. Defaults and storage policy are covered in Configuration.

Where to go next