git clone <this-repo> && cd pi-harness-nvidia-openshell
./scripts/setup.shThis is idempotent and re-runnable: it inits the OpenShell submodule, links
the relevant OpenShell agent skills (openshell-cli, generate-sandbox-policy,
debug-openshell-cluster, debug-inference) into .agents/skills/,
sanity-checks openshell/docker/pi, and asks whether to install the
pis command (pi-in-sandbox wrapper, see below). Restart pi after running it
so the new skills are picked up.
Start or restart the local gateway with: brew services restart openshell
Register it with the OpenShell CLI: openshell gateway add https://localhost:17670 --local --name openshell
To start nvidia/openshell/openshell now and restart at login: brew services start nvidia/openshell/openshell
Or, if you don't want/need a background service you can just run: /opt/homebrew/opt/openshell/libexec/openshell-gateway-homebrew-service openshell: restarting OpenShell Homebrew service...
On Success:
==> Successfully started openshell (label: homebrew.mxcl.openshell)
openshell: registering local gateway as jayce...
✓ Gateway 'openshell' added and set as active
Endpoint: https://localhost:17670
Type: local
✓ TLS certificates present
Inference routing (https://inference.local)
Gateway-side (run once on the host):
./scripts/create_vllm_provider.sh # openai-compatible provider -> devbox vLLM
./scripts/set_vllm_provider_for_inference.sh # route inference.local to it
openshell inference update --timeout 300 # long generations; hot-reloads in ~5sInside the sandbox, https://inference.local is the only inference path:
- HTTPS-only, credentials injected by the gateway (no API keys in the sandbox)
- The router strips caller
Authorizationand rewritesmodelto the configured one - OpenAI-compatible patterns only: /v1/chat/completions, /v1/completions, /v1/responses, /v1/embeddings, /v1/models
Pi is preconfigured via pi-config/ (baked into the image at /sandbox/.pi/agent/):
models.jsonregisters provideropenshellathttps://inference.local/v1(openai-completions, placeholder apiKey, Qwen3.8-27B with qwen-chat-template thinking)settings.jsonsets it as the default model, so barepiworks- The npm extension packages listed in
settings.jsonare pinned and pre-installed into/sandbox/.pi/agent/npm/at image build time (Dockerfile). pi finds them at startup and never needsregistry.npmjs.orgegress — the sandbox policy grants it none. To update a package, bump the pin inpi-config/settings.jsonand theDockerfiletogether, then rebuild.
Primary workflow — scripts/pi-sandbox (installed as pis by setup.sh):
fresh sandbox per session, git round-trip, automatic cleanup:
./scripts/build.sh # after Dockerfile/policy changes
cd <project> && pis # fresh sandbox for this project
cd <project> && git pull # collect the agent's commitspis defaults to your current directory, so it works from anywhere: cd
to a project, type pis, and the sandbox setup (create, upload, git config)
happens automatically in the background before the pi TUI starts. The host
pi binary is untouched. (setup.sh asks whether to install it; the
installer drops a wrapper into a writable PATH dir, preferring
~/.local/bin. Or manually:
ln -sf <repo>/scripts/pi-sandbox ~/.local/bin/pis. pi-sandbox /path also
accepts an explicit project dir.)
- Creates a one-shot sandbox with an auto-generated name (recovered by
diffing
sandbox listbefore/after create), uploads the project including.git(the uploader skips.gitotherwise, so it gets a second explicit upload), and runspiinside at/sandbox/<project>. - The agent commits and pushes from inside: the push rule is enabled in
policy.yaml(git smart HTTPgit-receive-pack), and a credential helper is preconfigured to use the token from your hostghlogin (injected with--env GITHUB_TOKEN, so it never lands in the sandbox's git config). - If your
originis an SSH URL (git@github.com:...), the sandbox copy of origin is rewritten to the equivalenthttps://URL: SSH can't be pushed from the sandbox (no egress to port 22, no keys), and the credential helper only applies to HTTPS. Your hostoriginis untouched. - On clean pi exit the sandbox is deleted. On abnormal exit (crash, Ctrl-C)
it is kept and the name is printed:
openshell sandbox connect <name>to continue,openshell sandbox delete <name>to discard.
The clean OpenShell mechanism (provider v2: gateway holds the secret, the
sandbox gets a placeholder, the proxy rewrites it) works for gh/API
traffic — see provider-profiles/github-push.yaml (imported profile +
github-push provider instance; placeholder resolution verified against
api.github.com). But the proxy only rewrites placeholders in Bearer
headers, URL paths, and queries — not inside base64-decoded Basic auth. Git
over HTTPS can only authenticate via Basic, and GitHub's git endpoints
reject Authorization: Bearer. So git pushes with the real token from
--env; the egress policy still confines it (only git/gh may reach
GitHub hosts). If the proxy ever rewrites Basic auth, the provider path
becomes fully secret-free — this gap is worth an issue upstream.
./scripts/run_sandbox.sh /path/to/project keeps a fixed-name sandbox
(pi-sandbox) alive across sessions and uploads the project (without
.git) to /sandbox/<project>. Round-trip work with
openshell sandbox upload/download <name> <path> <dest> (.gitignore
respected on upload; download extracts contents directly into the
destination dir). Useful for scratch work that does not round-trip via git.
Verify inside any sandbox (e.g. after openshell sandbox connect):
curl https://inference.local/v1/chat/completions -H 'Content-Type: application/json' \
-d '{"messages":[{"role":"user","content":"hi"}],"max_tokens":16}'
pi --list-models | grep openshell
pi -p "Reply with exactly one word: hi"Notes:
- A Qwen thinking model served by vLLM thinks by default; pi maps its thinking
level to
chat_template_kwargs.enable_thinking(off by default).pi --thinking highto opt in. openshell sandbox listshows phaseErrorwhen the main process exits (e.g. Ctrl-C in the shell). The sandbox is kept by default; useopenshell sandbox start, recreate, or-- sleep infinityas the main process.