Pi RPC Mode
Pi’s RPC mode is explicitly designed for embedding and headless operation. It exposes:- Structured command/response protocol with request IDs
- Event stream for message deltas, tool execution updates, and lifecycle events
- Session management (create, resume, switch, fork)
- Built-in tools (read, write, edit, bash, grep, find)
- Extension support for custom tools via skills
Spawning Pi Processes
The daemon spawns one Pi subprocess per session, managed byPiProcessManager and PiProcess.
Spawn Command (cli/src/daemon/pi-process.ts:46-80)
Tool Set
Rubber Duck enables Pi’s default tool set:read: Read file contents (supports multiple files)write: Write or overwrite fileedit: Apply precise edits with old/new string replacementbash: Execute shell commands in workspacegrep: Search file contents with regexfind: Search for files by name pattern
Model Configuration
Pi model selection is determined by:RUBBER_DUCK_PI_MODELenvironment variable (e.g.,gpt-4o-mini,gpt-4o)- If unset and
OPENAI_API_KEYis present: default togpt-4o-mini - Otherwise: Pi uses its own default model
RUBBER_DUCK_PI_PROVIDER or auto-detected from API keys.
Thinking Level (RUBBER_DUCK_PI_THINKING):
- Default:
off(for speed) - Options:
off,minimal,low,medium,high,xhigh
RPC Protocol
Command/Response
The daemon sends commands to Pi stdin as NDJSON:cli/src/daemon/pi-process.ts:184-213):
Event Stream
Pi pushes events to stdout (noid field):
cli/src/daemon/pi-process.ts:106-130):
Key Commands
prompt
Sends a user message to Pi and starts agent turn.
abort
Cancels the currently running agent operation.
agent_end with reason: "aborted".
get_state
Queries current session state.
duck doctor for diagnostics.
Event Bus and Pub/Sub
The daemon uses an EventBus to fan out Pi events to multiple subscribed clients (CLI terminals, voice app).Subscription Model (cli/src/daemon/event-bus.ts)
- CLI runs
duck attach→ daemon subscribes client to session - Pi emits
message_update→ daemon publishes to event bus - Event bus forwards event to subscribed client(s)
- Client’s socket receives NDJSON event:
{ event: "pi_event", sessionId, data: {...} } - CLI renderer formats and prints to terminal
Multi-Client Support
Multiple terminals can follow the same session:- Monitoring a background session
- Pair programming (one voice, one terminal)
- Debugging (verbose mode in one terminal, clean in another)
Tool Execution
CLI-Driven Tools (Pi Executes)
When the user typesduck say "run the tests", Pi receives the prompt and decides to call bash:
-
Pi emits
tool_execution_start: -
Pi executes
npm testin workspace, streaming stdout: -
Pi emits
tool_execution_end: - Pi continues agent turn, potentially calling more tools or generating a response.
Voice-Driven Tools (Daemon Executes)
When the assistant calls a tool during a voice session, execution is handled by the daemon, not Pi. This is because Pi is not running for voice sessions — the voice app uses the OpenAI Realtime API directly. Flow (cli/src/daemon/voice-tools.ts):
-
Realtime API sends
response.function_call_arguments.done - VoiceSessionCoordinator enqueues function call
-
App sends
voice_tool_callrequest to daemon: -
Daemon executes tool:
- Daemon returns result
-
App sends result to Realtime API:
read_file(path)write_file(path, content)edit_file(path, oldString, newString, replaceAll?)bash(command)grep_search(pattern, include?)find_files(pattern)
Tool Confinement
All tool execution (Pi and daemon) is confined to the workspace directory:- Pi subprocess
cwdis set to workspace path - Daemon voice tools execute with workspace as base path
- Relative paths are resolved within workspace
- Absolute paths and
..escapes are allowed but discouraged (future: enforce workspace confinement)
Session Management
Session Persistence
Pi sessions are stored as JSONL files:- Specifies
--session-dirto isolate Rubber Duck sessions - Tracks session metadata (ID, name, workspace) in
metadata.json
Resume and Continue
Resume Specific Session:--session <sessionFile>.
Continue Most Recent:
-c flag (Pi resumes last session in session dir).
Multiple Sessions Per Workspace
Users can create multiple independent conversation threads in the same repo:- Pi subprocess
- Session JSONL file
- Event stream subscription
Switching Voice Session
The voice app can switch between sessions:-
From CLI:
Daemon updates
metadata.jsonand pushesvoice_session_changedevent to app. - From Menu Bar: User selects session from popover list. App updates selection and daemon is notified.
-
From Hotkey:
App reads
metadata.jsonon connect to sync latest selection.
Health Monitoring
The daemon runs a health monitor that checks Pi process liveness every 30 seconds (cli/src/daemon/health.ts):
- Event bus publishes
pi_diedevent - CLI displays error and exits (if following that session)
- Voice app shows error overlay (if that session was active)
Extension UI Requests
Pi supports interactive prompts viaextension_ui_request events. For example, a Pi skill might ask:
cli/src/renderer/ui-handler.ts):
The follow and say commands auto-handle UI requests using @clack/prompts:
extension_ui_request during voice, the daemon returns a placeholder response and logs a warning.
Error Handling
Pi Process Crashes
pi_died.
Command Timeouts
Each Pi command has a 30-second timeout. If Pi doesn’t respond:Tool Execution Failures
Pi tools can fail (e.g., file not found, bash command error). Pi emits:Prompt Errors
If a prompt fails (e.g., model API error), Pi returns:Configuration
Pi Binary Resolution (cli/src/constants.ts)
Model and Provider
Session Directory
Testing
Rubber Duck includes E2E tests for the full daemon + Pi integration:- Starts daemon
- Attaches workspace
- Sends prompt: “List files in the current directory”
- Validates Pi events (tool_execution_start, tool_execution_end, agent_end)
- Checks final response contains file names
- Cleans up daemon
cli/tests/e2e.test.ts for implementation.
Next Steps
- Architecture Overview — Full system diagram
- Session Model — Workspaces, sessions, concurrency, persistence