Pi

Minimal, extensible TypeScript coding agent with pinned interpreter/package verification, external sandbox policy, and local-provider side profiles.

Last updated

Cloud API Local Provider TypeScript

1. Installation

Prerequisites

  • macOS 13+ or Linux (kernel 5.13+)
  • Node.js 20.6+
  • API key for at least one cloud provider, or a local OpenAI-compatible endpoint

Install Pi

npm install -g @mariozechner/pi-coding-agent

Install the preferred stack

brew tap nvk/tap
brew install nvk/tap/agent-bondage
brew install nvk/tap/envchain-xtra
brew install nono

Verify

pi --version
bondage --help
nono --version
envchain --version

2. nono Profile

Pi is structurally similar to Claude Code — a Node.js agent with bash, read, write, and edit tools. In the preferred setup, bondage verifies both the Pi entrypoint and the Node interpreter before handing execution to nono.

What the sandbox allows

ResourceAccessWhy
Current working directoryRead + WriteProject files
~/.pi/agent/Read + WriteConfig, sessions, extensions
Provider API endpointsNetworkAPI calls
/dev/tty, /dev/nullRead + WriteTerminal I/O
/dev/urandomReadCrypto randomness
/tmp/Read + WriteBash output temp files
Extension risk: Pi's own docs warn that "packages run with full system access. Extensions execute arbitrary code." The nono sandbox is the defense — even malicious extensions can't escape kernel-level restrictions.

3. envchain-xtra

Pi supports many providers. Store keys in one envchain-xtra namespace and let Pi read them from the environment automatically — no plaintext config file needed.

Store your API key

# For Anthropic (most common)
envchain --set pi ANTHROPIC_API_KEY

# Add more providers to the same namespace
envchain --set pi OPENAI_API_KEY
envchain --set pi GEMINI_API_KEY

Key provider env vars

ProviderEnv Var
AnthropicANTHROPIC_API_KEY
OpenAIOPENAI_API_KEY
Google GeminiGEMINI_API_KEY
DeepSeekDEEPSEEK_API_KEY
GroqGROQ_API_KEY
xAIXAI_API_KEY
OpenRouterOPENROUTER_API_KEY
Credential resolution order: CLI --api-key flag, then auth.json, then environment variable. With envchain, skip auth.json entirely — no plaintext keys on disk.

4. bondage Wrapper

Add this thin wrapper to your shell config:

pi() {
  bondage exec pi ~/.config/bondage/bondage.conf -- "$@"
}

Sample stack snippets

Assuming your shared [global] block already exists in ~/.config/bondage/bondage.conf, this is a script-backed Pi shape to adapt:

# ~/.config/bondage/bondage.conf
[profile "pi"]
namespace = pi
use_envchain = true
use_nono = true
nono_profile = pi
touch_policy = prompt
target_kind = script
target = /absolute/path/to/pi/dist/cli.js
target_fp = sha256:replace-me
interpreter = /absolute/path/to/node
interpreter_fp = sha256:replace-me
package_root = /absolute/path/to/pi
package_tree_fp = sha256:replace-me
nono_allow_cwd = true
nono_allow_file = /dev/tty
nono_allow_file = /dev/null
nono_read_file = /dev/urandom
ensure_dir = /Users/you/.pi
{
  "extends": "default",
  "meta": {
    "name": "pi",
    "description": "Pi with envchain-backed provider keys and writable local state"
  },
  "policy": {
    "add_deny_access": ["/Volumes"],
    "add_allow_readwrite": [
      "$HOME/.pi"
    ]
  },
  "workdir": {
    "access": "readwrite"
  }
}

How it works

  1. The shell wrapper passes only the profile name and your arguments
  2. bondage verifies the exact Pi entrypoint, Node runtime, and package tree
  3. envchain-xtra injects the selected provider keys
  4. nono applies the filesystem and network policy

Reduce startup network

# Add to ~/.zshrc
export PI_OFFLINE=1
export PI_SKIP_VERSION_CHECK=1

The current package scan found offline/version-check controls, not a dedicated PI_TELEMETRY switch. Provider calls still follow the model endpoint you select.

Reload your shell:

source ~/.zshrc

5. Local pi-ds4 Profile

For local ds4, use a separate Pi agent directory and a separate bondage profile. The goal is to make pi-ds4 disposable without changing the normal pi profile, default provider selection, or cloud credentials. Start with a no-tools chat profile; keep tool-use experiments isolated because direct ds4 tool requests can stall the server.

Install the upstream ds4 extension

The default local ds4 path for Pi is now the upstream mitsuhiko/pi-ds4 extension. Install it into the same isolated Pi state directory used by the side profile:

PI_CODING_AGENT_DIR="$HOME/.local/state/agent-stack/pi-ds4" \
  pi install https://github.com/mitsuhiko/pi-ds4

PI_CODING_AGENT_DIR="$HOME/.local/state/agent-stack/pi-ds4" \
  pi --model ds4/deepseek-v4-flash --thinking off -p 'reply with OK'
Current default: let mitsuhiko/pi-ds4 register the model and manage ds4-server. It starts the server on demand, chooses q2 on 128GB Macs, keeps leases under ~/.pi/ds4, and exposes /ds4 for logs. Launch the public wrapper with --no-tools until the local tool path passes direct server tests.

Side profile shape

# ~/.config/bondage/bondage.conf
[profile "pi-ds4"]
use_envchain = false
use_nono = true
nono_profile = custom-pi-ds4
target_kind = script
target = /absolute/path/to/pi/dist/cli.js
target_fp = sha256:replace-me
interpreter = /absolute/path/to/node
interpreter_fp = sha256:replace-me
package_root = /absolute/path/to/pi
package_tree_fp = sha256:replace-me
ensure_dir = /Users/you/.pi-ds4
env_set = PI_CODING_AGENT_DIR=/Users/you/.pi-ds4
env_set = SHELL=/bin/zsh
target_arg = --no-tools
target_arg = --model
target_arg = ds4/deepseek-v4-flash
target_arg = --thinking
target_arg = off

This profile assumes the upstream extension has already been installed into PI_CODING_AGENT_DIR. The extension owns model registration and ds4 lifecycle; the bondage profile supplies sandboxing and the model selector. Use Claude/Codex ds4 profiles for local file editing while Pi ds4 tool-use remains experimental.

Do not overuse stop sequences. A stop sequence like "Let me" or "The user wants" can make local models return an empty message before they call read or edit. Put behavior rules in the system prompt instead.

Direct benchmark profile

Keep a no-nono profile only for benchmark runs and nested-sandbox environments. That lets you measure Pi plus ds4 without weakening the normal pi-ds4 profile.

[profile "pi-ds4-rawdog"]
inherits = git-env,pi-script-target
use_envchain = false
use_nono = false
touch_policy = none
ensure_dir = /Users/you/.pi-ds4
env_set = PI_CODING_AGENT_DIR=/Users/you/.pi-ds4
env_set = SHELL=/bin/zsh
target_arg = --no-tools
target_arg = --model
target_arg = ds4/deepseek-v4-flash
target_arg = --thinking
target_arg = off

Shell shortcut

pi-ds4-install() {
  PI_CODING_AGENT_DIR="$HOME/.local/state/agent-stack/pi-ds4" \
    pi install https://github.com/mitsuhiko/pi-ds4
}

pi-ds4() {
  bondage exec pi-ds4 ~/.config/bondage/bondage.conf -- "$@"
}

pi-ds4-rawdog() {
  bondage exec pi-ds4-rawdog ~/.config/bondage/bondage.conf -- "$@"
}

pi-ds4-bench() {
  pi-ds4-rawdog -p "${*:-reply with OK}"
}

Smoke test

pi-ds4 -p 'reply with OK'
pi-ds4-bench

With the current upstream extension, print-mode smoke tests may include DeepSeek-style reasoning text before the final answer even with --thinking off. Count the smoke test as passing when it reaches the local ds4 server and ends with OK. In local testing, direct ds4 requests with OpenAI-style tools stalled before producing a file, so the public wrapper keeps tools disabled by default.

Current upstream smoke benchmark

PathResultNote
pi-ds4-bench no-tools~3-4 sReached local ds4 and ended with OK
Direct ds4 tools requeststalled over 30 sNo file produced; do not enable by default

Legacy manual-profile benchmark

These numbers are from the older manual models.json + custom extension setup. Keep them as a harness-overhead baseline, not as the default upstream-extension recommendation.

SettingPass rateAverageOKReadEdit
16k / 20486/613.8 s8.6 s9.7 s23.2 s
32k / 20486/616.4 s9.3 s12.0 s27.7 s
32k / 40966/619.9 s11.8 s15.5 s32.4 s
64k / 40966/621.0 s13.4 s16.7 s32.8 s

6. Verification

Test the full chain

bondage verify pi ~/.config/bondage/bondage.conf
bondage chain pi ~/.config/bondage/bondage.conf -- --help
pi --help

Confirm no plaintext keys

# Should return nothing
grep -r "sk-ant\|sk-proj" ~/.pi/ 2>/dev/null

Troubleshooting

SymptomCauseFix
"Invalid API key" envchain namespace or key name wrong envchain --set pi ANTHROPIC_API_KEY
Session data not saved nono blocking ~/.pi/ Add --allow-file ~/.pi/ to wrapper
Extensions fail to install npm cache blocked by sandbox Add --allow-file ~/.npm/ to wrapper
Local ds4 fails near full context Client advertised the full server window Use the upstream mitsuhiko/pi-ds4 chat path with --no-tools; test direct server tool requests before enabling Pi file edits
Blank response instead of edit A stop sequence cut off the model before tool use Remove behavioral stop strings and enforce tool behavior through the system prompt
Shell name → bondage → envchain-xtra → nono → Pi Convenience Launch policy Secret release Kernel sandbox Actual agent