gptr 1.0.0 is a complete rewrite: it runs language model agents in an existing R session, where generated code can inspect and modify objects already in memory.
get_response() and
dataframe_to_text(), is removed with no aliases.get_response(), use
session = peter("your prompt", model = "openai/gpt-6-sol"),
which returns a session object; its answer is session$text,
and session |> peter("next prompt") continues the
conversation. The key comes from OPENAI_API_KEY (or a
.env file read with gptr_env()) instead of the
api_key argument, instructions that went into
system_specification go into the prompt or the project
instructions file .gptr/vignette.Rmd, and answers stream to
the console when a person is present instead of through
print_response.dataframe_to_text(), pass the data frame to
peter() as context, for example
peter("Which variables are correlated?", mtcars): gptr
describes the object compactly and the model computes on it in your
session.model argument
or with gptr_config().context = "none". That setting still omits workspace and
attached-object descriptions, while retaining project instructions and
environment details. Unattended runs need saved provider consent;
offline providers and local loopback routes remain exempt, with Ollama
exempt only while its local-only inference is on.inst/COPYRIGHTS.peter() is the single entry point. Without a prompt it
opens a chat in the console; with a prompt it runs the agent loop and
returns the session. The pipe steers one session:
peter("...") |> peter("...") adds turns to the same
object. gptr_fork() is the only way to branch. Sessions are
saved in the project’s .gptr/ workspace (without one, until
R exits) and resumed with gptr_sessions(),
gptr_resume() and gptr_last().gptr_providers(check_login = TRUE) shows CLI sign-in
status.peter$grep(), peter$find() and
peter$ls(); shell, Python, SQL and knitr engines are
reached through peter$sh(), peter$py(),
peter$sql() and peter$knit().api = "bedrock-converse") and cost accounting where the
model supports them. The Claude plan (through the claude
command-line tool, experimental) and the ChatGPT plan (through
codex) are supported without keys. See
gptr_providers() and gptr_models().peter() returns logical, choice or score vectors with their
probabilities (gptr_prob()) that work inside
if, for and while, one element
per input. A generative model can emulate them; emulated answers are
marked as uncalibrated.manual (default), edits,
auto and plan, rules with
gptr_permissions(), and an advisory risk classifier,
gptr_risk(). Deny and ask rules also match read-only shell
commands, such as r(sh:cat *). A permission question
without a person present stops the run with a classed condition. In
auto and yolo mode, system(),
system2(), shell() and
shell.exec() with a command the code computes
(system2()’s env included) are refused unless
an allow rule such as r(fn:system) names the function; the
model is pointed to peter$sh(), whose command the rules
check when it runs.auto mode too, before a
Sys.setenv(), Sys.unsetenv(),
withr::local_envvar() or withr::with_envvar()
call, or an os.environ write in peter$py() or
peter$knit("python", code) code, that sets or unsets
R_USER_CONFIG_DIR, XDG_CONFIG_HOME,
HOME, APPDATA or USERPROFILE (the
last two matter on Windows; gptr asks on every system), which move its
configuration folder or your home, as it does for a provider key. It
also asks before such an R call with a name it cannot read, and before
readRenviron() and dotenv::load_dot_env();
?gptr_security lists the changes it does not read.peter() call in .R,
.Rmd, .qmd and .ipynb documents,
also what a call ran before it failed, and replays without model calls
(gptr_doc(), gptr_source(),
gptr_blocks(), gptr_cache());
gptr_source(model =) asks a recorded analysis again with
another model. A console transcript also records a run that was aborted
or stopped by an error, marked ## Run: <status>.@file and the notes of earlier !
lines, as #< lines) and, after /model or
/mode, the model and mode a turn ran with, so
gptr_source(replay = "live") sends each turn again with
what it was sent with and asks it on that model and in that mode (a turn
that names none is asked with those its session has from the turns
before, else with the model and mode settings
of the session that re-asks); code the model wrote is never sent again
as your context or your steer. A ! line that uses an object
of an undone turn is turned off (#~) and back on after
/redo, so source() does not stop there, and
/retry sends a turn’s ! notes again when that
turn is the latest message sent. gptr_export() turns such a
transcript into an ordinary R script.gptr_init() creates a .gptr/ project
workspace with the project instructions file
.gptr/vignette.Rmd; gptr_config() and
gptr_trust() manage settings and trust.gptr_rewind(),
gptr_checkpoints() and /undo restore objects,
files and the conversation.agents =,
parallel =, gptr_parallel()), across
providers.gptr_register(), gptr_spec() and the spec
constructors).gptr_tailor() makes a harness: a function you call
exactly like peter() that carries peter()
arguments you fix (such as mode, model,
tools or extensions), your own hard rules, and
coaching: a note added to peter’s instructions, skills peter can open, a
review that asks peter to check its work before it stops, and rules. It
calls no model. Share a harness with saveRDS() and load one
with gptr_tailor(readRDS(file)); dir = writes
it out as files to read.gptr_coach() improves a harness’s coaching by running
your task: the harness does the task, two coaches propose new coaching,
each proposal does the task, a third coach combines them, and the result
does the task once more. Code keeps the new coaching only when that last
run ended normally, delivered what the first one did, took no riskier
kind of action and passed your own check. k (1
to 5) asks for more coaching: with strategy = "parallel" it
runs k of each side by side, and when more than one result
passes, a blind judge picks one; with "sequential" it runs
k rounds, each building on the one before. Every session
runs unattended in its own R process with copies of your objects. It
works on API keys and on Claude Code and Codex plans (on a plan, at most
two sessions at once, and it stops at a plan limit). A call such as
h = gptr_coach(...) in a script or notebook is recorded as
the coaching it produced and replays without a model, a key or any
process.gptr_spec("skill", name, description = ..., text = ...)
needs no SKILL.md file. A skill registered for a call, by
an extension or a harness, stays in that session.gptr_tailor(peter, rules = list(never = ..., require = ...))
and in coaching: a never rule, such as
r(category:install), refuses every matching call in every
mode, sub-agents included; a require rule names a call that
must happen before peter finishes, such as saving results with
write.csv(), and gptr reminds peter at most twice and
reports a requirement still missing. Rules read the code before it runs,
so a name or path the code computes can slip past them.extensions = also applies to every session the run starts
(a peter() call from the agent’s code, team members,
fan-out children and workers). A nested peter$sh(),
peter$write() or peter$edit() whose command or
path the agent’s code computes meets every deny and ask rule, and the
strictest answer wins.claude-cli or
codex route signals gptr_error_plan_limit,
with the time the limit resets when the CLI says it..opts$returns works on Claude Code
(claude-cli): gptr asks for the reply’s shape at the end of
the prompt and checks the reply itself.gptr_key attribute (a
string) loads once per session, so passing it again when you continue
the session does not load it twice.gptr_mcp(),
gptr_mcp_add()) that exposes server tools as R functions,
and gptr_mcp_serve(), which serves the live session to
other agents. A project’s own servers, also those in the project files
of Claude Code, Codex, Cursor, VS Code and Pi, start only in a trusted
project.gptr_artifacts()).gptr_login() signs in to providers and MCP servers,
gptr_env() loads .env files (including the
jev-key alias) and gptr_scrub() cleans files
that captured a key. Model code that reads a registered key, or hands
one to a child process through the env of
peter$sh() or peter$script(), asks in
auto mode too.gptr_usage(),
gptr_prompt(), gptr_describe() and the
budget argument.background = TRUE) are
experimental.?gptr_security and ?gptr_egress for
what gptr does on your behalf and what it sends to model providers, and
?gptr_options for every option.gptr_message_billing_env condition with the removed names,
never their values. Handlers use muffleMessage; the notice
respects gptr.quiet. Actual billing refusals remain
errors.yolo mode (mode = "yolo",
/mode yolo) runs actions without approval, including
package installs, writes anywhere and gptr’s own configuration
functions. A person is still asked before deleting files outside the
project or protected files, moving the working directory out of the
project, rm(list = ls()), options(gptr.*) and
sending a secret over the network; a one-time notice says so.
setwd() or withr::local_dir() to a directory
the code computes, such as setwd(here::here(d)), counts as
such a move, in auto too; here::here() of
literal parts and a computed directory R moves back from, as with
withr::with_dir(), do not. The classifier reads code; it is
not a sandbox. In every mode, model code that would end or hang the R
session, such as q() or readline(), is refused
without a question.type_ahead swaps the two); Up takes back a message not yet
delivered, and /command and !code lines wait
for the next prompt. A paste is one draft, however busy the session, in
a terminal that supports bracketed paste. A question waits for typing to
pause and keeps the draft. On the CLI routes a steer is sent after the
CLI’s answer. In RStudio and R.app (Esc) and Positron (Ctrl-C) the pause
menu steers.peter> prompt is sent as one
message, pasted fences and """ blocks are read at once, a
block pasted at an approval or a choice is kept in the console history,
not read as the answer, and one pasted at the pause menu’s steer or
follow-up question is the whole message; a last pasted line without a
newline waits for Enter at the next prompt, so a two-line paste without
a final newline answers a question with its first line. Elsewhere a
block pasted at a prompt or a question arrives one line at a time, as
typed lines do.@, !, !!, /
and peter$, never in plain text, and never offers a name
that holds a line break or another control character.gptr_parallel() print one
line when each sub-agent starts and ends.peter() call made by the agent’s own code without
envir = works in an overlay of the caller’s environment;
its objects are in res$envir.object.size(), code the agent runs keeps no parse
data, and values returned by sub-agents stay small./model and model = accept a provider name
alone, such as claude-cli or codex-cli (its
first model); /model checks the name before the first
prompt. A model declared without tool calls says once that the session’s
tools are not sent.api = "bedrock-converse" signs requests with Signature
Version 4 in R. Region and endpoint are provider-record data
(compat = list(region = ...), base_url), so a
SigV4 gateway is configuration; see ?gptr_provider.
AWS_ACCESS_KEY_ID is handled as a secret./cost lists each agent and one total that
keeps the known cost when a request’s cost is unknown, and
/context says the CLI keeps its own transcript./undo and gptr_rewind()
restore what the agent’s served calls changed, and a rewind or
compaction starts a fresh CLI conversation. Automatic compaction does
not run there, since the CLI keeps its own context. /undo
reports each item once, by its outcome.par(mfrow = c(1, 2)) keep
every panel and knitr chunks keep their figures; plots of calls served
to the CLI routes are drawn on your device..x;
parallel = with .opts$context = "none" is an
error. A peter() call made by the agent’s code without
model = uses the running session’s model, and the egress
question names the sub-agent that asks. Assignments in a sub-agent’s
overlay are not reported as overwriting your objects.p = normalizePath("../x.txt"); file.remove(p).a and p only
when it has a rule to suggest (typed anyway, they say why, such as an
ask rule that asks every time); result lines and errors wrap in the
margin, and a failed r call still lists its objects;
sub-agent failures give their reason and count, and repeated nested
prompts are numbered; command output and long prompts stay in the
margin; Codex commands show without their shell wrapper and Codex
messages without a held last word; a cut code block says so inside its
fence; wide pipe tables stream verbatim; /model and
/status name the model behind a CLI’s
default.gptr_models(refresh = TRUE) reads each models.dev price
tier once, so models with a 200k-token price tier resolve and a 272k
tier gains no premium from 200k tokens.model = "gemini" (Google’s newest Gemini
Pro) asks for gptr_models(refresh = TRUE) or a
provider/id.gptr_models(refresh = TRUE), for
example MiniMax-M3 and gpt-oss-120b) are not refused as too small: gptr
reserves and requests at most half the window for output. A
compact_at too small for the preset is named as the cause,
and the hint reads tools = "minimal".gptr_models(provider = "ollama", refresh = TRUE) as the
remedy, not the internal model_prepare().peter(model = "ollama/<model>") asks the Ollama
server about that model before its first request in each R session, as
System 1 calls do, so a saved default Ollama model works in a new
session.provider/id on a route the catalog lists no
models for yet (before a refresh, for example OpenRouter, Groq, xAI or
Azure) says that gptr_models(refresh = TRUE) downloads them
from models.dev.gptr_provider() and gptr_spec("model")
accept a model’s prices as a list of price records as well
as a data frame, as gptr_config(providers =) does, and
check the table when the spec is built, naming the field.azure/
before AZURE_OPENAI_ENDPOINT is set, names the variable
that sets it (or providers$<id>$base_url in
gptr_config()).models set with
gptr_config(.scope = "session") or
options(gptr.providers =), replaces the one in a lower
layer (for example the user file); named lists merge.gptr_config() warns when it saves a setting to a
project that is not trusted and so does not apply it (for example
providers or model), naming
gptr_trust() and .scope = "session"; settings
that only tighten, such as mode = plan or deny
rules, stay silent (a permissions write names only its
allow rules).gptr_config(model = opuss) (or
small_model, system1) warns when the name is
not a model gptr knows in this session, as the console’s
/model does, with the reason and how to undo it; the value
is still saved, since a provider registered later or a catalog refresh
can make it valid.gptr_init() writes no mode or
record into .gptr/settings.json, so a
user-level mode = auto or record = "auto"
applies in every initialized project. In a project made by a development
build from GitHub, delete "mode": "manual" and
"record": "ask" from that file to let your user settings
apply.mode applies, so
gptr_config(mode = auto) in a trusted project takes effect;
an untrusted project only tightens the mode, and context
and record only tighten in any project. A project cannot
select yolo: gptr_config(mode = yolo) at
project scope is refused, naming .scope = "user" and
"session".api = "bedrock-converse" send
thinking levels (.opts = list(thinking =) or a
:high suffix) to a Claude model whose record declares
capabilities = list(adaptive_thinking = TRUE, effort = TRUE)
(Claude 4.6 and later; see ?gptr_provider). Any other
model, an undeclared Claude record included, runs without thinking and
says so once..opts = list(thinking = "ultra"), is reported as such (“the
string given is not one of them”, with “Did you mean” when it differs
from a choice only in case)."opus:off" runs at low, and
thinking = "max" on a model whose levels end at
high runs at high. ?gptr_provider
documents thinking_levels; a reasoning model declared
without it gets off to high.model, a tool for tools): after
opus = gptr_fake_provider(...), model = opus
uses the alias and suggests !!opus.choices, levels, threshold,
min_confidence and uncertain given to a
conversational model, also with agents = or
parallel =, are refused, naming the argument. Use a
decision model such as jev; after
gptr_config(system1 = "emulate:<ref>"),
model = jev runs it through a chat model. A decision model
refuses agents = and parallel =: it answers
every input in one call.uncertain = "stop" error names the uncertain inputs
(the first ten) and carries their positions ($index),
$names and $confidence, the confidence the
band tests, every answer ($value) and
$prob.as.data.frame() of a System 1 score names its
probability columns by level (p_<level>), as
gptr_prob(x, "probabilities") and choices do.choices given as a factor, or
.opts = list(output = "factor"), return a plain factor
without probabilities, as ?peter, ?gptr_prob
and the System 1 vignette say. When elements fail, the factor keeps
attr(x, "meta")$errors, which the failure warning points
to.get_response() and
dataframe_to_text() for the OpenAI ‘ChatGPT’ API (released
2025-04-05, after 0.5.0 and 0.6.0 in May 2024).