Skip to content

Pi

pi is fullsend's second agent runtime, opt-in per org or repo. It reaches models Claude Code cannot — Grok and Gemini alongside Claude — through the same sandbox, credentials and egress policy.

bash
fullsend run triage --runtime pi --model xai-vertex/xai/grok-4.6

Selecting it, and how it compares to Claude Code, is in Agent runtimes. This page is what changes once you are on it.

Models and providers

A model on pi is provider/id. Aliases and bare ids still work — opus/sonnet/haiku resolve through fullsend's table, and a bare id gets the provider from FULLSEND_PI_PROVIDER (default anthropic-vertex).

ModelSpecProvider
Claudeanthropic-vertex/claude-opus-4-6vendored extension
Geminigoogle-vertex/gemini-3.7-flashpi built-in
Grokxai-vertex/xai/grok-4.6vendored extension
GPTopenai/gpt-5.6-lunapi built-in

Grok's spec has three segments on purpose. pi sends the model id on the wire verbatim and Vertex wants the publisher-qualified xai/grok-4.6, so the id keeps its slash. Use the full xai-vertex/xai/grok-4.6; a bare xai/grok-4.6 would otherwise reach pi's built-in xai provider, which talks to xAI's own API and wants XAI_API_KEY. fullsend normalises the short form and a bare id under FULLSEND_PI_PROVIDER=xai-vertex, case-insensitively, so both land on the canonical spec.

GPT via OpenAI needs no API key in CI: the runner exchanges the job's GitHub identity for a short-lived OpenAI token (give fullsend three identifiers with fullsend github setup --openai-* or repository variables — see OpenAI Workload Identity; GitHub Actions only) and keeps it in a provider that belongs to this run, refreshed before it expires and removed when the run ends. Locally, put OPENAI_API_KEY in an env file for the runner (Running agents locally). Declare providers: [openai] on the harness; the sandbox can then reach api.openai.com for the Responses API and nothing else, and never sees the credential (ADR 0092). A custom harness must carry a policy: (the fleet's policies/base.yaml); without one the image's default policy leaves an uninspected route to api.openai.com and the run stops before the agent starts. Exercised so far: the local static-key path end to end on 2026-08-27 (OpenShell 0.0.115, pi 0.84.3, gpt-5.6-luna: placeholder in the sandbox, pi reading it from the runner-seeded auth.json, tool calls through the hook adapter, run-scoped provider deleted at the end, expired in place under --keep-sandbox), plus the placeholder-generation experiments recorded in the ADR. The WIF path has no live run yet; features/runtime/pi-openai.feature stays gated on runtime-pi-openai until an OpenAI organization is mapped to the pool repositories.

Harness model: and agents: entry model: values accept the provider/id form directly (xai-vertex/xai/grok-4.6); a harness can also select a provider with a bare model: plus FULLSEND_PI_PROVIDER.

Each provider has its own GCP project

Every Vertex provider on pi resolves its own project variable, so one run can reach models that live in different projects. That matters because Model Garden availability is per-project — Grok may well be enabled somewhere other than Claude.

Loading diagram...

ADC supplies the identity for all three — only the project differs, so one credential covers them. A pi run leaves an explicitly-set XAI_VERTEX_PROJECT_ID alone and only defaults it to the fleet's Vertex project, so Grok can be pointed at a project where it is actually enabled.

Endpoints and regions. anthropic-vertex uses CLOUD_ML_REGION (then GOOGLE_CLOUD_LOCATION). xai-vertex is fixed to the global endpoint — Vertex serves Grok only there, and regional endpoints answer FAILED_PRECONDITION — so region variables are deliberately ignored for it.

At a glance

CredentialsSame WIF external_account + refreshed OIDC token as Claude Code for Vertex providers. ANTHROPIC_* unset on the Claude provider, XAI_API_KEY unset on the Grok one; OPENAI_BASE_URL/AZURE_OPENAI_API_KEY unset on the OpenAI one. OpenAI uses a runner-exchanged WIF token (ADR 0092)
UnattendedNo approval prompts, stdin closed, bounded retries; a missing credential exits 1
Artifactsoutput.jsonl, transcripts/<agent>-<ts>_<id>.jsonl, metrics.json with runtime: pi, plus pi-debug.log with --debug
Extra knobsFULLSEND_PI_PROVIDER (prefix for bare ids), FULLSEND_PI_BASH_ALLOWLIST=enforce
Not supportedSub-agents, fallback chains, plugins:, Bedrock/Azure providers

Running it locally

Complete Running agents locally first — the CLI, OpenShell, credentials and the fleet clone are the same. Every example there runs on pi by adding --runtime pi to the same command:

bash
fullsend run triage \
  --fullsend-dir /tmp/fullsend-agents/ \
  --target-repo /tmp/target-repo/ \
  --env-file fullsend-gcp.env \
  --env-file fullsend-triage.env \
  --runtime pi

The plan block confirms the selection — overridden values carry their source, harness defaults print bare — and metrics.json records the same (runtime, runtime_source, requested_model, override_source):

    Model: opus
    Effort: high
    Runtime: pi (from --runtime flag)
...
runtime: selected "pi" from --runtime flag
...
→ Agent: claude-opus-4-6 (v0.84.2)
→ Result: stop
  ✓ Agent exited with code 0 (131.9s)

Pick a model the same way — on pi the model name is also the provider choice, and the same Vertex credentials cover Gemini:

bash
fullsend run triage ... --runtime pi --model google-vertex/gemini-3.7-flash

To keep an agent on pi (or off it) without passing flags every time, set runtime:/model: on its agents: entry in config.yaml — see per-agent settings.

What a local pi run needs, beyond the guide:

  • fullsend v0.37.0+ — the first release that carries the pi runtime; the release download and the container image both work as-is.
  • A sandbox image that includes pighcr.io/fullsend-ai/fullsend-sandbox v0.37.0+ (the image bakes PI_VERSION). A stale image fails preflight with pi preflight: pi --version exited 127; podman pull ghcr.io/fullsend-ai/fullsend-sandbox:latest fixes it.
  • Platforms — verified end to end on macOS Apple Silicon (podman machine, Homebrew openshell) and Fedora with rootless Podman; the guide's platform notes apply unchanged.
  • review and retro complete with schema-valid results but in a single context — pi has no sub-agent tool, so the parallel reviewer roster is not exercised (see Not yet exercised).
  • KnobsFULLSEND_PI_PROVIDER sets the provider for bare model ids (default anthropic-vertex); FULLSEND_PI_BASH_ALLOWLIST=enforce makes the Bash first-token allowlist block instead of warn.
  • Security hooks are fail-closed — a missing or modified hook adapter stops the run with exit 97 by design; repo-owned .pi/ content is never loaded.
  • Debugging--debug='*' (the = is required); sandbox-side failures land in pi-debug.log inside the run directory, next to the transcripts, not in the runner's output.

Behaviour differences worth knowing

  • No permission system. pi's posture is "run in a container". The sandbox, its egress policy and credential placeholders are the boundary (ADR 0027); fullsend's hook adapter is defense-in-depth on top.
  • Reads AGENTS.md natively — no CLAUDE.md bridge is injected.
  • The agent body is appended to pi's own system prompt rather than replacing it, so pi's default tool guidance stays. Claude Code's --agent replaces it.
  • --tools is enforced strictly, unlike Claude Code. Bash(a,b) becomes a first-token allowlist that is advisory by default; FULLSEND_PI_BASH_ALLOWLIST=enforce makes it block.
  • Failed tool calls are sanitized too — pi fires its post-tool event on failures, which Claude Code does not, so redaction and unicode normalization apply on both paths.
  • Fast release cadence (~weekly minors, with wire-format changes inside a minor) — versions are pinned exactly and the stream-parser fixtures are tied to the pinned version.

Not yet exercised

runtime: pi is selectable and has been run end to end, but no fleet lifecycle run on Vertex is recorded yet. Pilot on a disposable repo with triage/prioritize before code/fix. review and retro run to schema-valid results, but in a single context: pi has no sub-agent tool, so the parallel persona roster and its per-persona models are never exercised — treat them as unsupported for that purpose. extension_error events are not mapped.

Troubleshooting

The model is not found, or the provider is missing. A pi provider comes from an extension loaded with -e, and a failed extension is dropped silently — it simply does not appear. Re-run with --debug and read pi-debug.log, which captures pi's stderr including extension load errors.

No API key found for <provider>. The provider is registered but its credentials did not resolve. For Vertex providers that means ADC — check the project variable for that provider in the table above, not a shared one.

403 PERMISSION_DENIED on a Vertex call. The credentials work but the model is not enabled in that project's Model Garden, or the provider resolved a different project than you expect.

[pi-anthropic-vertex] disabled: set GOOGLE_CLOUD_PROJECT .... The sandbox environment comes from the harness (host_files, env.sandbox), not from --env-file, which only reaches the runner process (ADR 0055). Files sourced from .env.d/ need export on each line. The fleet harnesses already wire this; a custom harness must too.

The run used Claude instead of pi. The runtime falls back to claude when neither the config's runtime: (repo-wide or on the agent's agents: entry) nor --runtime/FULLSEND_RUNTIME selects pi; the plan block's Runtime: line and stderr's runtime: selected ... show which one ran and why.

--debug "..." fails with accepts 1 arg(s). --debug takes an optional value: write --debug='*' (with =).

The agent fails with nothing in the terminal. Sandbox-side pi failures land in pi-debug.log inside the run directory, next to the transcripts; kept sandboxes must be removed manually (openshell sandbox delete <name>).

The model says it is a different model than you selected. Do not trust the reply — a model asked about itself will often repeat whatever the conversation history said. metrics.json records the model that actually served the run, and the session JSONL under transcripts/ records the provider and model per message.

See also