Pi Agent Rust

pi_agent_rust

pi_agent_rust - Native AI coding agent CLI written in Rust

Why Should You Care? • TL;DR • Methodology • Quick Start • Features • Installation • Commands • Configuration

Rust 2024 License: MIT + Rider No Unsafe Code

```bash # Install latest release curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/pi_agent_rust/main/install.sh?$(date +%s)" | bash ``` --- ## The Problem You want an AI coding assistant in your terminal, but existing tools are: - **Slow to start**: Managed runtimes can add noticeable startup overhead - **Resource intensive**: Electron apps or heavy runtimes can add substantial overhead - **Unreliable**: Streaming breaks, sessions corrupt, tools fail silently - **Hard to extend**: Closed ecosystems or complex plugin systems ## The Solution **pi_agent_rust** is a from-scratch Rust port of [Pi Agent](https://github.com/badlogic/pi) by [Mario Zechner](https://github.com/badlogic) (made with his blessing!). Official release archives install the single end-user binary `pi`, with streaming responses and 36 built-in tools (19 in the default `--tools` list; 14 always in the model's schema, the rest reachable through the `xdev` dispatcher or enabled in settings). ### Current product direction This project is no longer trying to be a strict drop-in replacement for the legacy TypeScript Pi. That target became both impractical and undesirable as the legacy implementation evolved. Legacy Pi remains useful historical context, but it is not our compatibility authority or definition of completeness. OMP is the closer reference for where the product is going: feature surface, agent workflows, look and feel, and overall UI/UX. Pi Rust still chooses Rust-native architecture and may intentionally differ from both projects when that produces a simpler, safer, or better coding agent. Historical drop-in and parity artifacts remain in the repository as records; they do not gate product work, releases, or user-facing claims. All repository quality checks, builds, and releases run through Doodlestein Self-Releaser (DSR). Contributors must not invoke Cargo or RCH directly, and GitHub Actions is never an execution or evidence authority for this project. Use `dsr quality --tool pi_agent_rust` for the registered quality recipe and the canonical DSR build/release commands documented in [docs/releasing.md](docs/releasing.md). For signed published payload verification and offline CLI smoke checks, see [Published release consumer checks](docs/published-release-checks.md). Rather than a direct line-by-line translation, this port builds on two purpose-built Rust libraries: - **[asupersync](https://github.com/Dicklesworthstone/asupersync)**: A structured concurrency async runtime with built-in HTTP, TLS, and SQLite - **[rich_rust](https://github.com/Dicklesworthstone/rich_rust)**: A Rust port of [Rich](https://github.com/Textualize/rich) by [Will McGugan](https://github.com/willmcgugan), providing beautiful terminal output with markup syntax ```bash # Start a session pi "Help me refactor this function to use async/await" # Continue a previous session pi --continue # Single-shot mode (no session) pi -p "What does this error mean?" < error.log ``` ## Why Should You Care? If you already use Pi Agent, especially through OpenClaw, this project keeps the core workflow while upgrading the engine under the hood: - **A native single-binary design** intended to minimize startup and runtime overhead - **A bounded-resource architecture** for long-running sessions - **A capability-gated security model** for extension/tool execution, including command-level blocking of dangerous extension shell patterns Security is a first-class design goal here, not a bolt-on: - Capability-gated hostcalls (`tool`/`exec`/`http`/`session`/`ui`/`events`) - Two-stage extension `exec` enforcement: capability gate first, then command mediation that blocks critical shell classes by default (for example recursive delete, disk/device writes, reverse shell) and can tighten to block high-tier classes in strict/safe policy - Policy + runtime risk + quota enforcement on the execution path - Per-extension trust lifecycle (`pending` -> `acknowledged` -> `trusted` -> `killed`) with kill-switch audit logs and explicit operator provenance - Hostcall-lane emergency controls that can force compatibility-lane execution globally or for one extension when fast-lane behavior needs immediate containment - Structured concurrency via `asupersync` for more predictable cancellation/lifecycle behavior - Auditable runtime signals/ledgers and redacted security alerts for extension behavior ## TL;DR (Pi/OpenClaw Users) The Rust port is designed around large-session, multi-agent, and extension-heavy workloads. Release-facing performance numbers are published only when the checked-in evidence artifacts are current, have matching run provenance, and show data and a passing result for every declared budget, with no data-contract failures. Historical benchmark snapshots are retained in planning/evidence artifacts, but they are not treated as current README claims until the performance evidence gate is regenerated cleanly. Extension runtime guarantees are also concrete: | Extension assurance signal | Why you should care | |---|---| | Two-stage `exec` guard (`exec` capability policy + command-level mediation + DCG/heredoc AST signals) | Dangerous shell intent is caught before spawn, including destructive payloads hidden in multiline wrappers | | Trust lifecycle + kill switch (`pending/acknowledged/trusted/killed`) | You can quarantine an extension instantly, log who pulled the switch and why, and require explicit re-acknowledgement before restoring access | | Hostcall lane kill-switch controls (`forced_compat_global_kill_switch`, `forced_compat_extension_kill_switch`) | Fast-path regressions can be contained immediately by forcing compatibility-lane execution without disabling the extension system | | Deterministic hostcall reactor mesh (shard affinity, bounded SPSC lanes, backpressure telemetry, optional NUMA slab tracking) | Runtime behavior stays predictable under contention; queue pressure and routing decisions are observable instead of opaque | | Cold owner-isolated JS realms + persistent transpile cache | Every reload gets a fresh realm while versioned disk-cached transpilation avoids treating mutable JavaScript state as safely reusable | | Tamper-evident runtime risk ledger (`verify` / `replay` / `calibrate`) | Security decisions are hash-linked and can be replayed or threshold-tuned from real runtime traces | Bottom line: Pi's architecture targets lower latency, lower memory use, and stronger extension runtime safety under real workload pressure; current numeric claims must come from fresh, provenance-matched evidence artifacts. Data source: `docs/planning/BENCHMARK_COMPARISON_BETWEEN_RUST_VERSION_AND_ORIGINAL__GPT.md` (latest secure-path + full orchestrator checkpoints, 2026-04-23). ### README Citation Convention Release-facing numeric performance claims in this README include inline citations with format: `*(from [artifact-path], run [correlation-id])*` Example: `*(from [artifact-path], run [correlation-id])*` Two additional machine-recognized citation forms exist: - `*(from [artifact-path])*` — path-only citation. The cited artifact must exist and parse; freshness is enforced by the same 14-day rule as release-facing claims. - `*(from [artifact-path]; historical snapshot)*` — explicit historical contract. The citation itself declares the obligation a retained snapshot: existence and validity are still checked, but staleness is not enforced because such claims never satisfy current release-facing requirements. `scripts/check_readme_evidence_freshness.py` (a pre-release check listed in [docs/releasing.md](docs/releasing.md), not part of the code quality recipe) checks file freshness and artifact content so stale, no-data, or correlation-mismatched evidence cannot back user-facing performance claims; for release-facing citations of `budget_summary.json` it validates the full `pi.perf.budget_summary.v2` contract, including that the header counts equal the per-budget rows. It reports line-numbered proof obligations for cited claims and extracts claim-gated performance phrases for reviewer audit. Historical snapshot citations are mapped separately and do not satisfy current release-facing claims. ## Performance-Oriented Architecture In this README, `we` means the project owner and collaborating coding agents. The design concentrates performance work in several runtime layers rather than assuming one optimization proves an end-to-end result. | Technique | What we do | Runtime intent | |---|---|---| | Cold-start minimization | Single native binary, no Node/Bun runtime bootstrap, no JIT warmup, startup prewarm for extension runtime paths | Reduce time-to-first-interaction | | Less copying on hot paths | `Arc`/`Cow` message flow, zero-copy hostcall/tool payload handling, reduced clone-heavy provider/session paths | Reduce CPU and allocation pressure | | Deterministic dispatch core | Typed hostcall opcodes, fast-lane/compat-lane routing, bounded shard queues with reactor-mesh telemetry | Reduce tail latency under concurrent extension load | | Efficient long-session storage | SQLite session index + v2 sidecar (segmented log + offset index) with O(index+tail) reopen path | Avoid full-history work on eligible resumes | | Streaming parser tuned for real networks | SSE parser tracks scanned bytes, handles UTF-8 tails, normalizes chunk boundaries, interns event-type strings | Reduce repeated scanning and parser stalls | | Safe fast-path controls | Shadow dual execution sampling, automatic backoff on divergence/overhead, compatibility-lane kill switches for containment | Bound optimization risk and preserve fallback behavior | | DSR performance governance | Scenario matrices, strict artifact contracts, fail-closed perf gates | Detect regressions before release | If you want the full implementation inventory, see [Performance Engineering](#performance-engineering). ## Benchmark Methodology and Claim Integrity The benchmark evidence policy is designed to keep results realistic, reproducible, and hard to game. What we measured: - **Matched-state workloads**: resume a large session and append the same 10 messages. - **Realistic E2E workloads**: resume + append + extension activity + slash-style state changes + forks + exports + compactions. - **Scale levels**: from `100k` up to `5M` token-class session states. - **Startup/readiness**: command-level readiness (`--help`, `--version`) separately from long-session workflows. How we kept comparisons fair: - **Two scopes** in the benchmark report: - apples-to-apples (`pi_agent_rust` vs legacy `coding-agent`) - apples-to-oranges (legacy stack components included where legacy behavior is outsourced) - **Release-mode binaries** and repeated runs per matrix cell. - **No paid-provider noise** in core latency/footprint tables (provider-call costs are excluded from these core comparisons). How we kept claims honest: - **Security controls stayed on** during secure-path measurements (no policy/risk/quota bypasses for speed claims). - **Raw artifacts are preserved** (JSON/trace/time outputs) and called out in the benchmark report. - **Blockers are explicitly disclosed**: when direct legacy reruns were blocked by missing workspace deps, we state that and compare against prior validated legacy artifacts instead of pretending reruns succeeded. - **Interpretation notes are explicit**: the report distinguishes baseline sections vs fresh reruns so readers can see exactly which values came from which run set. - **Reproducibility over marketing**: methodology, caveats, and known limits are included alongside wins. If you want full details, see: - `docs/planning/BENCHMARK_COMPARISON_BETWEEN_RUST_VERSION_AND_ORIGINAL__GPT.md` (methodology + results + caveats + raw artifact paths) ## Why Pi? | Feature | Pi (Rust) | Typical TS/Python CLI | |---------|-----------|----------------------| | **Startup** | Native single-binary path (Fresh `v0.3.0` measurement pending; pre-v0.3.0 criterion: ~5-7ms p95 version, ~12-15ms help; see `tests/perf/reports/budget_summary.json` `startup_version_p95` and `startup_full_agent_p95`) | Runtime-dependent | | **Binary size** | Size-budgeted release profile (LTO, strip, `opt-level = "z"`); the `binary_size_release` budget and its latest stripped-artifact measurement are reported in [Current Evidence State](#current-evidence-state), never promoted here until claim readiness is `ready` | Runtime-dependent | | **Memory (idle)** | Bounded-resource design; the `idle_memory_rss` budget and its latest release-binary measurement are reported in [Current Evidence State](#current-evidence-state) (`docs/perf-budgets-recipe.md` defines the canonical 5-measurement taxonomy) | Runtime-dependent | | **Streaming** | Native SSE parser | Library-dependent | | **Tool execution** | Process tree management | Basic subprocess | | **Sessions** | JSONL with branching | Varies | | **Unsafe code** | Forbidden | N/A | ## Quick Example ```bash # 1) Start an interactive session pi # 2) Ask a codebase question pi "Summarize the architecture in src/" # 3) Attach a file inline pi @src/main.rs "Explain startup flow" # 4) Run single-shot mode for scripting pi -p "List likely regression risks for this diff" # 5) Continue your last project session pi --continue # 6) Inspect available models/providers pi --list-models pi --list-providers ``` --- ## Foundation Libraries ### asupersync [asupersync](https://github.com/Dicklesworthstone/asupersync) is a structured concurrency async runtime designed for applications that need predictable resource cleanup. Key features used by pi_agent_rust: - **Capability-based context (`Cx`)**: Async functions receive an explicit context that controls what they can do (HTTP, filesystem, time). This makes testing deterministic. - **HTTP client with TLS**: Built-in HTTP API with rustls, avoiding OpenSSL dependency hell - **Structured cancellation**: When a parent task cancels, all child tasks cancel cleanly. No orphaned futures. `pi_agent_rust` runs on `asupersync` end-to-end today (runtime + HTTP/TLS + cancellation). Provider streaming uses a minimal HTTP client (`src/http/client.rs`) feeding a custom SSE parser (`src/sse.rs`). ### rich_rust [rich_rust](https://github.com/Dicklesworthstone/rich_rust) is a Rust port of Will McGugan's [Rich](https://github.com/Textualize/rich) Python library. It provides: - **Markup syntax**: `[bold red]error[/]` renders as bold red text - **Tables**: ASCII/Unicode table rendering with alignment and borders - **Panels**: Boxed content with titles - **Progress bars**: Animated progress indicators - **Markdown**: Terminal-rendered markdown with syntax highlighting - **Themes**: Consistent color schemes across components The terminal UI uses rich_rust for all output formatting, providing the same visual quality as Rich-based Python tools. --- ## Quick Start ### 1. Install ```bash # Install latest release binary curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/pi_agent_rust/main/install.sh?$(date +%s)" | bash ``` If you already have the original TypeScript `pi` installed, the installer asks whether to make Rust Pi canonical as `pi` and automatically create `legacy-pi` for the old command. ### 2. Configure API Key ```bash export ANTHROPIC_API_KEY="sk-ant-..." ``` ### 3. Run ```bash # Interactive mode pi # With an initial message pi "Explain this codebase structure" # Read files as context pi @src/main.rs "What does this do?" ``` ### Using a local model `ollama`, `llamacpp` (llama.cpp's `llama-server`), `mistralrs` (mistral.rs), and `lmstudio` are built-in **local** providers. The first three need **no API key** and work out of the box against their default ports: ```bash pi --provider ollama --model llama3 -p "hi" pi --provider llamacpp --model -p "hi" pi --provider mistralrs --model default -p "hi" ``` To point at any other OpenAI-compatible server (a custom host/port, vLLM, etc.), add it to `~/.pi/agent/models.json`: ```json { "providers": { "ollama": { "api": "openai-completions", "baseUrl": "http://127.0.0.1:11000/v1", "apiKey": "ollama", "models": [{ "id": "Qwen3.6-35B-A3B-4bit", "contextWindow": 32768 }] } } } ``` Then `pi --provider ollama --model Qwen3.6-35B-A3B-4bit`. See [docs/models.md](docs/models.md) for the full `models.json` schema, provider aliases, and secret resolution (env vars and `!command` shell lookups). ### Refreshing provider model catalogs For OpenAI-compatible providers, model discovery is a standalone command: it prints one model ID per stdout line and exits without starting the TUI. ```bash # Use a successful live response or (with a warning on stderr) the static # registry when live discovery is unavailable pi --fetch-models openrouter # Bypass the cache and require a genuinely live response; never fall back pi --fetch-models openrouter --refresh-models # Make a successful live/cache catalog available to future --list-models and # interactive /model pickers pi --fetch-models openrouter --refresh-models --persist-models ``` Each standalone CLI invocation starts a new process, so its in-memory cache is fresh. The five-minute cache only avoids repeat discovery calls made within one long-lived process by SDK/library users; `--refresh-models` bypasses that cache. Discovery also reads usable `supported_reasoning_levels` advertisements, including `metadata.supported_reasoning_levels` and entries shaped as `{"effort":"high"}`. Nested metadata takes precedence. Pi preserves the advertised tiers through the cache and persisted catalog, constrains the model's thinking choices to those tiers, and sends the selected effort through the provider's request format. The recognized tiers are `off` (also advertised as `none`), `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. Missing, empty, or unusable advertisements leave existing model capabilities intact. Your configured thinking level remains authoritative; discovery does not select a server-advertised default effort. Persistence is opt-in. The v2 `~/.pi/agent/models.fetched.json` schema stores provider/model IDs, optional bounded reasoning tiers, the fetch timestamp, and a non-secret SHA-256 identity binding membership to the provider, API, query-free endpoint, auth-header mode, recognized credential-query ordered name/presence shape, and request-header name/presence shape that produced it. Pi never persists a static fallback, API credentials, URL query values, or request-header values—not even as offline-verifiable hashes. Credential-channel values are rotation-tolerant. Any non-empty query/header value outside a narrowly recognized credential channel may identify a tenant or deployment, so Pi refuses to persist that catalog (and ignores legacy persisted rows for such a current route) rather than reuse unverifiable membership. If a safely bound route shape no longer matches, Pi ignores those generated rows and reports how to refresh them. Credential rotation alone does not invalidate the catalog. The generated membership is likewise not account-bound: switching accounts on the same endpoint/transport shape can retain the prior account's saved model list until you rerun `--fetch-models --refresh-models --persist-models`. Inference still resolves and sends the current account's credential; only the opt-in model-membership list can be stale across that switch. The generated catalog is loaded first; your hand-written `~/.pi/agent/models.json` is loaded afterward and remains authoritative. Pi does not rewrite or merge that user-authored file. Legacy `pi.models.fetched.v1` files cannot be rebound safely because they lack this provenance, so Pi preserves them instead of overwriting them. Move the legacy file aside to `models.fetched.v1.backup.json`, then rerun the verified live refresh and persist command above to create a v2 catalog. --- ## Features ### Streaming Responses Real-time response streaming with extended thinking support: ``` pi "Write a quicksort implementation" ``` Watch the response appear incrementally, with thinking blocks shown inline. ### 36 Built-in Tools Tools are tiered so the model's live schema stays small while everything remains reachable. The tier table lives in `src/xdev.rs`; the default `--tools` list lives in `src/cli.rs`: - **Essential** (always in the provider schema): `read`, `write`, `edit`, `bash`, `grep`, `find`, `ls`, `hashline_edit`, `ask`, `todo`, `web_search`, `submit_plan`, `current_time`, `xdev` - **Discoverable** (registered, hidden from the schema until promoted via `xdev list/describe/run/promote`): `ast_grep`, `ast_edit`, `lsp`, `debug`, `manage_skill` — plus the memory-bank tools (`retain`, `recall`, `reflect`, `memory_edit`, `learn`) when `memory.backend` is `local` or `cass` (local memory plus read-only project history) - **Default-enabled**: `jobs` (background bash job control) and `hub` (PTY service supervision), alongside the essential tier. The default `--tools` list names 19 tools; the registry always adds `manage_skill` and, when any discoverable tool is enabled, the `xdev` dispatcher - **`--tools` opt-in extras**: `eval`, `github`, `security_scan` - **Settings-gated extras** (off until enabled in `settings.json`): `browser` (`browser.enableBrowser`), `computer` (`computer.enableComputer`), the media trio `inspect_image`, `generate_image`, `tts` (`media.enableInspectImage` / `media.enableGenerateImage` / `media.enableTts`), and `read_media` (`media.enableReadMedia`; inline video/audio for Gemini-family models) - **Opt-in only**: `subagent` (it can start additional coding-agent processes) | Tool | Description | |------|-------------| | `read` | Read files (images and http(s)/`skill://`-style URLs supported) | | `write` / `edit` / `hashline_edit` | Create, surgically replace, or hashline-anchored edit files | | `bash` | Shell with timeout, command mediation, optional PTY, background jobs | | `grep` / `find` / `ls` | In-process content search, file discovery, listing | | `web_search` | Ranked multi-provider web search with circuit breaking | | `ask` / `todo` | Structured mid-turn option cards; persistent session task list | | `submit_plan` | Submit a completed plan for approval when plan mode is active | | `current_time` | The host's wall-clock time: UTC and local ISO-8601 timestamps, the UTC offset, Unix epoch seconds, weekday, and ISO week (the local zone follows `TZ`); takes no arguments. The system prompt carries only the date so the cached prefix stays stable; this tool is the clock | | `xdev` | Dispatcher exposing the discoverable tier (`list/describe/run/promote`) | | `ast_grep` / `ast_edit` | Structural code search and rewrite | | `lsp` / `debug` | Language-server (14 ops) and DAP debugging (29 ops) bridges | | `eval` | Persistent Python and JS kernels with cell semantics | | `jobs` / `hub` | Background bash job control; PTY service supervision | | `github` | `gh`-backed PR/issue/run operations | | `security_scan` | Rule-pack security scanning to SARIF (`plan`/`run`/`disposition`/`compare`) | | `manage_skill` + memory tools | Managed skills CRUD; opt-in project memory bank | | `browser` | Headless Chromium automation over CDP (navigate, snapshot, click, type, screenshot) with a domain allowlist; settings-gated | | `computer` | Desktop automation (displays, windows, screenshots, mouse/keyboard, clipboard); mutating actions require approval; settings-gated | | `inspect_image` / `generate_image` / `tts` | Vision analysis of local images, image generation/editing, and text-to-speech through provider adapters; settings-gated | | `read_media` | Attaches a local video/audio file (mp4, webm, mov, mp3, wav, m4a, ogg, flac) as an inline media block. Gemini, Gemini CLI, and Vertex Gemini models receive it natively as `inline_data`; every other provider sees `[media omitted: , , ]`. Hard cap 5 MiB per file (`media.maxBytes`); settings-gated | | `subagent` | Delegate isolated work to named Rust Pi child agents | ### Project memory learning The local memory bank can extract reusable facts, lessons, preferences and decisions from completed saved sessions, and consolidate facts learned earlier. Enable the bank in settings, then run `/memory sync` in the default interactive UI to learn from the current completed branch: ```json { "memory": { "backend": "local", "autoExtract": true, "extractEveryTurns": 3 } } ``` `autoExtract` is off unless explicitly enabled. When enabled, one bounded, tool-free request runs after every three newly authored turns by default; the cadence accepts 1–100. The request uses the session's current model and provider credentials, has a 30-second deadline, and shares any remaining foreground time budget. Extraction failure leaves the completed foreground answer intact and can be retried with `/memory sync`. Explicit synchronization works with `autoExtract` off. Ephemeral sessions and delegated child agents do not extract project memories. The `cass` backend uses the same local learning pipeline. Extraction resolves the session's current attached workdir, screens complete bounded text evidence before selecting a recent request window, and redacts both request and stored results. Oversized evidence is refused rather than partially screened. Each successful extraction commits facts, search indexes, audit records and a durable branch checkpoint together, so reopening or revisiting an already synchronized branch does not repeat the provider request. Automatic consolidation can supersede only unchanged generated memories supplied to the request; manually retained or edited facts keep their ownership. Historical versions remain auditable. Learned facts become available through `recall`, `reflect` and the existing startup memory block. `/memory view`, `list`, `search` and `forget` remain available for inspection and management. All tools include automatic truncation for large outputs (2000 lines / 1MB), detailed metadata in responses, and process-tree cleanup for bash. Background-job logs live in a dedicated directory with a 256 MiB / 4096-entry budget. When it cannot admit another 16 MiB artifact, Pi deletes the oldest unlocked logs, only as many as needed, and always keeps active logs and at least eight recent ones. Set `PI_JOBS_ARTIFACT_RETENTION=preserve` to keep every log instead; Pi then refuses new background jobs once the budget is full. Job snapshots report the applied policy, removed-file count, and reclaimed bytes in `artifactCleanup`. Print mode can keep working after a background job finishes. Add `--wait-async` or set `"async": {"printWake": true}` to wait for the current session's jobs, deliver their completion notices to the agent, and run follow-up turns in the same session. A follow-up may start further jobs. The process exits when all owned work has settled and its notices have been delivered. Text continues streaming each answer; JSON mode emits the corresponding agent lifecycles. ```bash pi -p --wait-async --max-time 3600 \ "Run the integration suite in the background and fix any failures." ``` The flag is opt-in; ordinary print mode exits after its prompts and cancels unfinished jobs. With async waiting, `--max-time` covers the original run, idle waits, follow-up turns and retry delays. Reaching it fails the run and cancels owned jobs. Ctrl+C also interrupts idle waits and retry delays. Hub service logs retain the first 16 MiB of raw output per run. Pi continues draining the service after that limit or an artifact write failure, so live tail logs, readiness checks, and PTY input continue working. `logs`, `ps`, and `describe` report `logCapture` byte counts, truncation, I/O errors, and output drain state separately from process status; incremental reads also report evicted lines. The cap applies to each artifact, not the total across runs. `detached: true` skips session-exit cleanup, but the PTY and its supervisor still belong to the current Pi process: cross-process reattachment and survival after that host process exits are not implemented. Per-tool exposure is configurable via `tools.loadMode.` set to `essential`, `discoverable`, or `off`; an explicit `--tools` list always wins: ```bash pi --tools read,bash,edit,write,grep,find,ls,hashline_edit,subagent \ "Use the scout agent to inspect the provider implementation." ``` ### Model Spend Limits Use `--warn-cost` to show a one-time notice and `--max-cost` to stop admitting model requests once known spend reaches a USD threshold: ```bash pi --warn-cost 2 --max-cost 5 "Implement and test the change." pi -p --wait-async --max-cost 10 "Delegate the investigation and report the findings." ``` The corresponding settings are `session.warnCost` and `session.maxCost`; CLI values override them. Values must be positive finite numbers. The limits cover the active session tree: foreground turns, retries, native subagents, advisor reviews, compaction, titles, memory extraction and side questions share the same account. Child requests consult the parent's live balance; missing or ambiguous accounting acknowledgements block further child requests. Known partial usage from an in-flight request counts toward the next admission. The provider's final bill replaces that request's provisional estimate exactly once, including when the final bill is lower. Cancelling or reaping a native child retains partial usage already acknowledged by its parent, so siblings cannot spend that allowance again. An already-admitted request may settle its bill during cancellation; this does not permit new model requests. ```json { "session": { "warnCost": 2, "maxCost": 5 } } ``` Resuming includes recorded assistant usage from all retained branches and saved auxiliary-spend checkpoints. Forking, rewinding, retrying, changing models and reloading resources preserve the live balance. A new session gets a fresh balance; switching to another saved session restores that session's recorded spend. A resumed session already above its warning threshold does not repeat the warning. Change launch flags or settings before reopening to use different thresholds. The Rust SDK exposes `SessionOptions.warn_cost`, `max_cost` and an optional shared `cost_budget`; `CostBudget` clones share one account. Warnings arrive as `AgentEvent::CostWarning` (`type: "cost_warning"` in JSON). A refused request returns `Error::CostCap`; print mode exits with status **3**. Native RPC marks the terminal failure with `errorCode: "cost_cap"`, `retryable: false`, and `costCap: { "spent": ..., "cap": ... }`. These are request-boundary limits based on known provider costs or catalog token pricing. Requests already in flight may finish above the cap, and unreported/unpriced usage cannot be estimated. Spend is checkpointed with session saves, including normal turn completion, replacement and SDK shutdown. Only settled charges enter saved cost checkpoints; an in-flight estimate cannot permanently inflate the restored balance if its final bill is lower. When a saved session is replaced or shuts down, captured background requests retain a durable billing destination for that original session. Their final charges go to a separate cost journal beside the session file, without rewriting its transcript or racing a newly reopened session's saves. Resume combines those charges with saved checkpoints exactly once; a reopened live account also imports newly arrived charges before admitting another request. This applies to JSONL and SQLite sessions, including the first charge arriving after a zero-spend checkpoint. Failed replacements and same-file reloads keep the foreground account live. Cost journals are not created for `--no-session`. Saved forks retain references to observed unfinished billing writers. A fork can recover their final charges even after every foreground handle has closed, without opening an ancestor's transcript or SQLite database. Repeated forks of one live account use the same journal. Once a writer's final closure is observed, later checkpoints retain its final total and remove the completed reference. Missing required journals or writers block further requests instead of resetting spend. Keep the adjacent `.pi-cost-*.json` files with their session files when backing up a session directory. Unfinished references use the original absolute journal location: moving or copying the directory does not redirect outstanding billing to the copy. Observe final closure and save a new checkpoint before expecting a snapshot to be independent of its original journals. Failure to acknowledge a journal write blocks further requests on the affected account. If an incomplete save leaves primary usage and journal charges with an uncertain overlap, the known lower bound is retained and further requests are blocked. A crash before a normal checkpoint or before a final charge receives its durable acknowledgement can still lose uncheckpointed spend; these limits do not replace the provider's billing records. ### Native Subagents and Orchestration Rust Pi includes a native `subagent` tool; it does not depend on a QuickJS extension and never resolves a child executable by assuming a `pi` binary on `PATH`. By default it starts the current Rust Pi executable. Set `PI_SUBAGENT_PI_BINARY=/absolute/path/to/rpi` only when an explicit binary override is needed. Agent definitions are Markdown files in `$PI_CODING_AGENT_DIR/agents/*.md` (normally `~/.pi/agent/agents/*.md`) or the nearest `.pi/agents/*.md`. Project definitions take precedence over same-named user definitions. The process inherits the parent's provider, router, authentication, and model-registry environment, including `PI_CODING_AGENT_DIR`. Children choose their model in this order: the agent definition's `model:` pin, the configured `task` role (or `smol` fallback), then the parent's active provider and model. Each request captures the parent selection once, including any manual switch or failover already in effect; queued children, chain steps, schema retries, and explicit revivals retain that selection. Children resolve credentials from their inherited environment and existing authentication configuration. Credentials supplied only to the parent's CLI or in memory are not copied into child arguments or new environment variables. ```markdown --- name: scout description: Inspect code and return concise, evidence-backed findings. model: ai-router/gpt-5.6-sol reasoning: low tools: read,grep,find,ls skills: ../skills/codebase-archaeology/SKILL.md --- Read relevant code before answering. Do not edit files. ``` The tool accepts exactly one workflow shape: a single `{ agent, task }`, a bounded parallel `tasks` array (up to 8, default concurrency 4), or a sequential `chain` array. In chains, `{previous}` in a task is replaced by the preceding child's final output. Children run headlessly in isolated ephemeral sessions, stream progress into the parent TUI/JSON output, return structured status and stderr details, and are killed/reaped if the parent is cancelled. Child agents receive the declared tool allowlist; the default child allowlist deliberately excludes `subagent` to prevent accidental recursive delegation. With per-task `isolation: "worktree"`, a parallel batch captures each source workspace before launching children, including staged, unstaged, and untracked files. Queued children and corrective retries use that same baseline. Accepted patches apply after execution in task order; conflicts and cancelled work remain in their worktrees for inspection. `isoApply: "keep"` retains a result without applying it, and `isoApply: "drop"` discards its worktree after reporting the patch. In interactive mode, `/tan ` starts the same task-role child machinery without interrupting the main conversation. The child runs in the current directory, appears in `hub agent roster` with `kind=tan`, and sends its bounded completion summary through the follow-up queue at the next idle turn boundary. Because `/tan` inherits the delegation safety gate, it is available only when the opt-in `subagent` tool is enabled. It captures the same model precedence when the command is submitted, so a later parent model switch does not retarget it. Native children include `hub` in their default tool set and can exchange messages with other children of the same parent Pi process. Agent definitions with an explicit tool allowlist must include `hub` to enable this capability. A child's `hub` call with `{"op":"agent","action":"roster"}` returns its authenticated `selfId` and the parent's child roster. Use those run IDs as recipients: ```json {"op":"agent","action":"send","name":"receiver-1","text":"The parser change is ready."} {"op":"agent","action":"inbox"} {"op":"agent","action":"send","name":"parent","text":"I found a blocking dependency."} ``` Sibling messages enter the recipient's steering queue between turns and remain available in its inbox. The parent reads child reports with `{"op":"agent","action":"inbox","name":"parent"}`. Inbox pages return `nextCursor`, `hasMore`, and `oldestCursor`; pass `nextCursor` as `cursor` to request later messages. Each recipient retains its latest 64 messages, so clients can use `oldestCursor` to detect that earlier messages have fallen outside retention. The parent authenticates the child's sender identity. Children can read their own inbox, list peers, and send to a live sibling or the parent; transcript, steering, kill, and revival controls remain on the parent surface. The local connection closes when its owning child run ends or is cancelled. ### Session Management Sessions persist as JSONL files with full conversation history: ```bash # Continue most recent session pi --continue # Open specific session pi --session ~/.pi/agent/sessions/--home-user-project--/2024-01-15T10-30-00.jsonl # Ephemeral (no persistence) pi --no-session ``` Sessions support: - Tree structure for conversation branching - Model/thinking level change tracking - Automatic compaction for long conversations ### Extended Thinking Enable deep reasoning for complex problems: ```bash pi --thinking high "Design a distributed rate limiter" ``` Thinking levels: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max` ### Customization (Skills & Prompt Templates) - **Skills**: Place a skill directory containing `SKILL.md` under `.pi/skills/`, `.agents/skills/`, `~/.pi/agent/skills/`, or `~/.agents/skills/` and invoke it with `/skill:name`. See [skill discovery and precedence](docs/skills.md#locations). - **Prompt templates**: Markdown files under `~/.pi/agent/prompts/` or `.pi/prompts/`; invoke via `/