Context Pilot
An AI coding agent that lives in your terminal — and a fleet orchestrator and web cockpit to run many of them at once.
Context Pilot is a ~47,000-line Rust project (18 module crates + a main binary + an orchestration backend) plus a React web client. It started as a single self-hosting TUI in which an AI agent develops the very tool it runs inside, and grew an orchestration layer so that a fleet of such agents can be discovered, observed, commanded, and supervised — from the terminal or from a browser.
This document describes how the whole thing fits together: the agent's main loop, the module system, the three-tier durability model, the orchestrator, the bridge that joins an agent to a fleet, the web frontend, and the sidecar services (console server, Meilisearch, SQLite entities).
---
1. System overview
Context Pilot is not one process. It is a small constellation of cooperating surfaces:
┌───────────────────────────────────────────┐
│ Web frontend (web/) │
│ React 19 · TanStack Query · Vite · SSE │
└───────────────┬───────────────────────────┘
│ REST + Server-Sent Events
│ (HTTP :7878)
┌───────────────▼───────────────────────────┐
│ Orchestrator (cp-orchestrator) │
│ registry · materialized view · │
│ stream hub · supervisor · REST/SSE │
└───┬───────────────┬───────────────┬────────┘
oplog tail │ commands │ stream tap │ spawn / signals┌───▼───────────────▼───────────────▼────────┐ │ Agent (the TUI, src/) │ │ event loop · modules · LLM streaming · │ │ bridge (CPBRIDGE=1) · tier-② state │ └───┬───────────────┬───────────────┬────────┘ │ │ │ ┌───────▼──────┐ ┌──────▼──────┐ ┌──────▼───────┐ │ Console server│ │ Meilisearch │ │ SQLite │ │ (long-running │ │ (full-text │ │ entities │ │ processes) │ │ search) │ │ (structured) │ └───────────────┘ └─────────────┘ └───────────────┘
**One agent = one folder.** Every agent owns a *realm* (a working directory) and stores all its state under `<folder>/.context-pilot/`. Switching agents is switching folders. The orchestrator manages many such realms; the web cockpit renders them.
| Surface | Crate / dir | Role |
|---|---|---|
| **Agent (TUI)** | `src/` (`tui` binary) | The interactive coding agent: event loop, tools, LLM streaming, panels, persistence. |
| **Orchestrator** | `crates/cp-orchestrator` | Fleet control plane: discover, observe, command, supervise; serves REST + SSE on `:7878` (loopback — on the appliance Caddy fronts it on `:80`/`:443`). |
| **Web frontend** | `web/` | Browser cockpit: fleet dashboard, per-agent threads / panels / file manager. |
| **Console server** | `crates/cp-console-server` | Per-realm daemon running child processes that survive TUI restarts. |
| **Meilisearch** | external process | Full-text index of project files + logs. |
---2. The agent (TUI)
The agent is a blocking, single-threaded Elm/Redux-style application built on Ratatui + Crossterm. No async runtime — everything is driven by one event loop.
Main loop
src/app/run/lifecycle.rs is the heart. Each iteration is input-first (a non-blocking poll), then it advances background work: stream events, the tool pipeline, cache updates, file/GitHub watchers, the spine (notifications), and reverie (the context optimizer). The loop runs on an adaptive cadence — ~8 ms while streaming or when panels are dirty, ~2 ms when an orchestrator is connected (so a web command applies within a tick), ~50 ms when idle — and renders at ~28 fps.
State changes flow through a central action dispatcher (src/app/actions/), Redux-style: an action mutates State, the next render reflects it. Key pipelines:
- Streaming (src/app/run/streaming.rs) — LLM chunks drive a typewriter buffer; ToolUse blocks queue pending tool calls; Done finalizes the message and reconciles token/cost accounting. API errors retry with backoff.
- Tool pipeline (src/app/run/tools/pipeline.rs) — pre-flight validation → queue interception → execution → file-edit callbacks → tempo break → sentinel deferral. Each tool is its own flame-graph span.
- Prompt assembly (src/app/promptbuilder.rs) — rebuilds the LLM prompt in three phases: context panels injected as synthetic tooluse/toolresult pairs, then the conversation history, then strict role alternation.
Main-loop watchdog
Because the loop is single-threaded, every step runs inline on the one thread — so any synchronous block (a tool hitting a hung dependency, a slow panel rehash, a lock wait, a stalled socket read) freezes the entire UI for its full duration, historically with no trace of which step wedged. The watchdog (src/app/run/tools/watchdog.rs) ends that blind spot. It is the interactive sibling of the headless deadman, but purely observational: it never terminates, re-execs, or signals the process (a human is at the keyboard) — it only writes a diagnostic so a freeze that "had no apparent reason" becomes "the log says it wedged in <step for <Ns."