# Recipes Concrete invocations. `pi` below is whatever you aliased to `ai-sandbox`. ## A first session ```bash cd ~/Projects/myapp ai-sandbox doctor # is this machine ready? pi --allow aipi.reeseapps.com --env AI_API_TOKEN ``` With your pi config bridged, the token is found on its own, so the allowlist is the only thing you have to say: ```bash pi --allow aipi.reeseapps.com ``` Same thing, but remembered by the repo (so teammates review the policy, not just your shell history): ```bash mkdir -p .ai-sandbox printf 'aipi.reeseapps.com\n' > .ai-sandbox/allow pi ``` ## Pi's own configuration The bridge rewrites your host `~/.pi/agent/{models,settings}.json` instead of copying it: providers, models, thinking budgets and preferences come along, the credential becomes `"$AI_API_TOKEN"`, and anything that only works on the host (a `!command` header, a path into `~`) is dropped with a warning. `auth.json` is never bridged. ```bash # the everyday case: same provider as the host, token from the environment pi --allow aipi.reeseapps.com # a model your gateway serves, chosen per run pi --allow aipi.reeseapps.com --model juggernaut # keep the model's endpoint pinned by IP as well, on a LAN inference host pi --allow aipi.reeseapps.com --allow 10.20.0.7:11434 # a different variable name for the credential pi --allow aipi.reeseapps.com --pi-token-env GW_TOKEN # do not hand the token over automatically: the run then fails closed unless you # pass it yourself, which is what you want in shared scripts pi --allow aipi.reeseapps.com --env AI_API_TOKEN --no-pi-token-env # no bridge: pi starts with its own defaults inside the volume (and no thinking policy) pi --no-pi-config --allow aipi.reeseapps.com ``` `models.json` is kept in step with the host unless you edit it in the sandbox, where your edit then wins; deleting it inside the sandbox re-seeds it. `settings.json` is merged per key, so a preference you changed in the sandbox stays changed and every one you did not touch follows the host again. ### How hard it thinks `defaultThinkingLevel` is policy, not preference: `xhigh` unless you say otherwise, and re-applied every run rather than remembered from whatever the last sandbox set. ```bash # the everyday case: xhigh pi --allow aipi.reeseapps.com # one-off: the top of pi's range, or no reasoning at all for a mechanical job pi --thinking max --allow aipi.reeseapps.com pi --thinking off --net allow --allow registry.npmjs.org -- npm ci # let settings.json decide, the way it did before this was policy pi --thinking host --allow aipi.reeseapps.com # per repo, chosen by .ai-sandbox/config # thinking = high ``` Nothing is written for `codex`, `claude` or a command after `--`: those read their own configuration and ai-sandbox does not touch it. ## Your skills and standing instructions `~/.agents` is shared read-only at `/home/sandbox/.agents` by default, so the sandboxed harness finds the same skills, prompt templates and `AGENTS.md` you use on the host. A project's own `.agents/` needs nothing: it arrives with the project mount, and harnesses that read both see the project's alongside yours. ```bash # the everyday case: your skills are simply there pi --allow aipi.reeseapps.com # a run where the harness should not have them: reviewing untrusted code, or a # skills set that mentions internal hosts you would rather it not repeat back pi --net none --no-agents -- bash -lc 'make lint' # a curated set for sandboxes -- e.g. host skills stay yours, this dir is what you share pi --agents-dir ~/.config/ai-sandbox/agents --allow aipi.reeseapps.com # two sets, per trust level, chosen by config file in a repo # .ai-sandbox/config: agents_dir = ./.agents-shared ``` It is read-only and there is no flag to change that: a skill is an instruction you follow on the host later, so letting a sandbox write one would let it persist past the run. If a workflow really needs to *author* skills, do it in the project directory and move the result into `~/.agents` yourself, where you can read it first. ## Per harness ```bash # pi (baked into the image, config bridged) pi --allow aipi.reeseapps.com # pi with a model chosen on the command line pi --harness 'pi --model turbo' --allow aipi.reeseapps.com # a gateway that speaks a different protocol still needs only its host pi --allow gateway.internal:8443 --env AI_API_TOKEN # OpenAI Codex CLI / Claude Code: they read their own config files, so mount or # generate what they need and allow their endpoints; the pi bridge does not apply pi --allow chatgpt.com --env CODEX_API_KEY pi --allow api.anthropic.com --env ANTHROPIC_API_KEY # just a shell in the sandbox pi --shell # any other build/test command, no network needed pi --net none -- make test ``` ## Per ecosystem Only the endpoints the tool actually needs. Everything else stays blocked, so a typosquatted package from an unexpected host fails loudly instead of quietly working. ```bash # npm / node pi --allow registry.npmjs.org --allow nodejs.org --allow github.com # python: pypi needs both of these (the CDN that serves the files is separate) pi --allow pypi.org --allow files.pythonhosted.org # rust pi --allow crates.io --allow static.crates.io --allow index.crates.io # go pi --allow proxy.golang.org --allow sum.golang.org # docker/podman images from within a build pi --allow registry-1.docker.io --allow auth.docker.io --allow production.cloudflare.docker.com # a project that also needs its dev database (non-HTTP: pinned IP + port) pi --allow aipi.reeseapps.com --allow 10.20.0.5:5432 ``` ## Git ```bash # fetch/push over https with a token in the environment pi --allow github.com --env GH_TOKEN -- git push origin main # ssh: the key has to be mounted deliberately (an explicit, reviewable grant) pi --allow github.com --mount ~/.ssh:ro -- git push origin main # read-only view of a sibling repo for context pi --allow aipi.reeseapps.com --mount ../docs:/docs:ro ``` ## Tightening a run ```bash # nothing persistent, nothing readable, nothing writable outside the repo pi --net none --ephemeral-home --read-only -- make build # an entirely blank sandbox for untrusted code: no project, no home, no skills pi --net none --no-cwd --ephemeral-home --read-only --no-agents -- bash # CI-ish: refuse to start if the policy or environment is not exactly right pi --strict-args --net allow --allow-file .ai-sandbox/allow --env-file .ci/env -- npm ci ``` ## Long-lived gate shared by several sandboxes ```bash ai-sandbox gate up --allow aipi.reeseapps.com --allow registry.npmjs.org # name=aisb-gate # port=18081 # proxy_env=export HTTPS_PROXY=http://100.64.0.1:18081 ai-sandbox --proxy 100.64.0.1:18081 --allow aipi.reeseapps.com --shell ai-sandbox gate logs # what did it allow/deny? ai-sandbox gate down ``` ## Debugging ```bash ai-sandbox --dry-run --allow example.com --env KEY # the exact podman invocation ai-sandbox --print-policy # effective policy, no secrets ai-sandbox --print-entrypoint | less # the enforcement script ai-sandbox --keep --net none -- false # leave the container for forensics ai-sandbox ps AISB_GATE_KEEP=1 pi --allow example.com --shell # keep the gate after exit ai-sandbox clean --all # "the ruleset would not install" (exit 77): check the host podman info | grep -i rootless grep "$(id -un)" /etc/subuid ``` ## Making it the default for a project `.ai-sandbox/config`: ```ini net = allow harness = pi cpus = 8 memory = 8g mount = ../specs:/specs:ro env = AI_API_TOKEN ``` `.ai-sandbox/allow`: ``` aipi.reeseapps.com registry.npmjs.org ``` Then `pi` alone in that directory is the whole invocation — and because both files are in the repository, the sandbox policy gets reviewed with the code.