Script as history

gptr logo

gptr can keep the code an agent executed beside the prompt that requested it. In a designated R script, the code goes below the owning peter() statement between marker comments; in a report or notebook, it becomes a chunk or cell. A fresh recorded block can run again as R without another model request. Replaying still executes its R code, with the effects and input dependencies that code has.

The history keeps code and bounded output, rather than archiving every input object. Provide those objects in the replay environment; working on data already loaded in R does not require an intermediate dataset file. See Execution model for object lifetimes and the separate costs of checkpoints, workers and snapshots.

The examples below are recipes for your configured model. Run recording and file-writing steps deliberately in a temporary project.

Program in natural language, then use ordinary R

Write a prompt for each step. A pipe continues the same session, so the next prompt can use the objects created by the previous one. Keep prompts on separate lines and put short notes where they help explain the next step.

library(gptr)
cars = datasets::mtcars

session = peter("Fit mpg ~ wt in cars with stats::lm(); store fit.") |>
  # Reuse the fitted model.
  peter("Use fit to predict mpg for every row of cars; store predicted.") |>
  peter("Summarize observed mpg minus predicted; store residual_summary.")

graphics::plot(cars$mpg, predicted)
session |> peter("Explain the largest residuals.")

Prompts and ordinary R can alternate. A pipe adds turns to the same mutable session; assigning the session to another name does not make a branch. Use gptr_fork() for a branch.

Calls inside functions

peter() is an R function. Give it an environment when results should outlive a helper call:

rank_cars = function(data, envir) {
  peter("Find the three most fuel-efficient cars in data; store top.",
        data = force(data), envir = envir)
}
work = list2env(list(data = mtcars))
session = rank_cars(work$data, work)
work$top
summary(session)

data = force(data) supplies model context; it does not create an R binding in work. Here work$data is prepared before the call, so the R tool can read it. Objects attached by name must be visible from the evaluation environment. summary(session) reads recorded turns without a model call; it is not a narrative or an executable workflow.

A call nested inside a function, loop or another expression does not own a document block. Running that code again may make another model request. Keep recordable prompts at top level and reusable R calculations in functions. System 1 cache hits do not make an arbitrary loop into a recorded workflow.

Recording a designated R script

gptr_doc(path) binds a document and supplies explicit consent to write blocks into it. gptr_doc() reports the binding; gptr_doc(FALSE) removes it. It does not create a workflow from past conversation turns.

Save this as analysis.R in a temporary directory:

library(gptr)
cars = datasets::mtcars
session = peter("Find the three most fuel-efficient cars in cars; store top.")
top

Then run it from a separate driver or at the console:

gptr_doc("analysis.R")
work = new.env()
gptr_source("analysis.R", replay = "auto", envir = work)
gptr_blocks("analysis.R")
gptr_doc(FALSE)

A missing block makes a model request, and the executed code is recorded below the call. The header identifies the block, model, date and prompt hash; the body hash identifies its recorded code. ## Decision: retains a model-supplied note, and #> retains bounded output. A note is a model explanation, not proof that the code is correct.

The document must be writable, and recording needs consent. gptr_doc(path) supplies that consent explicitly; record = "off" prevents automatic recording into discovered documents. To opt out for one call, including a call in a bound document, pass .opts = list(record = FALSE). Pass it to each call in a pipe chain that should not record. record = TRUE in .opts keeps the usual consent checks; it does not supply consent. Without a binding, source discovery can find a call in a document. At an interactive console, agreeing to transcript recording creates a script under .gptr/transcripts/; an explicit gptr_doc(path, format = "transcript") selects an R transcript target.

Main-session recorded code runs as ordinary document code on replay. Keep its input objects in the document’s evaluation environment, using peter()’s default caller environment. An arbitrary custom envir is not restored or applied to the following recorded chunk. For a function that works in a separate environment, opt out with .opts = list(record = FALSE) and keep its executable steps explicitly in the function. Forks and team children have their own recorded overlay wrappers.

Inspecting and replaying

gptr_doc("analysis.R")
listing = gptr_blocks("analysis.R")
listing[, c("id", "status", "model", "date", "tokens", "cost")]

gptr_source("analysis.R", replay = "replay", envir = new.env())
gptr_cache()
gptr_doc(FALSE)

gptr_source() executes .R files only, expression by expression. It can regenerate a stale block and skip its old code in the same run, which base source() cannot do. Its return value is a block listing with the action taken for each block. gptr_blocks() reads R, R Markdown, Quarto and Jupyter formats; pending writes can be recovered before listing.

Replay mode Behavior
"auto" Replay fresh blocks; run missing or stale calls through the selected model.
"replay" Never call a model; a missing or stale record signals gptr_error_not_recorded.
"live" Ask the model again and rewrite recorded blocks when writing is allowed.
"record" Regenerate stale blocks; fresh blocks replay.

A call’s replay = argument wins over process options, the GPTR_REPLAY environment variable and settings. In any R front end, use gptr_source("analysis.R", replay = "replay") to run a recorded script without model calls. From a Unix shell:

GPTR_REPLAY=replay Rscript --vanilla analysis.R

This checks that the requested calls can use their records; it does not certify the analysis, freeze package versions, capture external datasets or make file/network operations in the recorded R code safe to repeat. Verify data paths, dependencies, outputs and side effects.

gptr_blocks() marks a block fresh, stale, user-edited or undone. Changed prompts or interpolated values can invalidate replay; the listing alone cannot evaluate runtime interpolation. Hand-edited code is recognized by its changed hash. Undone code stays visible as inert comments. Keep the associated answer cache if the replayed calls need their text or values; the code block and the cache are different parts of the record.

gptr_cache() reports System 1 and System 2 answer caches, temporary spill files and pending sidecars. gptr_cache("prune") removes entries no longer in use or beyond their retention window; gptr_cache("clear", "s2") removes System 2 answers. Clearing answers can change what is available during replay. Pending document sidecars are not deleted by cache cleanup; apply them with gptr_doc(path, sync = TRUE).

R Markdown, Quarto and Jupyter

In .Rmd and .qmd, a recorded block becomes a chunk labeled gptr-<id> after its calling chunk. Bind the report, run it with its normal renderer, then inspect the recorded blocks:

gptr_doc("analysis.Rmd")
rmarkdown::render("analysis.Rmd")
gptr_blocks("analysis.Rmd")
gptr_doc(FALSE)

Use quarto render analysis.qmd for .qmd; gptr_source() is not a renderer for either format. Arrange the document’s setup to load gptr, prepare its input data and select a model or replay mode. Plain R chunks still execute when the report is rendered.

For Jupyter, bind the saved .ipynb path in the R kernel. Blocks become agent cells. Writes are queued while the notebook is open to avoid competing with the notebook editor; after closing it, apply the pending blocks:

gptr_doc("analysis.ipynb")
# Run the notebook's `peter()` calls in its R kernel, then save and close the notebook.
gptr_doc("analysis.ipynb", sync = TRUE)
gptr_blocks("analysis.ipynb")
gptr_doc(FALSE)

A notebook edited since a pending block was queued may need conflict resolution. Reopening or listing a file is not proof that pending blocks were applied successfully.

Exporting a recorded script

gptr_export() reads a synchronized .R history and writes a new .R file without running it or asking a model. Its default format = "r" turns peter() calls into comments and keeps the recorded R executable. With format = "peter", calls remain active and the previous recorded code becomes comments, so that code is not executed a second time. Ordinary user code and comments retain their source order. Choose a new .R destination in an existing directory, different from the source; existing paths are never replaced. Export also refuses a source that changes during publication.

For example, save the following as analysis.R. With a configured model and the usual permission checks, its first three prompts steer one session; the fourth starts another. The ordinary plot runs between them, using objects in the same R environment.

cars = datasets::mtcars

session = gptr::peter("Fit mpg ~ wt in cars with stats::lm(); store fit.") |>
  # Reuse the fitted model.
  gptr::peter("Use fit to predict mpg for every row of cars; store predicted.") |>
  gptr::peter("Summarize observed mpg minus predicted; store residual_summary.")

# User code and this comment stay in the exported source.
graphics::plot(cars$mpg, predicted, xlab = "Observed mpg", ylab = "Predicted mpg")
graphics::abline(a = 0, b = 1)

session = gptr::peter("Check predicted length and finite values; store checks.")

Record it deliberately, inspect the code and results, then synchronize before exporting:

work = new.env(parent = globalenv())
gptr_doc("analysis.R")
gptr_source("analysis.R", replay = "record", envir = work)
work$residual_summary
work$checks
gptr_doc("analysis.R", sync = TRUE)

gptr_export("analysis.R", "analysis-raw.R")
gptr_export("analysis.R", "analysis-peter.R", format = "peter")
gptr_doc(FALSE)

The export operation creates no model request. Running the format = "peter" export can create new requests; running the raw export executes its recorded R and ordinary user code. Review those effects, then validate the raw result in a fresh environment with the intended inputs. For this example, input preparation is already in the source. For an analysis that uses a large object loaded earlier, supply that object in the new environment; exporting does not serialize the dataset or make an intermediate data file.

In the format = "peter" output, recorded Decision and Steer notes and bounded #> output follow each operand of this separate-line pipeline. Other layouts keep feedback after the complete statement. Notes and output are comments, not assertions that the analysis is correct. Each block keeps at most twelve output lines and eighty characters per output line.

Export requires fresh completed records and no pending sidecar. It refuses computed or nested prompts, mixed peter()/ordinary pipelines, shared-line statements, interpolated-input hashes, missing or edited blocks and secret-marked records. Raw export also refuses later ordinary code that depends on a removed session handle, such as session$value; put needed results in ordinary R objects, or retain the format = "peter" output. Earlier unrecorded console input is not reconstructed. Preserved user code can still require gptr or other packages, read or write files, or call the network. Export is a source transformation, not verified replay.

Asking Peter to summarize a completed workflow

Choose the deliverable explicitly. A history document, an ordinary executable script and a narrative summary have different roles. gptr_export() transforms an explicitly recorded R document; it cannot recover arbitrary conversation turns into a complete workflow.

With your configured model, continue session to request a narrative:

session |> peter(
  "Summarize completed steps, decisions, inputs, outputs and open issues.
Separate proposed work. Name saved files only when verified.",
  mode = plan
)
session$text

The answer is model-generated text and adds a turn to session. Check its claims against the workspace, files and history before treating it as a record. gptr_usage(session) shows the request’s usage.

For a history document, designate the target before recording top-level prompts, include ordinary R setup and checks, and inspect the blocks and replay behavior. To assemble one from earlier work, ask the model to draft it and review the draft; earlier unrecorded turns are not automatically recovered into validated blocks.

For an ordinary R script, request a separate, explicit implementation:

session |> peter(
  "Write ordinary R for the verified analysis, including inputs and output saves.
Do not call Peter. Identify missing inputs or steps instead of inventing them.",
  mode = plan
)
session$text

This returns model-generated output, not an exported or validated script. Save only reviewed code, then run it in a fresh environment with the intended inputs and verify its outputs.

See System 1 decisions for typed decisions and Teams and background work for orchestration and SDK control.