Run Pi with OpenRouter

View as Markdown

In this tutorial, we’ll run Pi, a terminal coding agent, inside an OpenShell sandbox. Pi works with many model providers out of the box, including OpenRouter, which we use here.

We’ll build a Pi image, give the sandbox access to OpenRouter, and start Pi. The sandbox lets Pi reach OpenRouter but nothing else on the network, and Pi never sees your real API key.

Prerequisites

You’ll need:

  • A working OpenShell installation with an active gateway. Refer to Installation.
  • Docker or Podman to build the image.
  • An OpenRouter API key.
1

Build the Pi Image

Pi doesn’t publish a container image, so we’ll build one from its npm package, based on Pi’s container guide. Save this Dockerfile as Dockerfile.pi:

FROM node:24-bookworm-slim
ARG PI_VERSION=latest
RUN apt-get update \
&& apt-get install -y --no-install-recommends bash ca-certificates fd-find git ripgrep \
&& ln -s /usr/bin/fdfind /usr/local/bin/fd \
&& rm -rf /var/lib/apt/lists/*
RUN npm install -g --ignore-scripts "@earendil-works/pi-coding-agent@${PI_VERSION}"
RUN mkdir -p /workspace && chown node:node /workspace
USER node
WORKDIR /workspace
ENV PI_CODING_AGENT_DIR=/tmp/pi-agent

We add two things to Pi’s guide because the sandbox limits what Pi can reach:

  • We install fd. Pi uses fd and ripgrep to search files, and if they’re missing, it downloads them from GitHub on first use. The sandbox blocks that download, so the image includes both.
  • We set PI_CODING_AGENT_DIR so Pi keeps its settings and sessions in /tmp, one of the few places the sandbox lets Pi write.

Now build the image:

docker build -t pi-agent:local -f Dockerfile.pi .

If your gateway uses Podman, build with podman build -t localhost/pi-agent:local -f Dockerfile.pi . and use localhost/pi-agent:local in the commands below. If your gateway runs on another machine, push the image to a registry the gateway can pull from, and use that image reference with --from.

2

Give Pi Access to OpenRouter

A sandbox has no network access until you grant it. To let Pi reach OpenRouter, we’ll create a provider, which stores your API key and opens access to the service. Pi only ever sees a placeholder for the key. OpenShell swaps in the real key only when Pi calls OpenRouter.

A provider is built from a provider profile, which describes the service. Save this one as provider-openrouter.yaml:

id: openrouter
display_name: OpenRouter
description: Direct OpenRouter inference access
category: inference
inference_capable: true
credentials:
- name: api_key
description: OpenRouter API key
env_vars:
- OPENROUTER_API_KEY
required: true
auth_style: bearer
header_name: authorization
discovery:
credentials:
- api_key
endpoints:
- host: openrouter.ai
port: 443
protocol: rest
access: read-write
enforcement: enforce
binaries:
- /usr/local/bin/node

Here’s what the main sections do:

  • credentials declares the API key. Pi finds the placeholder in OPENROUTER_API_KEY.
  • endpoints allows only openrouter.ai, and OpenShell sends the key nowhere else.
  • binaries allows only node to use this access. Pi runs on Node.js, so that includes Pi.

Check the profile, import it, and create a provider named openrouter:

openshell profile lint -f provider-openrouter.yaml
openshell profile import -f provider-openrouter.yaml
OPENROUTER_API_KEY=your-key \
openshell provider create \
--name openrouter \
--type openrouter \
--from-existing

Replace your-key with your OpenRouter API key, or drop that prefix if OPENROUTER_API_KEY is already set in your shell. --from-existing tells OpenShell to read the key from that variable.

3

Start Pi in a Sandbox

Next, create a sandbox from the image, attach the provider, and start Pi:

openshell sandbox create \
--name pi \
--from pi-agent:local \
--provider openrouter \
-- pi

Everything after -- runs inside the sandbox, so Pi and any commands it runs stay contained.

Pi starts with its default OpenRouter model. To pick a different one, type /model and choose any OpenRouter model from the list. OpenRouter usually has some free models, marked with a :free suffix, which are handy for trying this out without spending credits.

Then give Pi a try:

Explain what files are available in this workspace.

The workspace starts empty, but we’ll give Pi your own code in a later step.

4

Inspect the Sandbox

While Pi is running, open another terminal and take a look at the sandbox’s status, access, providers, and logs:

openshell sandbox list
openshell policy get pi --full
openshell sandbox provider list pi
openshell logs pi --tail

openshell logs streams the sandbox’s activity, including any connections it blocks. When you’re done, type /quit in Pi to exit.

5

Work on a Local Project

To point Pi at your own code, run this from your project directory. --upload copies the directory into the sandbox before Pi starts, skipping anything .gitignore excludes.

openshell sandbox create \
--name pi-project \
--from pi-agent:local \
--provider openrouter \
--upload .:/workspace \
-- pi

Pi’s changes stay inside the sandbox. To copy them back, run openshell sandbox download from another terminal while Pi is still running. Refer to Transfer Files.

6

Clean Up

When you’re finished, delete both sandboxes:

openshell sandbox delete pi pi-project

Troubleshooting

  • If the gateway can’t find pi-agent:local, make sure you built the image with the same container engine your gateway uses, or pushed it to a registry the gateway can reach.
  • If Pi’s requests to OpenRouter are denied, make sure the binaries path in the profile matches where node is installed in your image. In the image above, that’s /usr/local/bin/node.

Next Steps

  • To give Pi more tools, add them to the image. To let Pi reach package registries, source hosts, or other services, grant access with a sandbox policy.
  • To use a different model service, write a provider profile for it. The providers/ directory has examples, and Profiles describes each field.
  • To learn about images, resources, file transfer, and the sandbox lifecycle, refer to Sandboxes.