pi-env runs Pi Coding Agent safely against one selected project, with optional
guaranteed-reproducible tooling and optional managed agentic coordination.
It is built around three layers:
-
Sandboxed project isolation with Bubblewrap
Every run is confined to a mandatory Bubblewrap sandbox where the selected project is mounted read-write at
/workspace. The agent does not receive wholesale access to your home directory, credentials, shell configuration, Docker socket, or unrelated projects.Problem addressed: reducing the blast radius of agentic coding tools that can inspect files, run commands, and edit code.
-
Optional guaranteed-reproducible runtime with Nix
When selected, the Nix runtime provides pinned tools and dependencies so teams can run the agent with the same command-line environment across machines. Direct host-runtime mode remains available for convenience, but is intentionally unpinned.
Problem addressed: avoiding “works on my machine” drift in agent runs, checks, and development tooling.
-
Optional coordination repository for managed agentic development
pi-envincludes Git-backed coordination helpers and role workflows for teams that want structured multi-agent or multi-role development. This acts as a reference implementation of the coordination repository pattern: requirements, decisions, ownership, handoffs, and validation can be tracked outside the working project.Problem addressed: preventing ad-hoc agent work from becoming hard to audit, reproduce, assign, or hand off.
In short:
Without pi-env:
Pi -> host environment
-> home directory, SSH keys, cloud credentials, Docker socket,
shell config, unrelated projects
With pi-env:
Pi -> Bubblewrap sandbox
-> selected repo at /workspace, isolated HOME,
selected auth/config only
Most users start with one of two workflows:
- Direct use: run this checkout's
pi-envlauncher from any project. - Flake integration: add
pi-envas an input to a project's own flake so the team shares the same pinned runtime.
Assuming Linux, Git, and a configured host pi command are already available,
try pi-env on an existing public repository:
git clone https://github.com/spog/evm.git
cd evm
git clone https://github.com/u2up/pi-env.git ~/src/pi-env
~/src/pi-env/pi-env \
"Summarize this repository and suggest safe first checks."That direct checkout command starts in the default host runtime mode: Pi runs inside the Bubblewrap sandbox, but runtime tools are unpinned host tools. You can make the selection explicit or opt in to the reproducible Nix runtime:
~/src/pi-env/pi-env --runtime host "Inspect this repo with host tools."
~/src/pi-env/pi-env --runtime nix "Inspect this repo with pinned Nix tools."You can also use the Nix flake app directly when you want the pinned runtime without cloning first:
nix run github:u2up/pi-env -- \
"Summarize this repository and suggest safe first checks."All of these run Pi against the cloned repository with that repository mounted
read-write at /workspace inside the Bubblewrap sandbox. They are intended for
inspection. If you ask Pi to build or test a project, supply the project's
build tools with the host runtime policy or declare them in a devshell for the
Nix runtime as described below.
For tracked role-based agent work, enter the pi-env shell:
cd evm
nix develop github:u2up/pi-envThen bootstrap a local coordination repository for the checkout and run Pi:
bootstrap-coordination \
--project-root "$PWD" \
--project evm \
--project-key EVM
agent-coord-status
pi-env "Inspect this repository and review its state."This creates local coordination state under .pi-env/ for agent issue, TODO,
and synchronization tracking. .pi-env/ is operational state and should
normally stay untracked.
Install or configure these on the host before using this repository.
- Linux with unprivileged user namespaces/Bubblewrap support.
pi-coding-agentinstalled on the host and available aspionPATH.pi-envdoes not pin or install Pi itself.- Model credentials for Pi, either in Pi's normal auth files under
~/.pi/agentor as provider environment variables such asANTHROPIC_API_KEY/OPENAI_API_KEY. - Git or another way to fetch this repository.
Install Nix with flakes enabled for Nix runtime workflows (--runtime nix,
nix run, nix develop, profile installs, or project flake integration). You
can either enable the nix-command and flakes experimental features globally
or pass them when running Nix. Direct checkout use defaults to the host runtime
and does not require Nix.
Quick checks:
pi --version
# Needed only for Nix runtime workflows:
nix --versionIf Pi is not installed yet, install it using the upstream package. A common npm installation is:
npm install -g --ignore-scripts @earendil-works/pi-coding-agent@latest
pi --versionNode/npm are needed on the host for this Pi installation or upgrade step and
for host-runtime launches that use the npm-installed Pi launcher. In host
runtime mode, node for an npm-installed Pi launcher must resolve under
/usr/local/bin, /usr/bin, or /bin; PI_BWRAP_HOST_EXTRA_PATH does not
admit that launcher interpreter. The pi-env Nix shell provides Node and the
other runtime tools when you select the Nix runtime.
When you enter the devshell, select --runtime nix, run nix run, or consume
pi-env as a flake, Nix supplies the pinned runtime tools used by the wrappers:
bash bubblewrap cacert coreutils fd findutils gawk git gnugrep gnused
gnutar gzip jq nodejs ripgrep which
The development shell also includes review and patch utilities for contributor workflows:
diff diff3 patch
Verify the devshell tools with:
nix develop --command bash -lc 'command -v diff diff3 patch'You normally do not need to install these separately for pi-env itself.
Clone this repository for direct host-runtime use:
git clone https://github.com/u2up/pi-env.git ~/src/pi-envEnter the devshell when you want Nix-pinned pi-env commands and helper tools:
cd ~/src/pi-env
nix developIf flakes are not enabled globally, use:
nix --extra-experimental-features 'nix-command flakes' developVerify the commands are available:
pi-env --help
pi-bwrap --helpFor a conventional host-runtime install, use the non-Nix installer from a release/archive checkout:
./scripts/install-non-nix --prefix "$HOME/.local" --check-deps
export PATH="$HOME/.local/bin:$PATH"
pi-env --runtime host --helpThe installer does not invoke Nix and does not install a pinned runtime
toolchain. It copies pi-env commands to $PREFIX/bin, support files to
$PREFIX/share/pi-env, and writes wrappers that resolve coordination support,
coordination templates, and the role-manager package from that installed
prefix. Host tools such as Bash, Bubblewrap, Git, jq, Node, the pi launcher,
and standard POSIX text/file utilities must already be available. Use the Nix
flake, devshell, or profile packages when you need the reproducible pinned
runtime.
Release artifacts can contain only the pi-env payload directories (scripts/,
role-manager/, and pi-skill-templates/); end users do not need a Git clone
for installation. Prefer tagged releases or release artifact URLs for stable
installs, for example --ref v0.1.0 or --artifact-url URL.
For latest/development testing, the installer can bootstrap itself from a GitHub branch archive when no local payload is present:
curl -fsSL https://raw.githubusercontent.com/u2up/pi-env/main/scripts/install-non-nix \
| bash -s -- --ref main --prefix "$HOME/.local" --check-deps--ref main is mutable and non-reproducible; treat it as a development/latest
channel, not the recommended stable install path. Use --repo OWNER/REPO for
forks, or --artifact-url URL to install from a specific archive URL.
Re-run the same command to upgrade or repair an install. To remove installed files later, use the manifest-backed uninstall command:
pi-env-uninstall
# or, without PATH setup:
"$HOME/.local/bin/pi-env-uninstall"Inside nix develop the prompt is prefixed with (nix-dev). The shell exports
PI_ENV_ROLE_MANAGER_PACKAGE to the Nix-built role-manager package path and
prints a short reminder unless PI_ENV_QUIET is set.
For the smallest profile that can launch Pi in the sandbox, install the core
runtime package. It puts pi-env, pi-start, pi-bwrap, and the runtime tools
on PATH without the Git-backed coordination helper commands:
nix profile install ~/src/pi-env#pi-coreIf you also use coordination helpers, either install them separately or keep the compatibility runtime bundle:
nix profile install ~/src/pi-env#pi-coordination
# or, for the compatibility bundle used by older docs/automation:
nix profile install ~/src/pi-env#pi-runtimepi-runtime continues to include the core runtime plus coordination helpers.
None of these packages install pi-coding-agent; the host pi command must
already exist.
Use direct mode for local, ad hoc, or internal runs where selecting a pi-env
checkout is enough and the target project does not need to pin pi-env in its
own flake.lock. Direct checkout startup defaults to host runtime mode:
pi-env still enters the Bubblewrap sandbox, but command-line tools are the
unpinned host tools admitted by pi-env's conservative mount policy.
From the target project directory, run this checkout's launcher:
cd /path/to/project
~/src/pi-env/pi-env
~/src/pi-env/pi-env "Inspect this repo"
~/src/pi-env/pi-env --raw -- --model anthropic/claude-sonnet-4-5 "Inspect this repo"If you installed #pi-core or #pi-runtime into a profile, you can run the
shorter command:
cd /path/to/project
pi-env
pi-env "Inspect this repo"The checkout launcher defaults to host runtime mode. It preserves the current
project as the detected project root; inside the sandbox that project is
mounted read-write at /workspace. The pi-env checkout is only the source of
launcher code and runtime policy.
Select a runtime explicitly with --runtime or PI_ENV_RUNTIME:
~/src/pi-env/pi-env --runtime host "Inspect this repo"
~/src/pi-env/pi-env --runtime nix "Inspect this repo with pinned tools"
PI_ENV_RUNTIME=nix ~/src/pi-env/pi-envUse --runtime host for direct startup with unpinned host tools. Use
--runtime nix, nix run, nix develop, profile packages, or project flake
integration when you need the reproducible/pinned Nix runtime. --runtime auto
keeps compatibility with environments that already provide pi-env commands and
falls back to the Nix runtime when needed.
Use --raw -- when you want to pass arguments directly to Pi through
pi-bwrap instead of using the pi-start defaults:
pi-env --raw -- --model anthropic/claude-sonnet-4-5 "Inspect this repo"Select another pi-env flake reference with either form:
PI_ENV_FLAKE=github:u2up/pi-env ~/src/pi-env/pi-env
~/src/pi-env/pi-env --flake github:u2up/pi-envUse project-integrated mode when a repository should:
- pin pi-env for the team in the repository's
flake.lock; - share the same pi-env revision across machines and CI jobs;
- combine pi-env with project-specific Nix dependencies; or
- expose one committed
nix developentrypoint for both project tools and Pi.
After integration, the usual workflow is:
cd /path/to/project
nix develop
pi-env
pi-env "Inspect this repo"
pi-env --raw -- --model anthropic/claude-sonnet-4-5 "Inspect this repo"Both direct and flake-integrated modes keep the selected project root as the
single project mounted at /workspace. Direct use gets pi-env from a checkout
or profile; flake integration lets the project pin pi-env in its own
flake.lock and combine it with project-specific tools. Neither mode turns
pi-env into a separate workspace-env repository or multi-project manager.
For a project that does not yet have a flake, use this as a starting
flake.nix. Replace the pi-env.url with either a local checkout or a Git
reference that your team can access.
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
flake-utils.url = "github:numtide/flake-utils";
# Local checkout example. A shared repo could use github:u2up/pi-env.
pi-env.url = "git+file:///home/me/src/pi-env";
pi-env.inputs.nixpkgs.follows = "nixpkgs";
pi-env.inputs.flake-utils.follows = "flake-utils";
};
outputs = { nixpkgs, flake-utils, pi-env, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
in {
devShells.default = pi-env.lib.mkPiShell {
inherit pkgs;
# Smallest project shell: omit agent-coord* helper commands unless
# this project uses Git-backed coordination.
includeCoordinationHelpers = false;
extraPackages = with pkgs; [
# project-specific tools, for example:
# nodejs
# python3
];
shellHook = ''
echo "project shell loaded"
'';
};
});
}Then run:
nix develop
pi-envmkPiShell defaults includeCoordinationHelpers to true so existing
consumers keep bootstrap-coordination and agent-coord* commands on PATH.
Set it to false for core-only project shells.
If the project already has a flake.nix, keep its existing structure and add
only the pi-env pieces.
Add the input:
inputs = {
# existing inputs...
pi-env.url = "git+file:///home/me/src/pi-env";
# or: pi-env.url = "github:u2up/pi-env";
pi-env.inputs.nixpkgs.follows = "nixpkgs";
pi-env.inputs.flake-utils.follows = "flake-utils";
};Include pi-env in the outputs arguments:
outputs = { self, nixpkgs, flake-utils, pi-env, ... }:
# existing outputs...Then choose one integration style.
Use this when you want pi-env to own the shell composition and add your project
tools through extraPackages:
devShells.default = pi-env.lib.mkPiShell {
inherit pkgs;
# Keep this false for a core-only runtime shell. Omit the option or set it
# to true if the project uses bootstrap-coordination or agent-coord* helpers.
includeCoordinationHelpers = false;
extraPackages = with pkgs; [
# existing project tools
];
shellHook = ''
# existing shell hook
'';
};pi-env keeps its default runtime intentionally small. Host runtime mode uses
unpinned host tools from the conservative sandbox PATH; Nix runtime mode uses
the pinned tools provided by this flake. Neither mode bundles every compiler or
build system a target repository might need. Add host-runtime tools explicitly
with PI_BWRAP_HOST_EXTRA_PATH, or declare reproducible project tools in the
consuming project's Nix flake. Bubblewrap remains the isolation boundary in both
modes.
For builds or tests, declare tools in the consuming project's flake:
devShells.default = pi-env.lib.mkPiShell {
inherit pkgs;
includeCoordinationHelpers = false;
extraPackages = with pkgs; [
gnumake
gcc
pkg-config
];
};mkPiShell turns the extraPackages bin outputs into PI_BWRAP_EXTRA_PATH.
In Nix runtime mode, pi-bwrap validates those entries before starting the
sandbox, accepts only canonical /nix/store directories, and then appends them
after the core pi-env runtime path. Since /nix/store is already mounted
read-only, no host /bin, host /usr/bin, project-writable directory, or scan
of the whole store is needed. pi-env does not infer tools from a repository
automatically.
Advanced Nix-runtime users may set PI_BWRAP_EXTRA_PATH directly to a
colon-separated list of command directories, but entries must be absolute
existing directories that canonicalize under /nix/store; unsafe entries such
as /tmp/bin, $HOME/bin, ./bin, /usr/bin, or /bin are rejected before
Pi starts. Host-runtime users should use PI_BWRAP_HOST_EXTRA_PATH instead;
those entries are canonicalized, must exist, are mounted read-only, and are
rejected under host $HOME.
Use this when the project already has a custom mkShell and you only want to
add the pi-env commands:
packages = existingPackages ++ [
pi-env.packages.${system}.pi-core
];If your shell uses nativeBuildInputs or buildInputs, add the same package
there instead. Use pi-env.packages.${system}.pi-coordination when you want
only the optional coordination helpers, or pi-env.packages.${system}.pi-runtime
when older automation expects the compatibility bundle containing both the core
runtime and coordination helpers.
Update a consuming project's pinned input with:
nix flake update pi-envStart Pi with pi-env defaults:
pi-envDirect checkout pi-env defaults to host runtime mode. In all runtime modes,
pi-env delegates to pi-start, which runs the sandbox with the default tool
allowlist, --continue, and the default role-manager package when available:
pi-bwrap --tools read,bash,edit,write,grep,find,ls --continue -e "$PI_ENV_ROLE_MANAGER_PACKAGE"Select the runtime with --runtime host|nix|auto or
PI_ENV_RUNTIME=host|nix|auto; the command-line option wins. Host runtime is
unpinned and uses admitted host tools. Nix runtime is reproducible and pinned
by the selected pi-env flake, entering nix develop when needed.
For custom Pi arguments, use raw mode:
pi-env --raw -- --model anthropic/claude-sonnet-4-5 "Inspect this repo"
pi-env --runtime nix --raw -- --model anthropic/claude-sonnet-4-5 "Inspect this repo"pi-start is the default startup wrapper. It chooses the default tool list from
PI_BWRAP_DEFAULT_TOOLS when set, otherwise uses Pi's built-in tools:
read,bash,edit,write,grep,find,ls
By default, pi-start loads the packaged role-manager extension when the
package path exists. The role manager is inactive until you select a role,
restore one from session state, or request one through supported environment
variables. Set PI_ENV_ROLE_MANAGER_AUTO=0 to omit the automatic per-run
extension argument, especially if you prefer an installed-package workflow.
pi-bwrap runs pi-coding-agent inside the Bubblewrap sandbox. Use it directly
when you want full control over the Pi arguments or when running Pi subcommands:
pi-bwrap -- --help
pi-bwrap -- configPi's config subcommand enables or disables extensions, skills, prompt
templates, and themes.
To edit the sandboxed pi-env config, run it through Bubblewrap:
pi-bwrap -- config
# or
pi-bwrap configInside the sandbox, Pi uses /home/pi/.pi/agent/settings.json, backed by
pi-env's per-project state directory. Project-local config remains the mounted
repo's .pi/settings.json under /workspace.
By default, pi-env copies the host settings.json into sandbox state on each
run when global extensions/packages are imported. If you want sandbox edits made
by pi-bwrap -- config to persist instead of being refreshed from the host
copy, use:
PI_BWRAP_EXTENSIONS_SYNC=missing pi-bwrap -- configTo edit your real host/global Pi config, run pi config directly after
entering the Nix devshell:
nix develop
pi configThis uses the Nix-provided runtime/tools on PATH, but does not enter the
Bubblewrap sandbox. It modifies the host Pi agent config, normally
~/.pi/agent/settings.json unless PI_CODING_AGENT_DIR points elsewhere.
A pi-env invocation operates on one selected project root. The launcher detects
or receives that root, and the Bubblewrap layer mounts it read-write at the
fixed in-sandbox path /workspace. Complex layouts such as monorepos,
submodules, worktrees, or integration checkouts remain project-owned policy;
pi-env only chooses which root to expose for this run.
pi-bwrap:
- mounts the detected project root read-write at
/workspace; - mounts
/nix/storeread-only so declared devshell tools can be exposed through validated extra command paths; - constructs the sandbox
PATHfrom allowlisted host command directories (/usr/local/bin,/usr/bin, and/bin) instead of inheriting the caller's full hostPATH; - mounts
/usr/local/binand the global Pi npm package read-only when present, so a global npm-installedpiworks; - uses isolated
$HOME=/home/pi; - stores sandbox Pi state outside the project by default under
$XDG_STATE_HOME/pi-env/<project-hash>or$HOME/.local/state/pi-env/<project-hash>; - imports common Pi rules/skills/prompts/roles from the host Pi agent directory
by default (
$PI_CODING_AGENT_DIR, else~/.pi/agent), limited toAGENTS.md,CLAUDE.md,SYSTEM.md,APPEND_SYSTEM.md,skills/,prompts/, androles/; - exposes global Pi extensions and installed package directories from the host
Pi agent directory by default (
extensions/,npm/,git/) and copiessettings.json, while project-local.pi/extensionsand.pi/settings.jsonare available through/workspace; - copies host Git config into the sandbox by default (
~/.gitconfigand$XDG_CONFIG_HOME/git/config/~/.config/git/config), but not Git credentials or SSH keys; - copies host Pi model auth files (
auth.json,models.json) from~/.pi/agentinto sandbox state by default; - bind-mounts only the host Pi session directory for the current working
directory into the sandbox by default (disabled for ephemeral homes), so
/resumeand--continuecan access sessions for the directory/project without exposing all sessions; - passes
PI_COORD_ROOT,PI_COORD_REMOTE_URL,PI_COORD_PROJECT,PI_COORD_AGENT_ID,PI_COORD_PROJECT_KEY,PI_COORD_ROLE, and coordination directory context, mapping project-local coordination paths to/workspace/..., binding explicit externalPI_COORD_ROOTpaths at/agent-remotes, and explicitly mounting an external coordination clone withPI_BWRAP_COORDINATION_DIR; - accepts additional host-runtime command directories only through
PI_BWRAP_HOST_EXTRA_PATH; entries must be absolute, existing directories, are canonicalized, are mounted read-only, and are rejected under host$HOME; - does not mount host
$HOME,~/.ssh, cloud credential directories, or Docker sockets; - clears the environment, then passes only terminal basics and selected LLM provider variables;
- shares the host network by default so Pi can reach model providers.
In host runtime mode, pi-env also adds conservative read-only support mounts
for system runtime files: /lib, /lib64, /usr/lib, /usr/lib64, /bin,
/usr/bin, and certificate-related /etc paths when they exist. These support
mounts let admitted host binaries run inside Bubblewrap; they are not a general
host filesystem, /usr/share, locale, alternatives, or home-directory mount.
If your pi command or language-manager shims live under host $HOME (for
example ~/.local/bin, ~/.nvm, ~/.asdf, or a per-user npm prefix), host
runtime rejects them by default because host $HOME is not mounted. Prefer a
system/global install under /usr/local/bin, /usr/bin, or /bin, move a
custom pi launcher or other needed command directory outside $HOME and opt
it in with PI_BWRAP_HOST_EXTRA_PATH, or use --runtime nix for pinned tools.
Custom host tool directories admitted with PI_BWRAP_HOST_EXTRA_PATH are
mounted read-only and only after validation.
When an admitted host pi launcher uses #!/usr/bin/env node, node itself
must resolve under /usr/local/bin, /usr/bin, or /bin; the launcher check
does not admit node from PI_BWRAP_HOST_EXTRA_PATH. Use a system/global Node
install or --runtime nix when Node only exists in another custom directory.
Important: with the bash/read tools enabled, auth copied into the sandbox
and project sessions bind-mounted into the sandbox can be read by commands or
tools inside the sandbox. This is still safer than mounting your whole home, but
use least-privilege API keys or a provider proxy when possible.
Common environment knobs:
PI_BWRAP_PROJECT_ROOT=/path/to/repo # default: git root, else $PWD
PI_BWRAP_USE_GIT_ROOT=0 # bind only $PWD
PI_BWRAP_STATE_DIR=/path/to/state # persistent sandbox home/config; .pi-env/state is opt-in
PI_BWRAP_EPHEMERAL_HOME=1 # temporary home/config for this run
PI_BWRAP_IMPORT_AUTH=0 # do not import host ~/.pi/agent auth files
PI_BWRAP_AUTH_SYNC=missing # copy auth only if sandbox copy is absent; default is always
PI_BWRAP_IMPORT_SESSIONS=0 # do not bind host sessions for the current working directory
PI_BWRAP_HOST_AGENT_DIR=/path/to/agent # default: $PI_CODING_AGENT_DIR or ~/.pi/agent
PI_BWRAP_COMMON_AGENT_DIR=/path/to/dir # common rules/skills/roles dir; default: host Pi agent dir
PI_BWRAP_IMPORT_COMMON=0 # do not import common AGENTS/SYSTEM files, skills, prompts, or roles
PI_BWRAP_COMMON_SYNC=missing # copy common files only if sandbox copy is absent; default is always
PI_BWRAP_IMPORT_EXTENSIONS=0 # do not expose global Pi extensions/packages from host agent dir
PI_BWRAP_EXTENSIONS_SYNC=missing # copy settings.json only if sandbox copy is absent; default is always
PI_BWRAP_IMPORT_GIT_CONFIG=0 # do not import host ~/.gitconfig and XDG git config
PI_BWRAP_GIT_CONFIG_SYNC=missing # copy git config only if sandbox copy is absent; default is always
PI_BWRAP_HOST_GITCONFIG=/path # host global git config; default: ~/.gitconfig
PI_BWRAP_HOST_XDG_GIT_CONFIG=/path # host XDG git config; default: $XDG_CONFIG_HOME/git/config or ~/.config/git/config
PI_BWRAP_COORDINATION_DIR=/path/to/coordination # bind external coordination clone at /coordination
PI_COORD_ROOT=.pi-env/agent-remotes # explicit bare remotes; project paths map to /workspace, external paths to /agent-remotes
PI_COORD_REMOTE_URL=git@example:repo.git # optional Git-server coordination remote URL; no local remotes mount required
PI_COORD_PROJECT=pi-env # coordination project/domain name
PI_COORD_PROJECT_KEY=PIENV # optional generated item ID prefix
PI_COORD_ROLE=architect # active coordination role for helper commits/events
PI_BWRAP_DEFAULT_TOOLS="read,bash,..." # override pi-start/pi-bwrap default tools
PI_BWRAP_EXTRA_PATH=/nix/store/.../bin # Nix runtime: validated /nix/store command dirs
PI_BWRAP_HOST_EXTRA_PATH=/opt/tools/bin # host runtime: validated read-only host command dirs
PI_BWRAP_NET=0 # disable network sharing
PI_BWRAP_PASS_ENV="HTTP_PROXY,NO_PROXY" # pass extra env vars by nameCommon per-project overrides can be set before running pi-start / pi-bwrap,
or exported in the project's shell hook:
PI_BWRAP_PROJECT_ROOT=/path/to/repo pi-start # mount this repo at /workspace
PI_BWRAP_USE_GIT_ROOT=0 pi-start # use $PWD instead of git root
PI_BWRAP_EPHEMERAL_HOME=1 pi-start # throw away sandbox home after the run
PI_BWRAP_STATE_DIR=$PWD/.pi-env/state pi-start # opt in to project-local sandbox state
PI_BWRAP_IMPORT_AUTH=0 pi-start # do not copy host Pi auth into sandbox state
PI_BWRAP_NET=0 pi-start # disable network accessInside the sandbox, the selected project root is mounted read-write at
/workspace, while the sandbox home and Pi config live separately from the
host home. The default state location intentionally stays outside .pi-env/
because it can contain copied auth, settings, sessions, and caches; use
PI_BWRAP_STATE_DIR=$PWD/.pi-env/state only when you explicitly want that
project-local operational state.
pi-env keeps the runtime separate from user-specific agent behavior. It does
not ship common rules, skills, prompts, or custom roles itself. Instead,
pi-bwrap imports common Pi resources from an external directory into the
sandbox Pi agent directory.
By default, the common directory is the user's normal Pi agent directory:
$PI_CODING_AGENT_DIR # if set
~/.pi/agent # otherwiseFrom that directory, pi-bwrap imports only common agent resources:
AGENTS.md
CLAUDE.md
SYSTEM.md
APPEND_SYSTEM.md
skills/
prompts/
roles/
It does not import the whole host home, and auth/session handling remains
controlled separately by PI_BWRAP_IMPORT_AUTH and
PI_BWRAP_IMPORT_SESSIONS. Global extension/package exposure is controlled
separately by PI_BWRAP_IMPORT_EXTENSIONS.
To keep common rules, skills, prompts, or roles in a separate repo or directory,
point PI_BWRAP_COMMON_AGENT_DIR at it:
PI_BWRAP_COMMON_AGENT_DIR=~/CODE/my-pi-common pi-startExpected layout:
my-pi-common/
AGENTS.md
skills/
common-skill/
SKILL.md
prompts/
review.md
roles/
domain-architect.md
Disable common resource import entirely with:
PI_BWRAP_IMPORT_COMMON=0 pi-startProject-specific rules, skills, roles, and extensions should live in the project repository so they are versioned with the project:
project/
AGENTS.md
.pi/
extensions/
project-extension.ts
skills/
project-skill/
SKILL.md
prompts/
roles/
release-builder.md
settings.json
Pi loads the common/global resources from /home/pi/.pi/agent and also
discovers project resources from /workspace, giving a clean split:
- common rules/skills/roles and global extensions/packages: user-owned, reusable across projects;
- project-specific rules/skills/roles/extensions/packages: committed with the project;
pi-env: neutral runtime and isolation layer only.
pi-bwrap imports the user's host Git config into the isolated sandbox home by
default:
~/.gitconfig
$XDG_CONFIG_HOME/git/config, or ~/.config/git/config
Inside the sandbox these become:
/home/pi/.gitconfig
/home/pi/.config/git/config
This lets Git commands run by Pi use the user's normal identity, aliases,
default branch settings, diff settings, and other non-secret Git preferences
while still avoiding a host $HOME mount.
Disable this with:
PI_BWRAP_IMPORT_GIT_CONFIG=0 pi-startUse a different config source with:
PI_BWRAP_HOST_GITCONFIG=/path/to/gitconfig pi-start
PI_BWRAP_HOST_XDG_GIT_CONFIG=/path/to/xdg-git-config pi-startBy default the sandbox copy is refreshed on each run. Preserve an existing sandbox copy with:
PI_BWRAP_GIT_CONFIG_SYNC=missing pi-startGit credentials, SSH keys, signing keys, credential helpers' backing stores, and other files referenced from Git config are not imported automatically.
pi-env ships a Pi role-manager package for agent roles such as architect,
developer, builder, tester, and reviewer. The package contains a Pi extension
plus Markdown role definitions under role-manager/. pi-start loads it by
default with Pi's per-run extension/package flag when the package path exists;
this does not modify global or project settings.json.
Inside nix develop, the shell exports PI_ENV_ROLE_MANAGER_PACKAGE to the
Nix-built role-manager package path. To opt out of default loading for one run:
PI_ENV_ROLE_MANAGER_AUTO=0 pi-startYou can still install it into project-local Pi settings if you want Pi to load it normally without the per-run flag. In that workflow, use the opt-out variable if you want to avoid loading the same package through both mechanisms:
pi-bwrap install -l "$PI_ENV_ROLE_MANAGER_PACKAGE"
PI_ENV_ROLE_MANAGER_AUTO=0 pi-startThe role-manager package can also be built directly:
nix build /path/to/pi-env#pi-role-manager
pi-bwrap install -l "$(readlink -f result)"Base roles are bundled with the package:
| Role | Purpose | Default tools |
|---|---|---|
architect |
Design, trade-offs, decisions, and plans. | read, grep, find, ls |
developer |
Focused source changes. | read, grep, find, ls, edit, write, bash |
builder |
Build, packaging, integration, and release prep. | read, grep, find, ls, bash, edit |
tester |
Reproduction, tests, verification, and coverage gaps. | read, grep, find, ls, bash, edit, write |
reviewer |
Diff, risk, security, and maintainability review. | read, grep, find, ls, bash |
Role definitions are Markdown files with frontmatter. Project roles live in
.pi/roles/*.md beside other project Pi resources. Common roles can live in the
host/common agent resource directory as roles/*.md; pi-bwrap imports that
roles/ directory with common skills/ and prompts/ when common import is
enabled. A mounted coordination clone may also provide roles for that project
coordination domain.
Roles are merged by name; later sources override earlier ones:
- bundled base package roles;
- global/common agent roles imported into
/home/pi/.pi/agent/roles; - common roles from
PI_BWRAP_COMMON_AGENT_DIR/roleswhen directly visible; - coordination-domain roles from
$PI_COORD_DIR/roles; - project roles from
.pi/roles.
See role-manager/ROLE_FILE_SCHEMA.md for the full schema. See
examples/project-role-override/.pi/roles/domain-architect.md for a minimal
project-specific role that adds a role without changing the base package.
The extension registers these slash commands:
/role select a role interactively
/role <name> switch the current session to a role
/role-clear clear the role and restore prior settings
/role-cycle <name> <goal> run one bounded role cycle in this session
/role-new <name> <goal> start a fresh session and run one role cycle
When a role is active, only that role's instructions are injected into the
system prompt for each turn. Role frontmatter may request a thinking level,
model/provider, and tool allowlist. Unknown requested tools are warned and
ignored. The default pi-start allowlist includes every built-in tool used by
the bundled roles. /role-cycle includes the role's one-cycle checklist in the
kickoff prompt, enables the package's role_cycle_done tool for that cycle,
and instructs the model to call it as the final action so Pi can terminate the
cycle without an extra follow-up turn. If that tool is unavailable, the prompt
asks for a normal prose final report rather than JSON. /role-new requests
that Pi preserve the existing UI screen while switching to the fresh session.
When the role-manager extension has an active role, it sets PI_COORD_ROLE for
Pi subprocesses to the role's coordCommitter value, or to the role name when
coordCommitter is omitted. Coordination helper commands use that value only
for coordination item event actors and per-command coordination Git identity;
project repository commits keep the normal imported Git identity unless the
user explicitly changes it.
See designs/role-manager.md for the architecture.
pi-env includes opt-in helpers for one Git-backed coordination repository per
selected project. They are plain Git/text-file tooling and are separate from
pi-start. Install #pi-coordination, use the compatibility #pi-runtime
bundle, or leave includeCoordinationHelpers enabled in mkPiShell when you
want these commands. Projects use the project-local .pi-env/coordination
clone for the one project mounted at /workspace per invocation.
Guided setup with inferred, project-specific defaults:
bootstrap-coordination
# inspect another project root from this devshell
bootstrap-coordination --project-root /path/to/project --print-only
# or only print the suggested PI_COORD_* environment and init command
bootstrap-coordination --print-onlyManual minimal setup with a local bare remote:
export PI_COORD_ROOT=/workspace/.pi-env/agent-remotes
export PI_COORD_PROJECT=pi-env
export PI_COORD_PROJECT_KEY=PIENV
export PI_COORD_DIR=/workspace/.pi-env/coordination
export PI_COORD_AGENT_ID=agent-a
agent-coord-initTo use a remote hosted by a Git server, pass it explicitly or set
PI_COORD_REMOTE_URL:
agent-coord-init --project pi-env --remote git@example.com:org/pi-env-coordination.git
agent-coord-clone --remote git@example.com:org/pi-env-coordination.git
bootstrap-coordination --remote git@example.com:org/pi-env-coordination.git --print-onlybootstrap-coordination is a thin wrapper around agent-coord-init: it prints
the inferred root, clone dir, remote, agent ID, project, and project key, then
initializes with those explicit values. Remote selection uses this precedence:
explicit --remote, then PI_COORD_REMOTE_URL, then the local bare remote
under PI_COORD_ROOT. If the local coordination clone already exists but the
planned local bare remote is missing or empty, it recreates that remote from
the clone's committed Git history and repairs origin when it is absent or
points to a missing local path.
Without a configured remote URL, this creates a bare remote at:
$PI_COORD_ROOT/$PI_COORD_PROJECT-coordination.git
If PI_COORD_ROOT is unset, helpers default to the project-local
.pi-env/agent-remotes directory. Inside the pi-env sandbox, that is normally
/workspace/.pi-env/agent-remotes, available through the standard project
bind mount rather than a separate remotes mount.
If PI_COORD_ROOT is set to a project-local path, pi-bwrap rewrites it to the
matching /workspace/... path. If it is set to an existing local path outside
the project, pi-bwrap bind-mounts that directory read-write at
/agent-remotes and rewrites PI_COORD_ROOT=/agent-remotes inside the
sandbox. Without explicit overrides, the sandbox launcher only recognizes
project-local .pi-env/coordination and .pi-env/agent-remotes; root-level
coordination/ and agent-remotes/ directories are not selected or mounted
automatically.
When --remote or PI_COORD_REMOTE_URL is set, helpers use that URL directly
and do not create a local bare remote. The remote repository must already exist
and be accessible to Git. Provide SSH keys, tokens, or credential helpers
through narrowly-scoped sandbox/project configuration as needed; pi-env does
not import the host ~/.ssh directory or all host Git credentials wholesale.
It then clones/scaffolds $PI_COORD_DIR with AGENTS.md, project
PROJECT.md metadata, root issues/, requirements/, todos/,
decisions/, and notes/ directories, protocol docs, item-format docs, and
.pi/skills/agent-coordination/SKILL.md. When PI_COORD_DIR is unset, fresh
projects use .pi-env/coordination.
Clone the same coordination domain elsewhere with:
agent-coord-cloneCreate a type-coded timestamp-ID item in the project-root layout with:
agent-coord-new --type issue --category bug "Document pi config behavior"
agent-coord-push -m "Add PIENV documentation item"Issue items can use optional categories such as bug, feature-request,
task, question, or improvement; use --type issue --category task for task-category work. Use agent-coord-list --category bug issues open to filter, or agent-coord-list --group-by-category issues
to sort grouped issue output.
When top-level PROJECT.md exists, omit --project; PI_COORD_PROJECT can
remain set for coordination-domain selection while root item paths are used.
Generated item IDs use a project item key prefix, a type code, a UTC timestamp,
and a three-digit collision/order suffix that starts at 001:
<PROJECTKEY>-<TYPECODE>-<YYYYMMDD-HHMMSS>-<NNN>
For example, an issue can be created as
PIENV-ISS-20260607-204155-001.yaml; a functional requirement can be created as
PIENV-FRQ-20260607-204155-001.yaml. Use
agent-coord-init --project-key PIENV to set the initial project's stored key
during scaffolding. Project-root keys are stored in top-level PROJECT.md as item_key. Agents
should use stored keys instead of inventing new ones.
Key resolution for agent-coord-new is:
--project-key KEY;- stored root project
item_key; PI_COORD_PROJECT_KEYwhen no stored key exists;- derived
--project/PI_COORD_PROJECTfor project items; - derived coordination clone directory name when no project name is set.
Derived keys are uppercased and all delimiters, whitespace, pipes, slashes,
backslashes, and other non-alphanumeric characters are removed. For example,
pi-env_test becomes PIENVTEST. --id ID overrides the whole item ID.
Built-in type codes are ISS for issue, FRQ for functional-requirement,
QRQ for quality-requirement, CRQ for constraint-requirement, TODO for
todo, DEC for decision, and NOTE for note.
Lifecycle helpers are also available:
bootstrap-coordination
infer defaults and initialize via agent-coord-init
agent-coord-status show sync status and open/blocked/done items
agent-coord-list list issues, todos, notes, decisions, requirements, or
classes by status
agent-coord-cat print one resolved item's YAML or repo-relative path
agent-coord-pull run git pull --rebase --autostash
agent-coord-push commit and push coordination changes
agent-coord-new create a templated item
agent-coord-claim claim an item, commit, and push
agent-coord-done mark developer work done, commit, and push
agent-coord-review mark review pass/fail, commit, and push
agent-coord-verify mark verification pass/fail, commit, and push
agent-coord-close final-close reviewed+verified done items
agent-coord-lint lint item IDs, status, and item-matched tests
agent-coord-upgrade-rules --preview
preview/apply bundled rule template updates
pi-serial-roles serially run one developer/reviewer/tester Pi job at a time
Items are YAML files with chronological events and linked Markdown messages.
Issue state group names are developer-centric: open means developer work is
needed, blocked means developer work cannot proceed, done means the
developer believes implementation is complete, and closed means final
accepted after review and verification.
Functional, quality, and constraint requirements use the root-level
requirements/ directory while preserving FRQ, QRQ, and CRQ item-ID type
codes. TODO items use todos/ and single top-level body: |- records without
issue history. The agent-coord-list requirements command reports functional,
quality, and constraint requirement items; use functional, quality, or
constraint for class-specific listings. Done issue listings append
review and verification sub-status after the title. Imported requirement items
record traceability in a top-level source_refs list using stable strings such
as old requirement IDs, REQUIREMENTS.md#heading, and USE_CASES.md#section;
lint checks imported FRQ/QRQ/CRQ items for non-empty source references plus the
standard testable metadata.
Decision, note, and other non-issue item types live under their semantic type
directories. Use agent-coord-list notes or agent-coord-list todos to list
those root-level groups, optionally filtered by their YAML status values. The
accepted TODO type spellings are todo and todos; tdo is
not a supported alias. Stored implementation refs are structured objects with
repo,
branch, and full commit fields. agent-coord-done --implementation-ref pi-env:main@<full-hash> accepts the compact CLI form and writes the structured
YAML form. agent-coord-close finalizes only items that are done, reviewed, and
verified unless forced.
Commands that create item events or coordination commits accept --role ROLE
and read PI_COORD_ROLE. Item events store actor ID/role metadata explicitly;
helper commits use per-command Git identity overrides such as pi/architect <pi+architect@coordination.local>. These overrides are scoped to the helper's
coordination-repository git commit; normal project repository commits keep the
user's imported Git identity unless the user explicitly opts in to another
identity.
Existing coordination repositories are not silently overwritten. Rule upgrades are explicit and diffable:
agent-coord-upgrade-rules --preview
agent-coord-upgrade-rulesThe helpers do not make pi-start create, claim, mark done, review, verify,
close, commit, or push coordination state automatically. If a coordination clone
is under the mounted project, pi-bwrap only exposes it as normal project files
and sets PI_COORD_DIR to the sandbox path. For a coordination clone outside
the project, opt in explicitly:
PI_BWRAP_COORDINATION_DIR=/path/to/coordination pi-startThat clone is mounted read-write at /coordination and PI_COORD_DIR is set
to /coordination inside the sandbox.
When the role-manager extension has an active role, it sets PI_COORD_ROLE for
Pi subprocesses to the role's coordCommitter value, or to the role name when
coordCommitter is omitted. Bash-invoked agent-coord-* commands inherit the
active role without changing project Git identity.
pi-serial-roles is the first, deliberately serial automation mode for
coordination-backed role work. Use it when you want one long-lived shell to
process developer, reviewer, and tester issue jobs over one project clone and
one coordination clone, without concurrent source edits or competing Git
operations in that clone. It is useful for initial small automation and prompt
shake-out before investing in parallel workers.
Serial mode prerequisites:
- run from a clean Git project root, or pass
--project-root DIR; - provide a writable coordination checkout. Projects default to
.pi-env/coordination; usePI_COORD_DIRor--coord-dir DIRfor an explicit override path; - run from the pi-env devshell/profile so
pi-env,agent-coord-*helpers, andPI_ENV_ROLE_MANAGER_PACKAGEare available, or pass explicit--pi-envand--role-managerpaths; - configure Pi model credentials on the host the same way you do for normal
pi-envruns, for example host Pi auth files or provider environment variables; and - allow the orchestrator to mount the selected coordination clone into each raw
sandbox job. It passes
PI_BWRAP_COORDINATION_DIR,PI_COORD_DIR,PI_COORD_AGENT_ID, and role context for the job, and exposes packaged lifecycle helpers throughPI_BWRAP_EXTRA_PATHwhen they live in the Nix store.
Start the loop from the project root:
cd /path/to/project
pi-serial-roles --sleep 30Stop it with Ctrl-C, or use bounded modes when you want it to exit on its own:
pi-serial-roles --once
pi-serial-roles --max-jobs 3
pi-serial-roles --max-idle-polls 1 --sleep 5
pi-serial-roles --dry-run
pi-serial-roles --ui interactive --once
pi-serial-roles --ui json --once
pi-serial-roles --ui none --onceEach poll holds .pi-env/locks/pi-serial-roles.lock and creates
.pi-env/locks as needed. It requires a clean project working tree. A dirty
coordination checkout before selection is treated as busy: the loop skips
pulling, selecting, and claiming, then sleeps or exits according to the bounded
idle options. Clean coordination is still required before a pull, before a Pi
job, and after each job completes. Serial automation logs and future local
diagnostics default under
.pi-env/logs. Work priority is:
- tester: done issues with
reviewed: trueandverified: false; - reviewer: done issues with
reviewed: false; - developer: open issues that are unowned or already owned by the agent.
Developer items are claimed with agent-coord-claim before Pi is invoked.
Reviewer and tester prompts name only the selected done item and instruct the
role to use agent-coord-review or agent-coord-verify. If no issue is
eligible, the orchestrator sleeps and polls again without invoking Pi.
Every issue job starts a fresh raw Pi session with pi-env --raw -- and does
not pass --continue. The default --ui interactive mode launches the normal
Pi TUI with the selected item prompt, active role environment, coordination
mount, and tool allowlist, but without --mode json, --print, or -p. It
also passes the role-manager extension flag that requests graceful TUI shutdown
after role_cycle_done, so the orchestrator can continue after the bounded
role cycle finishes.
Use --ui json for structured automation. It adds --mode json for JSONL
output, which is useful for unattended loops, CI-like supervision, or when you
want to parse the final role_cycle_done details from the corresponding
tool_execution_end event alongside other structured tool, usage, compaction,
and error events.
Use --ui none for non-interactive prompt/response output. It adds --print,
prints the generated role report, and exits without a TUI or JSON event stream.
The previous hold-open interactive behavior has intentionally been removed
and is not available under another --ui value or compatibility alias. If you
need to inspect a completed session, use normal logs/output or run Pi directly
instead of keeping pi-serial-roles blocked after each item. Coordination state
and Git history are the memory shared between jobs; a fresh conversation avoids
stale context from a previous issue influencing item selection, review,
verification, or lifecycle helper use.
The command fails closed around role execution. Dirty project trees stop the loop before polling. A dirty coordination tree during idle pre-selection is treated as a busy checkout: the loop does not pull, inspect, select, claim, reset, discard, or stash, and it retries after the normal sleep. Dirty project or coordination trees after a role job remain fatal. A failed coordination pull/rebase stops with the helper's error so you can resolve the conflict and rerun. A non-zero Pi job also stops the loop instead of moving on to another issue; inspect the terminal output and clean up any project or coordination changes before restarting.
Serial mode does not require tmux, per-role clones, worktrees, or reviewer/tester leases, and the local lock prevents two serial loops from sharing one clone. Those pieces are future parallel-worker concerns. Parallel mode should use separate clones/worktrees and additional lease rules before multiple roles edit or mutate coordination concurrently.
See designs/serial-role-automation.md for the full design.
See designs/agent-coordination.md for the full design.
pi-env does not pin or install pi-coding-agent through Nix. The wrappers
expect a pi executable to already exist on the host PATH, then pi-bwrap
bind-mounts the host/global Pi installation read-only into the sandbox.
When a new Pi version is available, upgrade Pi on the host, outside pi-start /
pi-bwrap:
npm install -g --ignore-scripts @earendil-works/pi-coding-agent@latest
pi --versionThen continue using pi-env normally:
nix develop
pi-startDo not run Pi self-updates from inside the Bubblewrap sandbox: /usr/local/bin
and the global Pi npm package are mounted read-only there.
If your current global Pi supports self-update and your user has permission to update the global install, this can also be run on the host:
pi update --selfThat updates Pi itself. It is separate from updating a project's pi-env flake
input. If another project consumes this repository as a flake input and pi-env
changed, update that input in the consuming project with:
nix flake update pi-envRun the whole project test suite with:
tests/run.shCoordination helper smoke tests:
tests/agent-coord-blackbox.sh
tests/agent-coord-concurrency.sh
tests/agent-coord-lint.sh
tests/coordination-items-closed-or-done.shRole-manager package, schema/template, loader, and command smoke tests:
tests/role-manager-package.sh
tests/role-manager-schema.sh
tests/role-manager-loader.sh
tests/role-manager-commands.shItem-matched tests live in the project repository under tests/items/ and
match the item ID by filename stem. Project item tests mirror the root item
type, but not issue lifecycle status directories:
.pi-env/coordination/issues/closed/PIENV-ISS-20260607-204155-001.yaml
tests/items/issues/PIENV-ISS-20260607-204155-001.sh
.pi-env/coordination/requirements/PIENV-FRQ-20260607-204155-001.yaml
tests/items/requirements/PIENV-FRQ-20260607-204155-001.sh
- Pi's built-in tool list is
read,bash,edit,write,grep,find,ls.pi-startallowlists those by default. If you need extension/custom tools too, include them inPI_BWRAP_DEFAULT_TOOLSor callpi-bwrapwith your own--toolslist. - Global Pi extensions and globally installed Pi packages are exposed read-only
from the host agent directory by default. Disable this with
PI_BWRAP_IMPORT_EXTENSIONS=0. Project-local extensions/packages under.pi/are available because the project is mounted at/workspace. - Use
gitthrough thebashtool unless you install/register a separate Git tool extension. - Bubblewrap limits filesystem/environment exposure. It does not provide
domain-level network allowlists. For tighter network policy, disable network
with
PI_BWRAP_NET=0, use an external firewall/proxy, or add Pi's sandbox extension as an additional layer forbashcommands.