原始内容
Pi Container Sandbox
A Pi extension that runs file and shell tools inside an isolated container workspace while keeping Pi, authentication, model calls, sessions, and the TUI on the host.
The extension routes read, write, edit, bash, ls, find, grep, and user ! commands into the container. The host repository's .git directory is never mounted into it.
[!IMPORTANT] A container reduces accidental host modification; it is not an absolute security boundary. Review SECURITY.md before using untrusted images or repositories.
Contents
- Features
- Requirements
- Installation
- Uninstallation
- Quick start
- How it works
- Targets
- Checkpoints
- Workspace and lifecycle
- Host-untracked files
- Docker networking
- Dependencies and package caches
- Review agent
- Rebasing
- Commands
- Configuration
- CLI flags
- Tests
- Security and limitations
Features
- Routes Pi's standard file and shell tools into Apple Container or Docker.
- Keeps host credentials, sessions, model calls, and the repository
.gitdirectory outside the container. - Creates validated Git checkpoints on an isolated sandbox branch or fast-forwards the current branch.
- Supports automatic checkpoints after a model turn, agent run, or fully settled cycle.
- Preserves or removes containers according to configurable lifecycle policy.
- Supports protected host-untracked overlays, lockfile-based dependency bootstrap, and shared package caches.
- Provides a read-only review agent and guarded in-container rebasing.
- Supports disabled, dynamic, or fixed Docker port publication on host loopback.
Requirements
- Pi with Git package support.
- A supported runtime: Apple
containerordocker. - A Git repository with an existing
HEADcommit. - A clean tracked host worktree. Untracked files are handled separately by
hostUntrackedFiles. - A container image with the tools needed by the project. Core sandbox operations require common shell utilities, Bash, Git, and tar; sandbox
greprequires ripgrep.
Installation
Pi packages execute with the installing user's full permissions. Review index.ts and SECURITY.md before installation.
Install from npm
Install globally for the current user:
pi install npm:@kjrjay/pi-sandbox
Install only for the current trusted project:
pi install -l npm:@kjrjay/pi-sandbox
Project-local installation writes to .pi/settings.json, which can be shared with the project. Pi installs the package after the project is trusted.
To pin a specific release:
pi install npm:@kjrjay/pi-sandbox@0.1.0
Pinned npm versions remain fixed during package updates. Install a newer version explicitly when upgrading a pinned installation.
Install from GitHub
The latest source can also be installed directly from GitHub:
pi install git:github.com/kjrjay/pi-sandbox
To pin a Git installation, append a tag or full commit hash.
Try without installing
Load the npm package temporarily for one Pi invocation:
pi -e npm:@kjrjay/pi-sandbox
Update
Update an unpinned installation:
pi update --extensions
Use pi list to inspect installed package sources. Restart Pi after installation, or run /reload in an existing session.
Manual installation
Alternatively, clone the repository into Pi's global extension directory:
git clone https://github.com/kjrjay/pi-sandbox.git \
~/.pi/agent/extensions/container-sandbox
Pi auto-discovers index.ts from that directory. Pull updates with Git and run /reload afterward.
Uninstallation
Before uninstalling, checkpoint or preserve any named target branches and generated refs/heads/pi-sandbox-* branches, then stop active sandbox operations.
Remove a global npm installation:
pi remove npm:@kjrjay/pi-sandbox
For a project-local npm installation:
pi remove -l npm:@kjrjay/pi-sandbox
If the package was installed from GitHub, use its Git source instead:
pi remove git:github.com/kjrjay/pi-sandbox
Use pi list when unsure which source is recorded. Restart Pi or run /reload after removal.
For a manual clone, remove its directory:
rm -rf ~/.pi/agent/extensions/container-sandbox
Package removal intentionally does not delete configuration, caches, containers, or Git refs. Remove these only after reviewing anything that may contain recoverable work.
Optional configuration and cache cleanup:
rm -f ~/.pi/agent/extensions/pi-sandbox.json
rm -rf ~/.pi/agent/cache/container-sandbox
Docker containers created by the extension can be listed by their managed label:
docker ps -a --filter label=pi.container-sandbox.managed=true
Generated sandbox branches and recovery refs can be reviewed in each affected repository with:
git for-each-ref refs/heads/pi-sandbox- refs/pi-sandbox-recovery/
Named sandbox:<branch> targets use the exact branch name and should be reviewed separately.
Do not delete containers, branches, or refs until their work is committed, exported, or no longer needed.
Quick start
After installation, add a partial global configuration at:
~/.pi/agent/extensions/pi-sandbox.json
Example Docker configuration:
{
"runtime": "docker",
"image": "pi-tool-sandbox:latest",
"target": "sandbox",
"checkpointFrequency": "agent",
"dockerPortMode": "dynamic",
"dockerPortRange": "8000-8010",
"lifecycle": "stopped"
}
A trusted project can override global values with:
<project>/.pi/pi-sandbox.json
Start Pi from a clean Git worktree. The container is created lazily before the first agent run. Use /sandbox status to inspect the effective configuration and active container.
How it works
- The extension validates the repository and runtime configuration.
- It creates a local clone from the authoritative host Git ref and copies that workspace into the container.
- Pi's normal file and shell tools execute against the container copy.
- At the configured boundary, the extension stages and validates sandbox changes.
- It transfers Git objects through a temporary bundle and publishes them to the selected target.
Runtime command capture is bounded on the host. Bash output streams through Pi without retaining an unbounded duplicate buffer.
Targets
sandbox — default
Each session checkpoints into an isolated generated local host branch:
refs/heads/pi-sandbox-<session-hash>
Use target: "sandbox:<branch>" for an exact stable branch and container identity. For example, sandbox:feat/feature-b publishes to refs/heads/feat/feature-b. Named branches are limited to 96 characters and must be valid Git branch names. Container names use a bounded pi-<repo>-<readable-branch>-<hash> form derived from the repository path and exact target ref. The checked-out host branch, index, and worktree remain unchanged. The extension refuses to update a target branch while it is checked out in any host worktree. A host lock in the repository's common Git directory prevents concurrent Pi sessions from owning the same target branch and container. The lock is reserved during session startup, before the container is created; an idle open Pi session therefore continues to own its configured target.
current
Validated checkpoints fast-forward the branch that was checked out when the sandbox started, updating the host index and worktree. This mode:
- requires an attached local branch;
- uses a per-worktree lock to prevent concurrent sessions;
- refuses publication if the host branch or tracked worktree changes unexpectedly.
If the branch advances, run /sandbox rebase. The rebase occurs inside the container before the host branch is fast-forwarded.
Checkpoints
checkpointFrequency controls automatic checkpoint timing:
| Value | Boundary |
|---|---|
turn |
After each internal model response and its tool calls. This is the default and gives the finest-grained recovery. |
agent |
After one low-level agent tool loop, normally one checkpoint per submitted prompt. |
settled |
After retries, compaction retries, and queued continuations have finished. |
At each checkpoint, the extension:
- Stages the sandbox tree against the authoritative baseline.
- Generates a commit message with the active model, with a timestamp fallback.
- Reconstructs one commit using the expected parent and host Git author identity.
- Transfers it through a temporary Git bundle and host ref.
- Validates object integrity, parentage, ancestry, and commit count.
- Advances the sandbox branch with compare-and-swap or guarded-fast-forwards the current branch.
Copied host-untracked overlay files are removed from the checkpoint index. /sandbox checkpoint runs the same operation manually.
Workspace and lifecycle
New workspaces come from a local host clone and are shallow by default. No remote clone or remote Git authentication is required.
Existing containers are reused only after validating their image, package-cache mount, managed metadata, repository identity, configuration, and authoritative Git ref. Clean stale workspaces are reseeded. Mismatched or recoverable containers are preserved and rejected rather than silently overwritten.
lifecycle controls shutdown behavior:
| Value | Behavior |
|---|---|
remove |
Checkpoint and remove the container. This is the default. |
stopped |
Checkpoint and stop the container for reuse. |
running |
Checkpoint and leave the container running. |
/sandbox stop checkpoints and then stops stopped or running containers, or removes remove containers. A later sandbox tool call can start the sandbox again.
Host-untracked files
hostUntrackedFiles controls files already untracked on the host:
| Value | Behavior |
|---|---|
ignore |
Do not copy them. This is the default. Host files are not deleted or modified. |
copy |
Copy non-ignored files into the container as a checkpoint-protected overlay. |
Discovery follows .gitignore and optional .pi-sandboxignore rules. On reuse, previous and current overlay manifests are reconciled so files removed or newly ignored on the host are removed from the container before checkpointing.
Copied files remain readable by sandbox processes. Exclude credentials and other sensitive files.
Docker networking
When runtime is docker, dockerPortMode controls publication of every container port in dockerPortRange:
| Mode | Behavior |
|---|---|
disabled |
Publish no ports. |
dynamic |
Map each container port to a Docker-assigned host port on 127.0.0.1. This is the default and avoids port conflicts. |
fixed |
Map each container port to the same numbered host port on 127.0.0.1. Startup fails when a selected host port is occupied. |
Examples:
Dynamic: 127.0.0.1:49153 -> container:8000
Fixed: 127.0.0.1:8000 -> container:8000
In both publishing modes, the server inside the container must bind to 0.0.0.0 on a configured container port. Resolved mappings appear in the startup notification, /sandbox status, and the agent system prompt. They remain stable while a stopped container is preserved.
dockerPortRange accepts one port or an ascending range of at most 100 ports. Set hostGateway to a hostname such as host.docker.internal to add Docker's host-gateway mapping. Host-gateway access is disabled by default because it lets sandbox processes connect to services on the Docker host.
These options do not affect Apple Container.
Dependencies and package caches
installDeps supports:
| Value | Behavior |
|---|---|
never |
Do not install dependencies. This is the default. |
auto |
Run fixed lockfile-based bootstrap logic for npm, pnpm, Yarn, Bun, uv, or pip. |
New containers bind-mount the extension-owned cache directory:
~/.pi/agent/cache/container-sandbox/packages:/var/cache/pi-packages
Download caches are shared across containers. Project node_modules and virtual environments remain inside each workspace. With installDeps: "auto", bootstrap runs whenever a new or reused sandbox workspace starts.
Review agent
/sandbox review creates an isolated, in-memory reviewer session. With no instructions, it reviews the latest sandbox commit. Text after -- can select a scope and provide guidance:
/sandbox review -- check the last 3 commits for security issues
/sandbox review -- commit hash 21e81311a
The reviewer has a separate context and read-only tools routed into the existing container. It cannot use host filesystem or shell tools. The completed report records the resolved commits, instructions, tool activity, diff stat, model, and usage metadata without modifying files or starting a fix loop.
Requested commits must exist in the sandbox clone. Set gitCloneDepth: 0 when reviews need older host history.
Rebasing
/sandbox rebase checkpoints pending work and rebases it inside the container onto the latest commit of the original host branch. A clean result is bundled to the host, validated, and published to the selected target.
If conflicts occur, automatic checkpoints pause. Resolve conflicts inside the container, stage them, and continue the rebase. Use /sandbox rebase-status to inspect progress or /sandbox rebase-abort to restore the pre-rebase sandbox state.
Non-fast-forward rewrites of the host base branch are rejected. If the host branch advances during final current publication, the rebased work is preserved under refs/pi-sandbox-recovery/*.
Commands
| Command | Description |
|---|---|
/sandbox |
Show status. |
/sandbox status |
Show effective configuration and runtime status. |
/sandbox attach <host-image-path> [-- message] |
Attach an explicitly selected host screenshot to a new user message. |
/sandbox checkpoint |
Create and publish a checkpoint immediately. |
/sandbox review [-- instructions] |
Review sandbox commits with the read-only reviewer. |
/sandbox rebase |
Rebase sandbox work onto the latest host base. |
/sandbox rebase-status |
Show pending rebase state. |
/sandbox rebase-abort |
Abort the pending rebase. |
/sandbox stop |
Checkpoint and stop or remove the container. |
Mutating commands wait for the active agent to become idle.
/sandbox attach is intended for screenshots that exist only on the host. It reads the explicitly named host image, uses Pi's normal image normalization and resizing, and sends it directly as multimodal message content; it does not copy the image into the container. For example:
/sandbox attach ~/Desktop/screenshot.png -- Explain the error shown here
Without -- message, the default prompt is Please inspect this screenshot. If the agent is busy, the image and message are queued as a follow-up.
Configuration
Configuration precedence, from lowest to highest:
- Built-in defaults.
- Global
~/.pi/agent/extensions/pi-sandbox.json. - Trusted project
<project>/.pi/pi-sandbox.json. - CLI flags.
Both JSON files may contain only the values they override. Unknown keys and invalid values are rejected.
| Option | Default | Description |
|---|---|---|
runtime |
"docker" |
container or docker. |
image |
"pi-tool-sandbox:latest" |
Container image used by the sandbox. |
dockerPortMode |
"dynamic" |
disabled, dynamic, or fixed. |
dockerPortRange |
"8000-8010" |
Docker container port or ascending range, up to 100 ports. |
hostGateway |
"" |
Optional Docker hostname mapped to host-gateway. |
target |
"sandbox" |
sandbox, exact sandbox:<branch> (up to 96 characters), or current. |
checkpointFrequency |
"turn" |
turn, agent, or settled. |
hostUntrackedFiles |
"ignore" |
ignore or copy. |
gitCloneDepth |
1 |
Local clone depth; 0 uses full history. |
gitCommitCoAuthor |
"Pi <pi@localhost>" |
Optional commit-message co-author trailer. |
gitCommitAiMaxDiffBytes |
20000 |
Maximum staged diff sent for commit-message generation. |
installDeps |
"never" |
never or auto. |
lifecycle |
"remove" |
remove, stopped, or running. |
passEnv |
[] |
Allowlist of host environment-variable names exposed to sandbox commands. |
review.model |
"" |
Reviewer model; empty uses the current session model. |
review.thinkingLevel |
"high" |
Reviewer thinking level. |
review.maxDiffBytes |
100000 |
Maximum patch included directly in the review prompt. |
Example complete configuration:
{
"runtime": "docker",
"image": "pi-tool-sandbox:latest",
"dockerPortMode": "dynamic",
"dockerPortRange": "8000-8010",
"hostGateway": "",
"target": "sandbox",
"checkpointFrequency": "agent",
"hostUntrackedFiles": "ignore",
"gitCloneDepth": 1,
"gitCommitCoAuthor": "Pi <pi@localhost>",
"gitCommitAiMaxDiffBytes": 20000,
"installDeps": "never",
"lifecycle": "stopped",
"passEnv": [],
"review": {
"model": "",
"thinkingLevel": "high",
"maxDiffBytes": 100000
}
}
Commit author and committer identity come from host Git configuration. The extension does not interpret Mise or other host tool-version configuration; required tools must exist in the image.
CLI flags
| Flag | Description |
|---|---|
--no-sandbox |
Disable sandbox routing for this run. Standard tools execute on the host. |
--sandbox-runtime <container|docker> |
Override the runtime. |
--sandbox-image <image> |
Override the image. |
--sandbox-docker-port-mode <disabled|dynamic|fixed> |
Override Docker port publication mode. |
--sandbox-docker-port-range <port|start-end> |
Override Docker container ports. |
--sandbox-target <sandbox|sandbox:branch|current> |
Override the checkpoint target and optional exact branch. |
--sandbox-checkpoint-frequency <turn|agent|settled> |
Override checkpoint boundary. |
--sandbox-git-clone-depth <n> |
Override local clone depth. |
--sandbox-install-deps <auto|never> |
Override dependency bootstrap. |
--sandbox-lifecycle <remove|stopped|running> |
Override shutdown behavior. |
--sandbox-env FOO,BAR |
Override the environment-variable allowlist. |
Tests
Run the permanent control-plane suite from the extension directory:
./tests/run.sh
The suite uses isolated Pi state, starts no containers, calls no models, and is designed to finish within 30 seconds. Container and model workflows currently require explicit manual integration checks; see tests/README.md.
Security and limitations
There are no model-callable host shell or host Git tools. Host commands use fixed internal operations. The current target has a larger trust boundary because materializing commits can invoke repository-configured Git LFS or checkout filters.
The runtime, image, networking, shared writable package cache, forwarded environment variables, Git parsers, and other Pi extensions remain outside this extension's isolation guarantee.
See:
- SECURITY.md for the security model and remaining caveats.
- feature-list.md for planned improvements and known gaps.