pi-lot
-
An experimental desktop app for the Pi coding agent, built in 10 days and shelved (aka. the slop creep that shaped how I think about what to build.)
Status: cancelled.
-

pi-lot overview — the desktop app for pi coding agents
Overview
pi-lot was a desktop GUI for the pi coding agent. An Electron app that spawned pi as a subprocess and communicated over JSON-RPC, giving you streaming chat, tool execution display, model selection, session management, and analytics in a native window.
99% vibe coded. 248 commits in 10 days. Then shelved. The full retrospective is in the blog post.
The numbers
| Metric | Value |
|---|---|
| Commits | 248 across 9 active days |
| Lines of code | ~57,000 across 339 files |
| Agent sessions | 160 |
| API cost | $389.65 |
| Tokens consumed | 525 million |
| Tool calls | 9,806 |
| Hand-written code | ~1% |
Architecture
Process model
Standard Electron two-process architecture with pi spawned as a JSON-RPC subprocess. This avoided the nightmare of bundling pi’s ESM modules into Electron’s CommonJS main process — pi runs exactly as it would in a terminal.
graph TD
subgraph Electron App
M[Main Process<br/>Node.js]
P[Preload Script<br/>contextBridge]
R[Renderer Process<br/>React + Vite]
end
subgraph External
PI[Pi Agent<br/>--mode rpc]
FS[File System<br/>workspaces, sessions, config]
GIT[Git<br/>diff, status, log]
end
M -->|spawn + stdin/stdout| PI
M -->|contextBridge| P
P -->|IPC bridge| R
M -->|read/write| FS
M -->|child_process| GIT
PI -->|stdout JSON events| M
M -->|stdin JSON-RPC| PI
RPC communication flow
The main process manages pi’s lifecycle through PiRpcClient, which wraps a child process with JSON-RPC over stdin/stdout. Streaming events flow back through IPC to the renderer.
sequenceDiagram
participant U as User
participant R as Renderer<br/>(React)
participant M as Main Process<br/>(Node.js)
participant PI as Pi Agent<br/>(RPC mode)
U->>R: Type message
R->>M: IPC: session:send
M->>PI: stdin: JSON-RPC request
loop Streaming response
PI-->>M: stdout: message_start
M-->>R: IPC: session:event
R-->>U: Render streaming markdown
PI-->>M: stdout: content_block_delta
M-->>R: IPC: session:event
R-->>U: Update content
end
Note over PI: Tool execution
PI->>PI: read/write/edit/bash
PI-->>M: stdout: tool_use + tool_result
M-->>R: IPC: session:event
R-->>U: Render structured tool output
PI-->>M: stdout: message_stop
M-->>R: IPC: session:event
R-->>U: Message complete
IPC handler architecture
IPC handlers were split into domain-specific modules to keep the main process organized. Each module registers its own handlers through a shared context.
graph LR
subgraph Main Process
REG[registerAllIpcHandlers]
CTX[Shared Context<br/>mainWindow, sessionManager,<br/>sendToRenderer]
end
REG --> S[sessions.ts<br/>open, send, abort,<br/>steer, fork, compact]
REG --> W[workspaces.ts<br/>add, list, open,<br/>remove, pin]
REG --> SET[settings.ts<br/>pi config, auth,<br/>keybindings, providers]
REG --> G[git.ts<br/>diff, status, log,<br/>branches]
REG --> AN[analytics.ts<br/>session stats,<br/>cost aggregation]
REG --> A[app.ts<br/>window controls,<br/>dialog, path utils]
REG --> AC[auto-commit.ts<br/>commit staging]
CTX -.-> S
CTX -.-> W
CTX -.-> SET
CTX -.-> G
Session management
SessionManager in the main process tracks all open pi sessions, each backed by its own PiRpcClient subprocess. It handles lifecycle (open, send, abort, fork, compact, dispose) and routes events back through a callback.
graph TD
SM[SessionManager] -->|manages| S1[Session A<br/>PiRpcClient]
SM -->|manages| S2[Session B<br/>PiRpcClient]
SM -->|manages| S3[Session C<br/>PiRpcClient]
S1 -->|spawn| P1[pi --mode rpc<br/>cwd: /project-a]
S2 -->|spawn| P2[pi --mode rpc<br/>cwd: /project-b]
S3 -->|spawn| P3[pi --mode rpc<br/>cwd: /project-c]
SM -->|onSessionEvent callback| MAIN[Main Process<br/>sendToRenderer]
subgraph Per Session Capabilities
SEND[send message]
ABORT[abort generation]
STEER[steer model/thinking]
FORK[fork session tree]
COMPACT[compact context]
CYCLE[cycle model]
end
SM --> SEND & ABORT & STEER & FORK & COMPACT & CYCLE
State management — domain store architecture
The client-side state went through a major refactor (SR-TASK series). The monolithic session store was split into four focused domain stores, with a compatibility facade preserving the original API.
graph TD
subgraph Domain Stores
REG[session-registry-store<br/>identity, file paths,<br/>workspace, active pointer]
RT[session-runtime-store<br/>streaming, connected,<br/>model, thinking, retry]
TR[session-transcript-store<br/>messages, tool executions,<br/>content blocks]
TREE[session-tree-store<br/>raw content, UI leaf ID,<br/>conversation branching]
end
FACADE[session-store.ts<br/>Compatibility Facade<br/>composes openSessions Map]
FACADE -->|reads from| REG & RT & TR & TREE
subgraph React Components
CHAT[Chat View]
SB[Sidebar]
TB[Toolbar]
STAT[Status Bar]
end
REG --> SB
RT --> TB & STAT
TR --> CHAT
TREE --> CHAT
subgraph Event Pipeline
EVT[session-events-reducer.ts<br/>Pure functions: event to mutation descriptor]
DISP[Dispatcher<br/>applies descriptors to stores]
end
EVT --> DISP
DISP --> REG & RT & TR & TREE
The event pipeline used pure reducer functions — each event maps to a typed mutation descriptor with no store access or side effects. A dispatcher applies these descriptors to the appropriate stores. This pattern enabled comprehensive regression testing.
Frontend feature structure
graph TD
subgraph Routes — TanStack Router
APP[/_app route<br/>main layout]
REV[/_review route<br/>review layout]
SETTINGS[/settings/*<br/>settings pages]
end
subgraph Features
CHAT[chat/<br/>message rendering,<br/>adapters, markdown]
SESS[sessions/<br/>events, resolver,<br/>tree utils, identity]
MOD[model-selector/<br/>dynamic model switching]
THINK[thinking/<br/>thinking level controls]
REVIEW[review/<br/>file diff panel]
ANALYTICS[analytics/<br/>session cost tracking]
WS[workspaces/<br/>folder management]
SBAR[status-bar/<br/>token usage, cost]
ACOMMIT[auto-commit/<br/>commit staging]
end
APP --> CHAT & SESS & MOD & THINK & SBAR
REV --> REVIEW
SETTINGS --> MOD
subgraph UI Components — shadcn/ui
SIDEBAR[sidebar]
CMD[command palette]
POPOVER[popover]
SCROLL[scroll-area]
TOAST[toast notifications]
end
Key features at time of shelving
- Streaming chat with markdown rendering in a native window
- Tool execution display — structured rendering of file reads, writes, edits, bash commands
- Model selector with dynamic model switching per active session
- Session management — multi-session with workspace-scoped sidebar navigation
- Session forking — branch conversation trees at any point
- Context compaction — trigger pi’s compaction from the UI
- Analytics route — session cost tracking and aggregation
- Review route — file diff panel for reviewing agent changes and submitting feedback back to the agent
- Git integration — diff, status, log, branch info surfaced in the UI
- Auto-commit — commit staging from the app
- Full theme system — dark/light mode with design tokens
- Structured logging with validation and event schemas
- Regression test harnesses for session UX failure modes
- Settings — pi provider config, keybindings editor, auth management
What was left on the roadmap
- Session persistence and resume across app restarts
- Multiple workspace tabs
- File references (@file mentions in chat)
- Image paste support
- Keyboard shortcuts system
Why it was shelved
Read the full story: pi-lot: a slop creep journey
What came after
The project split into two layers that belong in different places: