A Rust port of earendil-works/pi —
the pi agent harness — focused on the core coding-agent loop.
The upstream project is a TypeScript monorepo (~189k LOC). This port covers
the agent runtime, multi-provider LLM API, and CLI end-to-end and ships a
working pi binary that talks to real Anthropic, OpenAI, Google, and any
OpenAI-compatible endpoint.
pi/
├─ Cargo.toml # workspace, version 1.0.0, MSRV 1.80
└─ crates/
├─ pi-ai/ # ←→ packages/ai
├─ pi-agent/ # ←→ packages/agent
└─ pi-coding-agent/ # ←→ packages/coding-agent (binary: `pi`)
| TS package | Rust crate | Status |
|---|---|---|
@earendil-works/pi-ai |
pi-ai |
SSE streaming for Anthropic Messages, OpenAI Chat Completions, Google Generative AI. Retry with Retry-After. Cancellation token. Custom headers. OpenAI-compatible passthrough (OpenRouter, Groq, etc.). |
@earendil-works/pi-agent-core |
pi-agent |
Streaming run_agent loop with per-tool permission gate, typed AgentError, #[instrument] spans. Builtin tools: read, write, edit, bash, ls, grep, glob, web_fetch, todo. |
@earendil-works/pi-coding-agent |
pi-coding-agent |
pi CLI: print mode (-p), interactive REPL with streaming render, session persistence + --resume, AGENTS.md / CLAUDE.md loader, slash commands (/help /reset /model /tools /cost /sessions /resume /session), interactive permission prompts (--yolo to skip). pi sessions list/show/delete subcommand. |
@earendil-works/pi-tui |
— | Not ported (TS terminal renderer). |
@earendil-works/pi-web-ui |
— | Not ported (browser components). |
The streaming LLM API and agent runtime are published on crates.io:
[dependencies]
pi-ai = "1.2" # provider-agnostic streaming
pi-agent = "1.2" # agent loop, permission policy, built-in toolsInstall the CLI via:
cargo install pi-coding-agent # installs binary `pi`See the crate-level docs at crates.io/crates/pi-ai and crates.io/crates/pi-agent.
git clone https://github.com/nktkt/pi.git
cd pi
cargo build --release
export ANTHROPIC_API_KEY=sk-ant-...
# or any of: OPENAI_API_KEY, GOOGLE_API_KEY / GEMINI_API_KEY
# One-shot:
./target/release/pi -p "List the files in this directory and summarize them"
# Same prompt, JSON-lines on stdout for scripting:
./target/release/pi -p "..." --json
# Interactive:
./target/release/pi
# Resume a saved session:
./target/release/pi --resume <id>
# Skip permission prompts (bash/write/edit run unconfirmed):
./target/release/pi --yolo -p "Run the test suite"
# List saved sessions:
./target/release/pi sessions listPick the model explicitly:
PI_MODEL=claude-opus-4-7 pi -p "..." # Anthropic
PI_MODEL=gpt-4o pi -p "..." # OpenAI
PI_MODEL=gemini-2.0-flash pi -p "..." # Google (via GOOGLE_API_KEY)Any base URL whose API matches OpenAI Chat Completions works through the
openai-completions code path. From Rust:
use pi_ai::Model;
let m = Model::openai_compat(
"openrouter",
"anthropic/claude-3.5-sonnet",
"https://openrouter.ai/api/v1",
200_000, 8_192,
);Or override StreamOptions::base_url at call time. The same code path is
exercised by Groq, Together, Cerebras, DeepSeek, Fireworks, xAI, etc.
┌────────────────────────────┐
│ pi-coding-agent (bin) │
│ print mode | interactive │
│ session persistence │
│ permission prompts │
│ AGENTS.md loader │
└──────────────┬─────────────┘
│ AgentConfig + tools + PermissionPolicy
▼
┌────────────────────────────┐
│ pi-agent │
│ run_agent / _with_history │
│ streaming events │
│ permission gate │
└──────────────┬─────────────┘
│ Context, StreamOptions (incl. CancellationToken)
▼
┌────────────────────────────┐
│ pi-ai │
│ stream_simple → Provider │
│ ├─ AnthropicProvider │
│ ├─ OpenAiProvider │
│ └─ GoogleProvider │
│ SSE + retry + cancel │
└────────────────────────────┘
- Append the user prompt onto the message transcript.
- Build a
Context { system_prompt, messages, tools }and callpi_ai::stream_simple(). - Consume the SSE event stream. Emit
TextDeltaevents to the agent subscriber as they arrive (the CLI prints them live). - When the assistant finishes, append it. For each
Content::ToolCall, consultPermissionPolicyif the tool flaggedrequires_permission(), thenexecute()and append theToolResultMessage. - Repeat until
stop_reason ≠ ToolUseormax_turnsis reached. - After every turn the CLI persists the session to disk.
cargo test # 10 passing, no network
cargo fmt --check
cargo clippy --workspace --all-targets -- -D warningsCI runs the same checks on macOS and Linux against stable + MSRV
(1.80).
- CHANGELOG.md — what shipped in 1.0.0.
- ROADMAP.md — future 1.x targets: OpenAI Responses API,
Bedrock, prompt caching, MCP client,
--jsonprint mode, mdBook docs, crates.io publishing.
MIT — same as the upstream project.