Guides

Using Pi

Run a model profile's soldiers on the Pi coding agent instead of Claude Code, including free and non-Anthropic models.

A model profile can name a harness, the agent CLI its soldiers run on. Claude Code is the default. With "harness": "pi" the soldiers of that profile run on Pi, which can use almost any provider and model, including free tiers. The commander is a Claude Code session unless you also run it in Pi (see Pi as the commander), and a task keeps its harness through vx redispatch, vx prompt, vx decide, vx land and vx strike.

This page describes what was checked against Pi 1.3.0. vx doctor warns on any other major series. For example profiles with free and cheap models, see Models for other harnesses.

Free tiers can keep your code

A soldier sends whatever it reads to the model provider: your prompts, the files it opens, command output and anything in your environment that a command prints. Many free model tiers log prompts and outputs and may use them for training, under terms that differ per provider and change over time. Read the provider's terms before you point a profile at a free model, and do not use one for private or proprietary code, or on a machine whose files or environment hold secrets. Pi, like Claude Code in vexillum, has full host access under your user.

Set it up

  1. Install Pi. Either the installer or npm works:

    curl -fsSL https://pi.dev/install.sh | sh
    npm install -g --ignore-scripts @earendil-works/pi-coding-agent
  2. Run pi once and log in to a provider with /login, or export the provider's API key. pi --list-models lists what your account can use.

  3. Install herdr's integration for Pi, so herdr reads a Pi soldier's state (working, idle) from Pi itself instead of from the screen:

    herdr integration install pi
  4. Run vx doctor. When a profile selects pi, or a pi binary is on your PATH, it reports the binary, its version and the integration. They only warn: a missing piece never makes the environment not ready, and a setup without Pi sees none of these lines.

Add a profile

Add a profile with "harness": "pi" to .vexillum/models.json (or the global ~/.vexillum/models.json):

{
  "profiles": {
    "pi-scout": {
      "model": "anthropic/claude-haiku-4-5",
      "effort": "low",
      "when": "Scout whose question reduces to verifiable facts, run on Pi.",
      "harness": "pi"
    }
  }
}
vx dispatch "which flags does the build script accept?" --kind scout --profile pi-scout

On a Pi profile the two fields mean:

FieldValuePassed to Pi as
modelprovider/id, such as anthropic/claude-haiku-4-5 or openrouter/meta-llama/llama-4-scout:free--model <provider/id>
efforta thinking level: off, minimal, low, medium, high, xhigh or max--thinking <level>

vexillum checks the shape only: a model must look like provider/id, and effort must be one of Pi's levels. Pi's models are whatever your providers offer, so it cannot check that a model exists; Pi reports that when the soldier starts. Pi also clamps a thinking level the model does not support to the nearest one it does, so xhigh on a model that stops at high runs at high. A claude alias such as sonnet is not a provider/id, and switching an existing profile to pi without giving it a provider/id model is an error that names the file and the field. A later layer is checked against the harness the profile ends up with, so a project file can retune a pi profile from the global base.

Model ids that end in :free

Pi reads a :level suffix on --model as a thinking level, and some ids end in a colon segment of their own, such as OpenRouter's :free. vexillum never builds that suffix: keep the whole id in model and put the thinking level in effort, which is passed with --thinking. Pi matches an exact model id before it looks for a suffix, so openrouter/meta-llama/llama-4-scout:free is taken as the id it is. Do not write ...:free:high in model.

What differs from a Claude Code soldier

TopicClaude Code soldierPi soldier
Permissions--dangerously-skip-permissionsPi has no permission prompts and no flag to skip. It has the same full host access, and landing is still the control: nothing reaches your history until you approve vx land.
Project trustvexillum dismisses Claude's "trust this folder" dialogStarted with --approve. Pi asks "Trust project folder?" and stops until someone answers when the camp has .pi/ settings, extensions, skills, prompts, themes or system-prompt files, or a .agents/skills directory in it or a parent. Nobody is at a soldier's pane, and its default answer saves the decision in ~/.pi/agent/trust.json. The cost: the project's committed .pi/ extensions run in the soldier. A soldier can already run any command as you, so that adds no reach, but review a repository's .pi/ before you dispatch on it.
Terminalnormal--tui-mode regular. Pi runs fullscreen on the terminal's alternate screen by default since 1.0.0, which has no scrollback to read.
RulesCLAUDE.md, which imports AGENTS.mdPi reads AGENTS.md in each folder and, only when there is none, CLAUDE.md. It does not expand @AGENTS.md. Nothing extra is needed: the vexillum block lives in AGENTS.md (or in CLAUDE.md when the project has no AGENTS.md), and Pi reads whichever holds it. A project that has both files with different content shows a Pi soldier only AGENTS.md.
Skillsthe project's .claude/skillsnone. Soldiers do not use skills in this release.
QuestionsClaude's question selector, answered with a key pressan ask_user tool that vexillum gives the soldier, answered through a file (see Blocked soldiers). With no tool, a needs-decision: line answered as plain text.
Wakethe sentinel reads the status from herdrthe same. The sentinel needs no extension to watch a Pi soldier, and the commander is woken as usual. (A commander that runs in Pi has an extension of its own for that: see Pi as the commander.)
vx shipthe tribunal's reviewer and fixer run claude -p on sonnet at high effortthey run pi -p --no-session on the model and thinking level the mission itself ran with, or Pi's default when the profile set none

Blocked soldiers

Every Pi soldier is started with a small extension of vexillum's own that gives it an ask_user tool: a question, the likely answers as options, and always a way to type another one. The extension is built into the vx binary and written to ~/.vexillum/projects/<key>/pi/ask_user.ts when you dispatch, then loaded with pi -e. Nothing is added to the camp, Pi's project-trust prompt never applies to it, and nothing besides Pi itself runs it (no Node or Python to install).

While the soldier waits in ask_user:

  1. the extension saves the question and its options under ~/.vexillum/projects/<key>/asks/<task-id>/ and tells herdr the soldier is blocked, which is what the herdr integration for Pi reports only for this tool;
  2. the sentinel marks the task blocked, wakes the commander, and stores the question as the task's decision with "kind": "ask", its options and a question_id;
  3. you answer, and the soldier carries on with your answer as the tool's result.
vx status
vx decide <task-id> 2                      # an option's 1-based number
vx decide <task-id> "Use the existing helper"   # an option's exact text, or any text

vx decide writes the answer to a file the extension is waiting on, addressed to that question's id, and waits until the extension has taken it. Nothing is typed into the pane. A file is used instead of key presses because what a key does depends on which of Pi's dialogs is up and where its cursor is, and a prompt sent to Pi while a tool runs is queued as a steering message instead of answering the tool. You can also answer in the soldier's pane yourself: the dialog closes the same way, and the task goes back to running by itself. If you ran vx decide after that, it refuses with a message that the question is no longer open, and vx decide <task-id> --dismiss clears the task.

The files hold only the question and options the soldier wrote and your answer: one question.json and one answer.json, readable by you only. They are removed as soon as the question is closed.

If the extension did not load (an old Pi, or a pi that failed at startup), the soldier has no ask_user. Its prompt then tells it to end its turn with a needs-decision: line instead, the task becomes blocked with a prose decision, and vx decide types your answer into the pane as plain text, so write the answer itself and not an option number. vx doctor does not check the extension, because it loads only when a soldier starts: if a soldier ignores ask_user, read its pane for a line from Pi about the extension, and see the checklist in the repository for how to confirm it loaded.

If herdr's Pi integration is not installed, herdr does not see the soldier as blocked while it waits. The sentinel still blocks the task from the open question file, within a few seconds. --dismiss works as for any task. See Blocked and interrupted.

Not in this release

  • Skills for Pi soldiers.

For a checklist to verify a Pi setup on a real machine, see docs/verification/pi-soldier-checklist.md in the repository.

On this page