sequenceDiagram
participant Client
participant Pi
Client->>Pi: initialize
Pi-->>Client: initialized event
Client->>Pi: setModel
Pi-->>Client: modelChanged event
Client->>Pi: prompt
Pi-->>Client: assistantMessage (streaming)
Pi-->>Client: toolCall
Client->>Pi: toolResult (if needed)
Pi-->>Client: assistantMessage
Pi-->>Client: turnEnd
21 RPC Mode
21.1 Overview
RPC (Remote Procedure Call) mode turns Pi into a stdin/stdout JSON-RPC server. It is designed for editor integrations, IDE plugins, and other process-control scenarios where a parent process needs bidirectional communication with Pi.
21.2 Starting RPC Mode
pi --mode rpcOr programmatically:
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | pi --mode rpcRPC mode reads JSON-RPC messages from stdin and writes responses and events to stdout. Each message is a single line of JSON.
21.3 Protocol Overview
The protocol follows JSON-RPC 2.0 conventions:
- Requests:
{ "jsonrpc": "2.0", "id": 1, "method": "...", "params": {...} } - Responses:
{ "jsonrpc": "2.0", "id": 1, "result": {...} } - Notifications (events):
{ "jsonrpc": "2.0", "method": "...", "params": {...} }(noid)
21.4 Commands
21.4.1 initialize
Initialize the RPC session.
// Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"cwd": "/path/to/project",
"model": "claude-sonnet-4-20250514",
"provider": "anthropic",
"apiKey": "sk-...",
"thinkingLevel": "medium",
"tools": ["read", "write", "edit", "bash"]
}
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"sessionPath": "~/.pi/agent/sessions/.../550e8400.jsonl",
"model": "claude-sonnet-4-20250514",
"provider": "anthropic"
}
}21.4.2 prompt
Send a prompt to the model.
// Request
{
"jsonrpc": "2.0",
"id": 2,
"method": "prompt",
"params": {
"content": "Summarize this codebase"
}
}
// Response (immediate)
{
"jsonrpc": "2.0",
"id": 2,
"result": { "accepted": true }
}21.4.3 sendUserMessage
Send a user message programmatically.
{
"jsonrpc": "2.0",
"id": 3,
"method": "sendUserMessage",
"params": {
"content": "What files did you modify?"
}
}21.4.4 abort
Abort the current operation.
{
"jsonrpc": "2.0",
"id": 4,
"method": "abort",
"params": {}
}21.4.5 setModel
Change the active model.
{
"jsonrpc": "2.0",
"id": 5,
"method": "setModel",
"params": {
"model": "gpt-5"
}
}21.4.6 setThinkingLevel
Change the thinking level.
{
"jsonrpc": "2.0",
"id": 6,
"method": "setThinkingLevel",
"params": {
"level": "high"
}
}21.4.7 setActiveTools
Set the active tool set.
{
"jsonrpc": "2.0",
"id": 7,
"method": "setActiveTools",
"params": {
"tools": ["read", "bash"]
}
}21.4.8 compact
Trigger context compaction.
{
"jsonrpc": "2.0",
"id": 8,
"method": "compact",
"params": {
"prompt": "Focus on database changes"
}
}21.4.9 newSession
Start a new session.
{
"jsonrpc": "2.0",
"id": 9,
"method": "newSession",
"params": {}
}21.4.10 resumeSession
Resume a previous session.
{
"jsonrpc": "2.0",
"id": 10,
"method": "resumeSession",
"params": {
"sessionId": "550e8400-e29b-41d4-a716-446655440000"
}
}21.4.11 shutdown
Shut down the RPC session.
{
"jsonrpc": "2.0",
"id": 11,
"method": "shutdown",
"params": {}
}21.4.12 getStatus
Get current session status.
// Request
{
"jsonrpc": "2.0",
"id": 12,
"method": "getStatus",
"params": {}
}
// Response
{
"jsonrpc": "2.0",
"id": 12,
"result": {
"idle": false,
"model": "claude-sonnet-4-20250514",
"thinkingLevel": "medium",
"contextUsage": 0.45,
"activeTools": ["read", "write", "edit", "bash"],
"sessionId": "550e8400-..."
}
}21.5 Event Types
Events are notifications sent from Pi to the client without a request ID:
21.5.1 initialized
{
"jsonrpc": "2.0",
"method": "initialized",
"params": {
"sessionId": "...",
"model": "...",
"provider": "..."
}
}21.5.2 assistantMessage
Streamed assistant response:
{
"jsonrpc": "2.0",
"method": "assistantMessage",
"params": {
"type": "delta",
"delta": "Let me look at "
}
}{
"jsonrpc": "2.0",
"method": "assistantMessage",
"params": {
"type": "complete",
"content": "Let me look at the files...",
"usage": { "inputTokens": 1500, "outputTokens": 45 },
"cost": 0.0023
}
}21.5.3 toolCall
{
"jsonrpc": "2.0",
"method": "toolCall",
"params": {
"toolName": "read",
"input": { "path": "src/app.ts" },
"toolCallId": "tc_001"
}
}21.5.4 toolResult
{
"jsonrpc": "2.0",
"method": "toolResult",
"params": {
"toolCallId": "tc_001",
"output": { "content": [{ "type": "text", "text": "..." }] },
"details": {}
}
}21.5.5 turnEnd
{
"jsonrpc": "2.0",
"method": "turnEnd",
"params": {
"totalTokens": 3200,
"totalCost": 0.015
}
}21.5.6 error
{
"jsonrpc": "2.0",
"method": "error",
"params": {
"message": "Rate limit exceeded",
"code": "rate_limit",
"retryable": true
}
}21.5.7 contextUsage
{
"jsonrpc": "2.0",
"method": "contextUsage",
"params": {
"used": 45000,
"total": 200000,
"percentage": 0.225
}
}21.6 Extension UI Protocol
Extensions can send custom UI events through RPC:
{
"jsonrpc": "2.0",
"method": "extension:ui",
"params": {
"extension": "my-extension",
"type": "dialog",
"data": {
"title": "Confirmation",
"message": "Allow rm -rf?"
}
}
}The client should respond with:
{
"jsonrpc": "2.0",
"method": "extension:response",
"params": {
"extension": "my-extension",
"result": true
}
}21.7 Error Handling
Errors follow JSON-RPC 2.0 error format:
{
"jsonrpc": "2.0",
"id": 2,
"error": {
"code": -32602,
"message": "Invalid params: 'content' is required",
"data": { "field": "content" }
}
}21.7.1 Standard Error Codes
| Code | Description |
|---|---|
-32700 |
Parse error (invalid JSON) |
-32600 |
Invalid request |
-32601 |
Method not found |
-32602 |
Invalid params |
-32603 |
Internal error |
-32000 |
Model error |
-32001 |
Tool error |
-32002 |
Rate limit |
-32003 |
Context limit |
21.8 Client Examples
21.8.1 Node.js Client
import { spawn } from "node:child_process";
const pi = spawn("pi", ["--mode", "rpc"], { stdio: ["pipe", "pipe", "inherit"] });
let buffer = "";
pi.stdout.on("data", (data) => {
buffer += data.toString();
const lines = buffer.split("\n");
buffer = lines.pop() || "";
for (const line of lines) {
if (line.trim()) {
const message = JSON.parse(line);
handleMessage(message);
}
}
});
function send(method: string, params: object, id?: number) {
const msg = { jsonrpc: "2.0", method, params, ...(id ? { id } : {}) };
pi.stdin.write(JSON.stringify(msg) + "\n");
}
function handleMessage(message: any) {
if (message.method === "assistantMessage") {
process.stdout.write(message.params.delta || "");
} else if (message.method === "turnEnd") {
console.log("\n--- Turn complete ---");
}
}
// Initialize
send("initialize", {
cwd: process.cwd(),
model: "claude-sonnet-4",
}, 1);
// Send prompt after initialization
setTimeout(() => {
send("prompt", { content: "List files in the current directory" }, 2);
}, 1000);21.8.2 Python Client
import subprocess
import json
def send(proc, method, params, msg_id=None):
msg = {"jsonrpc": "2.0", "method": method, "params": params}
if msg_id is not None:
msg["id"] = msg_id
proc.stdin.write(json.dumps(msg) + "\n")
proc.stdin.flush()
def read_message(proc):
line = proc.stdout.readline()
return json.loads(line)
pi = subprocess.Popen(
["pi", "--mode", "rpc"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
# Initialize
send(pi, "initialize", {"cwd": ".", "model": "claude-sonnet-4"}, 1)
resp = read_message(pi)
print("Initialized:", resp)
# Send prompt
send(pi, "prompt", {"content": "Hello!"}, 2)
# Read events
while True:
msg = read_message(pi)
if msg.get("method") == "assistantMessage":
params = msg["params"]
if params.get("type") == "delta":
print(params["delta"], end="", flush=True)
elif params.get("type") == "complete":
print("\n[Complete]")
elif msg.get("method") == "turnEnd":
breakRPC mode is ideal for building IDE integrations, web interfaces, or any application that needs fine-grained control over Pi’s behavior.