---
title: "Script as history"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Script as history}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
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](execution-model.html) 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.
``` r
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:
``` r
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:
``` r
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:
``` r
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
``` r
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:
```sh
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-` after its calling
chunk. Bind the report, run it with its normal renderer, then inspect the recorded blocks:
``` r
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:
``` r
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.
``` r
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:
``` r
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:
``` r
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:
``` r
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](system-one.html) for typed decisions and
[Teams and background work](teams-and-background.html) for orchestration and SDK control.