22  JSON Event Stream Mode

22.1 Overview

JSON Event Stream mode outputs all Pi events as newline-delimited JSON objects. It is designed for logging, piping to other tools, and building custom integrations without the bidirectional complexity of RPC mode.

22.2 Usage

pi --mode json "Summarize this codebase"

Or with a pipe:

echo "Explain this function" | pi --mode json

In JSON mode, Pi:

  1. Reads the initial prompt from command-line arguments or stdin
  2. Processes the prompt through the full agent loop
  3. Outputs every event as a JSON line to stdout
  4. Exits when the agent turn completes

22.3 Output Format

Each line is a JSON object with this structure:

{
  "type": "event_type",
  "timestamp": "2026-07-31T12:00:00.000Z",
  "data": { ... }
}

22.3.1 Example Session

$ pi --mode json "What files are in this project?"
{"type":"session_start","timestamp":"...","data":{"sessionId":"550e8400","model":"claude-sonnet-4"}}
{"type":"model_start","timestamp":"...","data":{"model":"claude-sonnet-4","provider":"anthropic"}}
{"type":"assistant_message","timestamp":"...","data":{"type":"delta","delta":"I'll"}}
{"type":"assistant_message","timestamp":"...","data":{"type":"delta","delta":" check"}}
{"type":"assistant_message","timestamp":"...","data":{"type":"delta","delta":" the"}}
{"type":"tool_call","timestamp":"...","data":{"toolName":"bash","input":{"command":"ls -la"},"toolCallId":"tc_001"}}
{"type":"tool_result","timestamp":"...","data":{"toolCallId":"tc_001","output":{"content":[{"type":"text","text":"total 48\ndrwxr-xr-x ..."}]}}
{"type":"assistant_message","timestamp":"...","data":{"type":"complete","content":"Here are the files...","usage":{"inputTokens":1500,"outputTokens":45}}}
{"type":"model_end","timestamp":"...","data":{"usage":{"inputTokens":1500,"outputTokens":45},"cost":0.0023}}
{"type":"turn_end","timestamp":"...","data":{"totalTokens":3200,"totalCost":0.015}}
{"type":"session_end","timestamp":"...","data":{}}

22.4 Event Types

22.4.1 Session Events

22.4.1.1 session_start

{
  "type": "session_start",
  "data": {
    "sessionId": "550e8400-e29b-41d4-a716-446655440000",
    "sessionPath": "~/.pi/agent/sessions/.../550e8400.jsonl",
    "model": "claude-sonnet-4-20250514",
    "provider": "anthropic"
  }
}

22.4.1.2 session_end

{
  "type": "session_end",
  "data": {}
}

22.4.2 Model Events

22.4.2.1 model_start

{
  "type": "model_start",
  "data": {
    "model": "claude-sonnet-4-20250514",
    "provider": "anthropic"
  }
}

22.4.2.2 model_end

{
  "type": "model_end",
  "data": {
    "usage": {
      "inputTokens": 1500,
      "outputTokens": 45,
      "cacheReadTokens": 200,
      "cacheWriteTokens": 0
    },
    "cost": 0.0023,
    "durationMs": 1200
  }
}

22.4.3 Message Events

22.4.3.1 assistant_message (delta)

Streamed content delta:

{
  "type": "assistant_message",
  "data": {
    "type": "delta",
    "delta": "Let me "
  }
}

22.4.3.2 assistant_message (complete)

Full message when generation is complete:

{
  "type": "assistant_message",
  "data": {
    "type": "complete",
    "content": "Let me look at the files in this project.",
    "usage": {
      "inputTokens": 1500,
      "outputTokens": 45
    }
  }
}

22.4.3.3 assistant_thinking (delta)

Thinking/reasoning content delta (for reasoning models):

{
  "type": "assistant_thinking",
  "data": {
    "type": "delta",
    "delta": "The user wants to know about files..."
  }
}

22.4.3.4 assistant_thinking (complete)

Full thinking content:

{
  "type": "assistant_thinking",
  "data": {
    "type": "complete",
    "content": "The user wants to know about files. I should use ls..."
  }
}

22.4.4 Tool Events

22.4.4.1 tool_call

{
  "type": "tool_call",
  "data": {
    "toolName": "bash",
    "input": { "command": "ls -la" },
    "toolCallId": "tc_001"
  }
}

22.4.4.2 tool_result

{
  "type": "tool_result",
  "data": {
    "toolCallId": "tc_001",
    "output": {
      "content": [
        { "type": "text", "text": "total 48\ndrwxr-xr-x ..." }
      ]
    },
    "details": {}
  }
}

22.4.4.3 tool_error

{
  "type": "tool_error",
  "data": {
    "toolCallId": "tc_002",
    "toolName": "bash",
    "error": "Command failed with exit code 1",
    "stderr": "Error: file not found"
  }
}

22.4.5 Turn Events

22.4.5.1 turn_end

{
  "type": "turn_end",
  "data": {
    "totalTokens": 3200,
    "totalCost": 0.015,
    "durationMs": 4500
  }
}

22.4.6 Error Events

22.4.6.1 error

{
  "type": "error",
  "data": {
    "message": "Rate limit exceeded",
    "code": "rate_limit",
    "retryable": true
  }
}

22.4.7 Compaction Events

22.4.7.1 compaction

{
  "type": "compaction",
  "data": {
    "entriesSummarized": 15,
    "freedTokens": 8000,
    "summary": "Previous conversation discussed..."
  }
}

22.5 Message Types

The data.type field in assistant_message events distinguishes between:

Type Description
delta Streaming text fragment
complete Full message after generation completes

22.6 Processing JSON Output

22.6.1 Using jq

Filter and format events:

# Get only assistant messages
pi --mode json "Explain this code" | jq 'select(.type == "assistant_message" and .data.type == "delta") | .data.delta' -r

# Get tool calls only
pi --mode json "Fix the bug" | jq 'select(.type == "tool_call") | .data.toolName'

# Get token usage
pi --mode json "Review code" | jq 'select(.type == "model_end") | .data.usage'

# Pretty-print full output
pi --mode json "Write tests" | jq .

22.6.2 Using Node.js

const { spawn } = require("node:child_process");

const pi = spawn("pi", ["--mode", "json", "Summarize this codebase"]);

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()) continue;
    const event = JSON.parse(line);
    
    switch (event.type) {
      case "assistant_message":
        if (event.data.type === "delta") {
          process.stdout.write(event.data.delta);
        }
        break;
      case "tool_call":
        console.log(`\n[Tool: ${event.data.toolName}]`);
        break;
      case "turn_end":
        console.log(`\n\nTokens: ${event.data.totalTokens}, Cost: $${event.data.totalCost}`);
        break;
    }
  }
});

22.6.3 Using Python

import subprocess
import json

proc = subprocess.Popen(
    ["pi", "--mode", "json", "Explain this code"],
    stdout=subprocess.PIPE,
    text=True,
)

for line in proc.stdout:
    event = json.loads(line)
    
    if event["type"] == "assistant_message":
        data = event["data"]
        if data["type"] == "delta":
            print(data["delta"], end="", flush=True)
    elif event["type"] == "tool_call":
        print(f"\n[Tool: {data['toolName']}]")
    elif event["type"] == "turn_end":
        print(f"\n\nTotal: {data['totalTokens']} tokens, ${data['totalCost']}")

22.7 Comparison with RPC Mode

Feature JSON Mode RPC Mode
Communication One-way (stdout only) Bidirectional (stdin/stdout)
Use case Logging, piping Interactive control
Steering messages Not supported Supported
Tool result injection Not supported Supported
Model switching Via CLI args only At any time
Session management Via CLI args only Full control
Complexity Simple Complex
Best for Scripts, CI/CD IDE integrations, bots
Tip

Use JSON mode when you need a simple one-shot pipeline. Use RPC mode when you need ongoing bidirectional communication with Pi.

22.8 Use Cases

22.8.1 CI/CD Integration

# Generate a PR summary
pi --mode json "Summarize changes since main" | \
  jq 'select(.type == "assistant_message" and .data.type == "complete") | .data.content' -r | \
  gh pr comment --body-file -

22.8.2 Automated Code Review

# Run code review and save report
pi --mode json "Review code for security issues" | \
  jq 'select(.type == "assistant_message" and .data.type == "complete") | .data.content' -r \
  > security-review.md

22.8.3 Streaming to a File

# Stream output to a file in real-time
pi --mode json "Write documentation" | \
  tee raw-events.jsonl | \
  jq 'select(.type == "assistant_message" and .data.type == "delta") | .data.delta' -r \
  > documentation.md