insideR (insider) is an R package-call x-ray for inspectable R code.
It opens one R package call, shows what actually runs, captures the inputs, extracts the relevant R logic where possible, and creates a self-verifying replay script that a human or coding agent can inspect, modify, and compare.
It is best suited to package behavior that is implemented in R or can be traced through R-visible source. It still works when that logic calls base R or other R packages, but it does not decompile compiled internals or fully model opaque non-R side effects.
The goal is not to replace R packages with loose scripts. The goal is to make package-based work less black-box while keeping the discipline of packages: namespaces, documentation, tests, maintainers, and reusable APIs.
Why this exists
R packages are the right way to build reusable statistical and data-science software. But in applied work, package calls can become opaque:
- Which method actually ran?
- Which internal helpers were used?
- Which inputs and hidden state affected the result?
- Which parts are pure R and which parts depend on external packages, compiled code, or other opaque boundaries?
- Where can I safely customize behavior without editing the installed package?
- What changed after customization?
- What should a coding agent read before touching the repo?
insideR is designed for that gap.
It turns this:
predict(fit, newdata = recent_data)into a small, inspectable replay artifact:
predict_replay/
replay.R # extracted functions + input loading + verification
data/
fit.rds # captured inputs
recent_data.rds
original_result.rds # original package result
insider_manifest.rds # machine-readable unpack metadata
replay.R records provenance, includes a static security/risk scan, reruns the extracted logic, and verifies the replayed result against the stored original result.
The core idea
Most projects use only a small slice of a package.
insideR focuses on that slice:
call -> resolved method -> internal helpers -> external dependencies -> result
The intended workflow is:
trace -> resolve dependencies -> slice -> replay -> customize -> compare -> handoff
That workflow serves two audiences:
- Humans get comfort through transparency: they can see what the package call did before trusting or changing it.
- Agents get compact context: they can see the relevant files, functions, dependencies, risks, and validation steps before editing anything.
What works now
The current package already supports the first practical layer: explain, unpack, replay, and graph.
Explain a call
insider::explain_call(
predict(fit, newdata = recent_data)
)explain_call() reports:
- the original call;
- the resolved function or S3 method;
- internal and external calls;
- replayability status;
- compiled-code boundaries;
- static security/risk findings.
Unpack a call
insider::unpack_call(
predict(fit, newdata = recent_data),
output_dir = "predict_replay"
)unpack_call() creates a replay folder with captured inputs, extracted functions, the original result, a manifest, and a self-verifying replay.R.
Run:
source("predict_replay/replay.R")The replay script verifies that the replayed output still matches the original result. When the source package is installed, it can also compare against a live package call.
Build a package graph
g <- insider::build_graph("mgcv")
g <- insider::build_graph("~/repos/myPackage")
insider::graph_search(g, "predict")
insider::graph_node(g, "build_features")
insider::graph_callers(g, "build_features")
insider::graph_callees(g, "predict.my_model")build_graph() indexes source directories or installed packages into a queryable R code graph: functions, classes, methods, imports, and call edges. Source-directory graphs include file and line-number information.
Current API
Implemented:
explain_call(expr, max_depth = 5)
unpack_call(expr, output_dir, max_depth = 5, overwrite = FALSE)
build_graph(x, cache = TRUE, refresh = FALSE)
graph_search(graph, pattern, kind = NULL)
graph_node(graph, name)
graph_callers(graph, name)
graph_callees(graph, name, internal_only = FALSE)Planned next:
trace_call(expr)
resolve_dependencies(expr)
slice_call(expr, output_dir)
build_context(target, task, format = c("md", "json"))
compare_call(path)
propose_change(path)Detailed planning and prioritization are tracked in Obsidian project memory, not in the package repository.
Human use case
A human analyst can ask:
I trust this package enough to use it, but I need to understand and customize one behavior. Where is that behavior, and can I change it safely?
insideR should help them:
- identify the actual method/function used;
- inspect the internal helper path;
- replay the call outside the installed package;
- make a local customization;
- compare the changed result to the original;
- decide whether the change is a one-off local variation or a package-worthy improvement.
Agent use case
A coding agent should not blindly search an entire package repo for one requested change.
insideR should give the agent a compact task packet:
insider_context.md
insider_context.json
dependencies.json
call_graph.json
modification_points.md
validation_plan.md
Those artifacts should answer:
- which functions matter for this task;
- which files define them;
- which functions call or are called by them;
- which dependencies are internal, external, compiled, or unresolved;
- where modification is safest;
- what validation commands should be run.
This makes insideR a bridge between R package internals and agent-assisted maintenance.
What insideR is not
insideR is not:
- a replacement for R packages;
- a replacement for Git;
- a promise that every R call can become package-free code;
- a full debugger;
- a full dependency manager;
- a full security scanner;
- a chat-style autonomous agent;
- a way to bypass maintainers and mutate production packages directly.
It is a transparency and replay layer around package calls.
Security and provenance
Transparency includes knowing what extracted code can do.
Every explain/unpack operation includes a static risk scan for calls worth reviewing, including:
- shell command execution;
- network access;
- dynamic code evaluation;
- file-system modification;
- environment/global-state mutation;
- compiled/internal entry points.
The generated replay script also records package provenance: package name, version, source repository where available, R version, and generation time.
The static scan is a review aid, not a malware detector.
Scope and limitations
Current support is strongest for:
- plain R functions;
- exported package functions;
- S3 methods;
- non-exported namespace helpers where possible;
- input capture using
.rdsfiles; - source extraction using R introspection;
- simple call-tree reporting;
- replay project generation;
- static package/source graphs.
Known hard cases should be reported honestly rather than hidden:
- compiled C/C++/Fortran internals;
- complex S4/R6 systems;
- heavy tidy-evaluation/non-standard evaluation;
- database/API calls;
- Shiny/reactive workflows;
- parallel execution;
- hidden global state;
- runtime dispatch inside internal call trees.
In practice, the package is strongest when the interesting behavior lives in ordinary R functions, S3 methods, and helper chains that R can inspect.
Beyond the unit test suite, this scope claim is checked against real, unmodified CRAN packages — currently httr and mgcv, chosen for different domains and failure modes. See notes/validation.md for the methodology and results.
Roadmap
The next product direction is package-slice extraction for human-agent workflows.
Priority sequence:
-
trace_call()— capture the actual runtime path for a concrete call. -
resolve_dependencies()— separate internal functions, external packages, captured objects, compiled boundaries, and unresolved references. -
slice_call()— create a lightweight call-specific mini-pipeline. - graph provenance/confidence/unresolved references.
-
explain_function()— explain a function even before a concrete call exists. -
build_context()— produce markdown/JSON context for coding agents. -
compare_call()and proposal bundles.
The detailed roadmap is maintained in Obsidian project memory so the package repository stays focused on user-facing docs and implementation.
Philosophy
The package should protect both sides:
- users get visibility, safety, and freedom to experiment;
- maintainers keep a clean package API and decide what becomes official;
- agents get compact context and validation plans before they edit.
The promotion path is:
local customization
-> repeated useful pattern
-> documented recipe
-> formal option
-> package feature
insideR exists to make that path visible, testable, and manageable.