A secure, Docker-based sandbox for running Pi Agent against your projects.
Pi runs inside a read-only container with all dangerous capabilities dropped — it can read and write your code, stage and commit git changes. It has full network access, but can only authenticate to the APIs you provide keys for via model configs and .env — no docker flags, no secrets in environment variables on your host.
- Isolates the agent in a container with a read-only filesystem and full network access (authenticates only to services you've provided keys for)
- Mounts your project at
/projects/<name>so the agent can read and write your code - Manages sessions per-project, preserving conversation history across runs
- Configures models via a simple JSON file supporting OpenAI-compatible, Anthropic, Google, and other APIs
- Injects API keys through
.env— no shell setup required
make build
make run-
Copy the example data:
cp -r data.example/. data/
The
data.example/directory contains everything you need — models config, system instructions, env var mappings, and more. Copy it todata/and edit to suit your setup. Files ending in.example.mdor.env.exampleare templates; rename them (e.g.,AGENTS.example.md→AGENTS.md,.env.example→.env) when you want to use them.Also update the Dockerfile: Copy
data.example/Dockerfiletodata/Dockerfileif you want to customize the container's base image or add dependencies. The example installs common tools (bash, git, ripgrep, python3, npm, etc.). Edit this file to add your custom packages or build steps. -
Configure models in
data/config/models.json. This file is mounted read-only into the container at/root/.pi/agent/models.json. See the Pi docs on custom models for supported APIs (OpenAI-compatible, Anthropic, Google, etc.). -
Optionally add system instructions in
data/config/APPEND_SYSTEM.md. This file is mounted read-only into the container at/root/.pi/agent/APPEND_SYSTEM.md. -
Set literal env vars in
data/.env(copy fromdata.example/.env.example). AddKEY=VALUElines for variables you want injected directly into the container:OPENAI_API_KEY=sk-proj-... ANTHROPIC_API_KEY=sk-ant-... -
Run the agent:
make run
Pass an API key via environment variable (e.g.,
ANTHROPIC_API_KEY), or via the--api-keyflag.
Run with custom arguments:
make run ARGS="--model llama3.1:8b"To avoid typing make run every time, add this function to your shell startup script (~/.bashrc, ~/.zshrc, etc.):
pibox() {
local target=run
if [ "$1" = "--edit" ]; then
target=edit
shift
fi
make -C ~/projects/pi-box "$target" REPO_DIR="$(pwd)" ARGS="$*"
}Then you can just run pibox from any project directory, optionally passing arguments:
pibox --model llama3.1:8bPass --edit as the first argument to run in edit mode (mounts data/config read-write so Pi can modify its own config):
pibox --edit install npm:pi-web-accessPlace skills in data/config/skills/ to make them available across all projects. Skills are Pi Agent Skills packages that provide specialized capabilities.
# Create a skill
cd data/config/skills
mkdir my-skill
cd my-skill
# Create SKILL.md with name and description
cat > SKILL.md << 'EOF'
---
name: my-skill
description: What this skill does
---
# My Skill
Usage instructions here...
EOFAvailable skills include:
- Pi Skills - Pre-built skills for common tasks
- Anthropic Skills - Document processing, web search
Create a custom skill by adding a SKILL.md file to data/config/skills/. Skills are loaded automatically and appear as /skill:name commands in the agent.
Place skills in data/projects/<project-safe-path>/skills/ (where <project-safe-path> is the sanitized repo path, e.g. Users--yourname--code--myproject). These are project-specific and take precedence over global skills.
Environment variables are injected into the container via data/.env — no shell config or docker flags needed. Copy data.example/.env.example to data/.env and edit to add your variables.
Copy data.example/.env.example to data/.env and add KEY=VALUE lines. The Makefile passes this file to Docker via --env-file, which loads all variables into the container's environment:
# data/.env
OPENAI_API_KEY=sk-proj-...
ANTHROPIC_API_KEY=sk-ant-...Use this when you want to store secrets directly in the project. The variables are injected as-is with their original names.
The container runs pi with a read-only filesystem, mounting:
| Host Path | Container Path | Mode | Purpose |
|---|---|---|---|
$(REPO_DIR) |
/projects/<project> |
rw | Your project files |
data/config/ |
/root/.pi/config-src |
ro | All Pi config: models, settings, skills, extensions, prompts, themes, keybindings, trust decisions |
data/projects/<project-safe-path>/ |
/root/.pi/agent/sessions |
rw | Pi agent sessions and per-project overrides |
In normal mode (make run), your config is mounted read-only at
/root/.pi/config-src, and /root/.pi/agent is an in-memory (tmpfs)
directory that the entrypoint populates from that source at startup. This is
required because Pi locks and writes settings.json while it runs (e.g. to load
installed packages/skills) — a purely read-only config directory makes Pi fall
back to "No packages installed". Because the working copy is in-memory, any
changes the agent makes to its own config are ephemeral and never reach the
host.
In edit mode (make edit), data/config is bind-mounted read-write
directly at /root/.pi/agent, so changes persist to the host. Use this to
install packages/skills or edit settings:
make edit ARGS="install npm:pi-web-access"Installed npm packages (like pi-web-access) live in
data/config/npm/node_modules/, and are registered in
data/config/settings.json under packages. Skills bundled inside a package
(declared via its pi.skills field) load from there — they do not appear in
data/config/skills/, which is only for skills you author yourself.
data.example/ # Copy to data/ and edit to get started
├── .env.example # Literal env vars template
├── config/
│ ├── models.json # Model & provider configuration
│ ├── settings.json # Global Pi settings
│ ├── AGENTS.example.md # Global context file template
│ ├── APPEND_SYSTEM.example.md # System prompt append template
│ ├── SYSTEM.example.md # System prompt replacement template
│ ├── keybindings.json # Custom keybindings
│ ├── trust.json # Project trust decisions
│ ├── skills/ # Global skills directory (placeholder)
│ ├── extensions/ # Global extensions
│ ├── prompts/ # Prompt templates
│ ├── themes/ # Themes
│ ├── npm/ # npm pi packages
│ └── git/ # Git pi packages
data/ # Your working copy — edit these files
├── .env # Literal env vars (copy from .env.example)
├── config/ # Same structure as data.example/config/
│ ├── models.json
│ ├── settings.json
│ ├── AGENTS.md
│ ├── APPEND_SYSTEM.md
│ ├── SYSTEM.md
│ ├── keybindings.json
│ ├── trust.json
│ ├── skills/
│ ├── extensions/
│ ├── prompts/
│ ├── themes/
│ ├── npm/
│ └── git/
└── projects/
└── <project-safe-path>/ # Per-project session directory (sanitized repo path)
├── sessions/ # JSONL session files
│ └── <project-path>/
│ └── <timestamp>_id.jsonl # Session history
├── models.json # Per-project model overrides
├── settings.json # Per-project settings overrides
├── APPEND_SYSTEM.md # Per-project system prompt append
├── auth.json # Per-project auth tokens
└── skills/ # Project-specific skills
Project directory naming: The project directory name is the repo's absolute path with leading slashes stripped and slashes replaced with -- (e.g., /Users/daniel.litman/code/pi-box becomes Users--daniel.litman--code--pi-box). This ensures sessions are project-isolated — each repository gets its own directory, and the same repo always maps to the same directory.
Session files live relative to the Makefile's location on the host, so the session state persists across runs and is tied to the project, not the working directory from which make is invoked.
- Docker