Running Pi
This guide covers running the Pi coding agent in a Moat container.
Pi is a model-agnostic, bring-your-own-key coding agent. Unlike the other agents, Pi has no credential of its own — it runs against whichever model backend you grant. Moat supports three Pi backends today: anthropic, openai, and lunaroute. Any other backend fails hard.
Prerequisites
- Moat installed
- An Anthropic API key from console.anthropic.com, an OpenAI API key from platform.openai.com, or a LunaRoute API key (
lr_...) from lunaroute.com
Granting a backend
Pi uses your existing anthropic or openai grant — the same credential the other agents use — or a lunaroute grant. Grant one:
export ANTHROPIC_API_KEY="sk-ant-..."
moat grant anthropic
# ...or
export OPENAI_API_KEY="sk-..."
moat grant openai
# ...or
export LUNAROUTE_API_KEY="lr_..."
moat grant lunaroute
The credential is stored encrypted and never enters the container. Moat’s proxy injects the real token at the network layer. See Credential management for details.
Choosing a backend
Pi decides which backend to use as follows:
--providerflag orpi.providerinmoat.yaml, if set. Must beanthropic,openai, orlunaroute, and the matching grant must be configured.- Otherwise, the backend is inferred from the single configured grant.
- If more than one grant is configured and no provider is set, the run fails hard and asks you to choose.
- If no supported grant is configured, the run fails before a container is created.
# One grant configured — inferred automatically
moat pi
# Several grants configured — choose explicitly
moat pi --provider openai
# Pin a model
moat pi --provider anthropic --model claude-opus-4-8
These checks run before any container is created, so misconfiguration fails fast with an actionable message.
LunaRoute
LunaRoute serves many models behind one key. Pi reaches it through LunaRoute’s own Pi extension, @lunaroute/pi-extension, which registers a lunaroute provider and fetches the model list from the gateway.
moat grant lunaroute
moat pi --provider lunaroute
Pi starts on the first model LunaRoute lists. Switch with /model inside Pi, or pin one with --model / pi.model.
When the backend is lunaroute, moat pi:
- Adds
npm:@lunaroute/pi-extensionto the image’s Pi packages (unlesspi.packagesalready lists it, pinned or not). - Writes a placeholder LunaRoute login to
~/.pi/agent/auth.json. The proxy replaces it with the real key:Authorization: Bearerongw.lunaroute.comandLUNAROUTE-API-KEYonmcp.lunaroute.com(used by the extension’s web tools). The key is sent to no other host. - Allows Pi’s startup network calls. Other backends run with
PI_OFFLINE=1, which would stop the extension from loading its models. - Allows
gw.lunaroute.comandmcp.lunaroute.comundernetwork.policy: strict. - Loads LunaRoute’s model list before Pi starts. Pi picks its model before it fetches any model list, and in
-pmode it never fetches one, so a new container would otherwise fail withUnknown provider "lunaroute". The step runspi --mode rpcuntil the extension has saved the list to~/.pi/agent/models-store.jsonand usually takes 1–2 seconds; the request goes through the proxy like any other. If the gateway can’t be reached, Moat says so after 15 seconds and Pi then fails to find the provider.
Use moat pi for LunaRoute. moat run with a pi command skips both the extension and the model-list step, so Pi has no LunaRoute models; Moat warns when a run holds the lunaroute grant without the extension.
Use moat grant lunaroute, not moat grant anthropic --base-url, for Pi. The --base-url grant is how Claude Code reaches LunaRoute. Pi’s anthropic backend only talks to api.anthropic.com, where a gateway key is never sent, so moat pi refuses to start on it and points you at moat grant lunaroute. The same lr_ key works for both grants.
Extensions in pi.packages also load during the model-list step, so each one adds to startup time.
A working example is in examples/agent-pi-lunaroute. It also installs the Pi packages the LunaRoute team recommends, pinned to tested versions; with all of them the model-list step takes about 5 seconds instead of 2.
Running Pi
Interactive:
moat pi
moat pi ./my-project
Non-interactive (headless):
moat pi -p "explain this codebase"
moat pi -p "fix the bug in main.py"
Configuration
# moat.yaml
agent: pi
grants:
- anthropic
pi:
provider: anthropic # optional; inferred from the grant when unset
model: claude-opus-4-8 # optional; Pi's per-provider default when unset
See the moat.yaml reference for the full field list.
Packages
Declare Pi packages (extensions, skills, prompt templates, themes) in pi.packages. Moat runs pi install for each at image build time, so they are baked into a reproducible, cached layer — no runtime install, no network needed at run time.
pi:
provider: anthropic
packages:
- "npm:@acme/pi-reviewer@1.2.0"
- "git:github.com/acme/pi-skills@v3"
Only remote sources are accepted (npm: / git: / https:// / ssh://); local paths are rejected. Pin exact versions/refs — Pi has no lockfile. If a package needs a runtime tool, add it to dependencies:.
Safety defaults
Every moat pi image bakes a safe ~/.pi/agent/settings.json:
defaultProjectTrust: never— a checked-out repo’s own.pi/config and extensions (which are arbitrary code) do not auto-load. Your project’sAGENTS.md/CLAUDE.mdstill load (that is inherent to Pi).- Telemetry and analytics off; quiet startup.
Pi config can redirect model traffic to any host (via baseUrl, custom providers, or extensions), so only the network policy actually constrains egress. moat pi warns when it runs under a permissive policy — prefer network.policy: strict for untrusted work.
Runtime context
Moat injects a description of the sandbox (workspace, grants, network policy, Docker access) into Pi’s system prompt via Pi’s --append-system-prompt, so it augments rather than replaces your own AGENTS.md / CLAUDE.md.
Not yet supported
- Other backends — only
anthropic,openai, andlunarouteare wired up. Gemini, Bedrock, and the rest are planned. pi.mcp— Pi core has no MCP configuration format to target.pi.sync_logs— session-log syncing is a planned follow-up.- Subscription (OAuth) login — v1 uses API-key grants only.