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.

GitHub / Visit project

-

Cover for pi-lot overview — the desktop app for pi coding agents

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

MetricValue
Commits248 across 9 active days
Lines of code~57,000 across 339 files
Agent sessions160
API cost$389.65
Tokens consumed525 million
Tool calls9,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:

  • agents — agent-facing: extensions, skills, themes, knowledge base. How agents work.
  • ariadne — human-facing: session analytics, cost tracking, file hotspots, session replay. What agents did.