Pi Bridge
Overview¶
The Pi bridge wraps the pi CLI (from the pi-mono project), a minimal terminal coding harness that is aggressively extensible. Pi supports multi-provider model selection, thinking levels, TypeScript extensions, skills, prompt templates, and fine-grained JSONL event streaming. It provides both token usage and cost data.
The bridge is implemented by PiBridge and configured through PiBridgeBuilder. Access the builder through the AgentCtrl facade:
use Cognesy\AgentCtrl\AgentCtrl;
use Cognesy\AgentCtrl\Enum\AgentType;
// Dedicated factory method
$builder = AgentCtrl::pi();
// Or via the generic factory
$builder = AgentCtrl::make(AgentType::Pi);
Prerequisites¶
Install Pi globally via npm or bun:
Configure an API key:
Basic Usage¶
use Cognesy\AgentCtrl\AgentCtrl;
$response = AgentCtrl::pi()
->execute('Explain the architecture of this project.');
echo $response->text();
With model selection:
$response = AgentCtrl::pi()
->withModel('sonnet')
->execute('Review the test suite.');
echo $response->text();
Model Selection¶
Pi supports flexible model identification with optional provider prefix and thinking level shorthand:
// Model name only (uses default provider)
AgentCtrl::pi()->withModel('sonnet');
// Provider/model format
AgentCtrl::pi()->withModel('openai/gpt-4o');
AgentCtrl::pi()->withModel('anthropic/claude-opus-4-6');
AgentCtrl::pi()->withModel('google/gemini-2.5-pro');
// Model with thinking level shorthand
AgentCtrl::pi()->withModel('sonnet:high');
Use withProvider() to explicitly set the provider when the model name alone is ambiguous:
Thinking Levels¶
Pi supports six thinking levels that control how much the model reasons before responding:
use Cognesy\AgentCtrl\Pi\Domain\Enum\ThinkingLevel;
AgentCtrl::pi()->withThinking(ThinkingLevel::Off); // No thinking
AgentCtrl::pi()->withThinking(ThinkingLevel::Minimal); // Minimal reasoning
AgentCtrl::pi()->withThinking(ThinkingLevel::Low); // Light reasoning
AgentCtrl::pi()->withThinking(ThinkingLevel::Medium); // Moderate reasoning
AgentCtrl::pi()->withThinking(ThinkingLevel::High); // Deep reasoning
AgentCtrl::pi()->withThinking(ThinkingLevel::ExtraHigh); // Maximum reasoning
Alternatively, use the model shorthand: ->withModel('sonnet:high').
System Prompts¶
Replace or extend the default system prompt:
// Replace entirely
AgentCtrl::pi()
->withSystemPrompt('You are a PHP code reviewer.')
->execute('Review this code.');
// Append to default
AgentCtrl::pi()
->appendSystemPrompt('Focus on security issues.')
->execute('Review the authentication module.');
Tool Control¶
By default, Pi provides four tools: read, write, edit, and bash. Additional built-in tools include grep, find, and ls.
// Restrict to read-only tools
AgentCtrl::pi()
->withTools(['read', 'grep', 'find', 'ls'])
->execute('Analyze the codebase structure.');
// Disable all tools (pure conversation)
AgentCtrl::pi()
->noTools()
->execute('Explain dependency injection.');
File Arguments¶
Attach files to the prompt using withFiles(). These are passed as @-prefixed arguments to Pi:
$response = AgentCtrl::pi()
->withFiles([
'/projects/app/src/PaymentService.php',
'/projects/app/tests/PaymentServiceTest.php',
])
->execute('Review these files for potential issues.');
Extensions and Skills¶
Pi supports TypeScript extensions and skills that add custom tools, commands, and capabilities:
// Load specific extensions
AgentCtrl::pi()
->withExtensions(['./my-extension.ts'])
->execute('...');
// Disable extension auto-discovery (load only explicit ones)
AgentCtrl::pi()
->noExtensions()
->withExtensions(['./deploy-ext.ts'])
->execute('...');
// Load specific skills
AgentCtrl::pi()
->withSkills(['/path/to/my-skill'])
->execute('...');
// Disable skill auto-discovery
AgentCtrl::pi()
->noSkills()
->execute('...');
Streaming with Pi¶
Pi streams output as JSONL with granular event types. The bridge normalizes these into the standard callback API:
use Cognesy\AgentCtrl\AgentCtrl;
use Cognesy\AgentCtrl\Dto\AgentResponse;
$response = AgentCtrl::pi()
->onText(fn(string $text) => print($text))
->onToolUse(fn(string $tool, array $input, ?string $output) => print("\n> [{$tool}]\n"))
->onError(fn(string $message, ?string $code) => print("\nError: {$message}\n"))
->onComplete(fn(AgentResponse $r) => print("\n--- Done ---\n"))
->executeStreaming('Analyze the error handling in this codebase.');
Event Normalization¶
Pi emits a rich set of JSONL events that are normalized:
MessageUpdateEvent(text_delta) -- Text deltas delivered throughonText().ToolExecutionEndEvent-- Tool results delivered throughonToolUse()with tool name, call ID, result, and error flag.ErrorEvent-- Errors delivered throughonError().SessionEvent,AgentStart/End,TurnStart/End,MessageStart/End,ToolExecutionStart-- Lifecycle events available through thewiretap()event system.
Session Management¶
Pi maintains sessions as JSONL files. Agent-Ctrl extracts session IDs from the session header event:
// First execution
$first = AgentCtrl::pi()->execute('Create an implementation plan.');
$sessionId = $first->sessionId();
// Continue the most recent session
$next = AgentCtrl::pi()
->continueSession()
->execute('Begin implementing the plan.');
// Resume a specific session by ID
if ($sessionId !== null) {
$next = AgentCtrl::pi()
->resumeSession((string) $sessionId)
->execute('Continue with the next step.');
}
// Ephemeral mode -- don't save session
AgentCtrl::pi()
->ephemeral()
->execute('Quick one-off question.');
// Custom session storage
AgentCtrl::pi()
->withSessionDir('/tmp/pi-sessions')
->execute('...');
API Key Override¶
Override the API key for a specific execution without changing environment variables:
Usage and Cost Data¶
Pi provides token usage and cost data from the message events:
$response = AgentCtrl::pi()
->withModel('sonnet')
->execute('Analyze the project dependencies.');
$usage = $response->usage();
if ($usage !== null) {
echo "Input tokens: {$usage->input}\n";
echo "Output tokens: {$usage->output}\n";
echo "Total tokens: {$usage->total()}\n";
if ($usage->cacheRead !== null) {
echo "Cache read: {$usage->cacheRead}\n";
}
if ($usage->cacheWrite !== null) {
echo "Cache write: {$usage->cacheWrite}\n";
}
}
$cost = $response->cost();
if ($cost !== null) {
echo sprintf("Cost: $%.6f\n", $cost);
}
Data Availability¶
| Data Point | Available | Notes |
|---|---|---|
| Text output | Yes | Extracted from message_update text_delta events |
| Tool calls | Yes | Normalized from tool_execution_end with call IDs and error status |
| Session ID | Yes | Extracted from JSONL session header event |
| Token usage | Yes | Input, output, cache read, cache write tokens |
| Cost | Yes | Cost in USD from usage data |
| Parse diagnostics | Yes | Malformed JSON line counts and samples |
Complete Example¶
use Cognesy\AgentCtrl\AgentCtrl;
use Cognesy\AgentCtrl\Dto\AgentResponse;
use Cognesy\AgentCtrl\Pi\Domain\Enum\ThinkingLevel;
$response = AgentCtrl::pi()
->withModel('sonnet')
->withThinking(ThinkingLevel::High)
->appendSystemPrompt('Focus on security and performance.')
->withTools(['read', 'bash', 'edit', 'grep'])
->withFiles(['/projects/app/src/Kernel.php'])
->withTimeout(300)
->inDirectory('/projects/app')
->onText(fn(string $text) => print($text))
->onToolUse(fn(string $tool, array $input, ?string $output) => print("\n> [{$tool}]\n"))
->onComplete(fn(AgentResponse $r) => print("\n--- Complete ---\n"))
->executeStreaming('Review the application architecture and suggest improvements.');
if ($response->isSuccess()) {
echo "\nReview completed successfully.\n";
echo "Tools used: " . count($response->toolCalls) . "\n";
$usage = $response->usage();
if ($usage !== null) {
echo "Tokens: {$usage->total()} (in: {$usage->input}, out: {$usage->output})\n";
}
$cost = $response->cost();
if ($cost !== null) {
echo sprintf("Cost: $%.6f\n", $cost);
}
} else {
echo "\nFailed with exit code: {$response->exitCode}\n";
}
Comparison with Other Bridges¶
| Feature | Claude Code | Codex | OpenCode | Pi |
|---|---|---|---|---|
| System prompts | Yes (replace + append) | No | No | Yes (replace + append) |
| Permission modes | Yes (4 levels) | No | No | No |
| Turn limits | Yes | No | No | No |
| Sandbox modes | No | Yes (3 levels) | No | No |
| Image input | No | Yes | No | No |
| Thinking levels | No | No | No | Yes (6 levels) |
| Named agents | No | No | Yes | No |
| File attachments | No | No | Yes | Yes (@-prefix) |
| Extensions | No | No | No | Yes (TypeScript) |
| Skills | No | No | No | Yes |
| Tool control | No | No | No | Yes (select/disable) |
| Session sharing | No | No | Yes | No |
| Session titles | No | No | Yes | No |
| Ephemeral mode | No | No | No | Yes |
| API key override | No | No | No | Yes |
| Token usage | No | Yes (partial) | Yes (full) | Yes |
| Cost tracking | No | No | Yes | Yes |
| Multi-provider models | No | No | Yes | Yes |
Environment Variables¶
| Variable | Description |
|---|---|
ANTHROPIC_API_KEY |
Anthropic API key |
OPENAI_API_KEY |
OpenAI API key |
PI_CODING_AGENT_DIR |
Override Pi config directory (default: ~/.pi/agent) |
PI_SKIP_VERSION_CHECK |
Skip version check at startup |
PI_CACHE_RETENTION |
Set to long for extended prompt cache |