Skip to main content
Guides

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

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:

  1. --provider flag or pi.provider in moat.yaml, if set. Must be anthropic, openai, or lunaroute, and the matching grant must be configured.
  2. Otherwise, the backend is inferred from the single configured grant.
  3. If more than one grant is configured and no provider is set, the run fails hard and asks you to choose.
  4. 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-extension to the image’s Pi packages (unless pi.packages already lists it, pinned or not).
  • Writes a placeholder LunaRoute login to ~/.pi/agent/auth.json. The proxy replaces it with the real key: Authorization: Bearer on gw.lunaroute.com and LUNAROUTE-API-KEY on mcp.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.com and mcp.lunaroute.com under network.policy: strict.
  • Loads LunaRoute’s model list before Pi starts. Pi picks its model before it fetches any model list, and in -p mode it never fetches one, so a new container would otherwise fail with Unknown provider "lunaroute". The step runs pi --mode rpc until the extension has saved the list to ~/.pi/agent/models-store.json and 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’s AGENTS.md/CLAUDE.md still 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, and lunaroute are 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.