Skip to main content
Fleet Pi is a TanStack Start web app with a Nitro-backed API, a React 19 UI, and a repo-local agent workspace. Locally it runs on the Vite dev server. Hosted deployments run on Cloudflare Workers, where each signed-in user’s Agent chat runs in a per-user Pi Durable Object. This page maps the runtime boundaries.

Topology

Layers

Browser client

React 19 with TanStack Router. AgentChat composes InputBar, MessageList, and the right-hand resources and workspace panels. Streaming chat, structured plan cards, and tool cards are rendered with the shared agent-elements integration from packages/hax-design (@workspace/hax-design), the single source of truth for Fleet Pi chat surfaces, the OpenUI kit, and shared Pi protocol types. State management:
  • usePiChat orchestrates the NDJSON stream lifecycle, tool rendering, and queued steering / follow-up prompts.
  • useChatStorage keeps minimal session metadata (sessionFile, sessionId) in localStorage so a refresh can hydrate the matching Pi session file.

apps/web — TanStack Start

File-based API routes live under apps/web/src/routes/api/. The Vite dev server serves them locally, and the Cloudflare Worker serves them in hosted deployments: Internal modules:
  • lib/pi/server.ts — Pi runtime cache, createPiRuntime, queuePromptOnActiveSession, NDJSON encoding.
  • lib/pi/server-chat-stream.ts — assistant turn lifecycle (beginAssistantTurn, handleSessionEvent, finalizeAssistantTurn).
  • lib/pi/plan-mode.ts — mode allowlists and the plan-mode Pi extension.
  • lib/pi/circuit-breaker.ts — opossum configuration around Bedrock invocations.
  • lib/pi/run-provenance.ts — provenance recording around session and tool events.
  • lib/workspace/server.ts and workspace-contract.ts — durable workspace contract, manifest, and section kinds.
  • lib/pii/sanitizer.ts — input redaction before logging.
  • lib/logger.ts — Pino logger with redaction and requestId correlation.
  • lib/pi/durable/hosted-agent-turn.ts and user-pi-durable.ts — hosted Agent turns, forks, and transcript erasure on the per-user Durable Object.

Hosted chat on Cloudflare Workers

On the Cloudflare deployment (FLEET_PI_DEPLOYMENT=cloudflare), the Worker binds one SQLite-backed Durable Object class, UserPiDurable, as USER_PI_DURABLE. Each signed-in user resolves to one object, which opens a Pi Durable harness (@earendil-works/pi-durable) on its own storage.
  • Transcript — the object’s SQLite storage is the hosted transcript. Hosted session ids start with durable: and have no sessionFile.
  • Neon — keeps auth, settings, and an optional session projection. It is no longer the source of truth for hosted transcripts.
  • Daytona — the user’s volume holds the harness workspace and artifacts. Without a Daytona sandbox, the agent works on a built-in workspace stored in the Durable Object.
  • Models — each turn sets the conversation’s model, so model switches apply to resumed conversations. Forks inherit the parent conversation’s model.
  • Erasure — DELETE /api/chat/account clears the object’s storage. Deleting a single hosted session only hides it.
Sessions that still carry a JSONL sessionFile keep using the legacy runtime path. See the API reference for the request rules.

Workspace context on hosted turns

On hosted turns, the bundled workspace-context extension reads Markdown from your workspace (your Daytona volume, or the built-in workspace) and adds three prompt sections. It never runs code from the workspace. The agent reads a matching skill’s SKILL.md with the read tool when a task calls for it. If your workspace has no AGENTS.md or system/ policy, the agent is told to read before writing and to ask before destructive changes. Fleet Pi reads these files once per turn. It reads them again after a write or edit call changes AGENTS.md, system/, memory/project/, or pi/skills/, and after any bash command. A message of the form /name args expands from pi/prompts/name.md before Fleet Pi submits it, including messages sent mid-turn as steering or follow-ups. The template supports $ARGUMENTS, $@, and positional $1, $2, and so on. If no matching template exists, the message is sent unchanged.

Built-in workspace without Daytona

When you have no Daytona API key, or your sandbox is not ready yet, hosted turns use a built-in workspace stored in your Durable Object. Fleet Pi seeds it from the same versioned template as Daytona volumes. It never overwrites your edits, and changed template files land in .fleet/template-updates/<version>/. On the built-in workspace:
  • The agent gets only the read, search, write, and edit tools. There is no shell, so bash, code mode, and MCP tools are unavailable.
  • Workspace context and /name prompt templates work as described above.
  • Writes to memory/project/ and plans/ still need a rationale, and system/ and evals/ stay protected.
  • Only text files (.md, .txt, .json, .yaml, .yml) are stored, under AGENTS.md, README.md, artifacts/, evals/, instructions/, memory/, plans/, pi/skills/, pi/prompts/, scratch/, and system/.
  • Each file can be up to 64 KiB, with at most 2 MiB and 500 files in total.
The Workspace panel and workspace routes read and write this store. DELETE /api/chat/account erases it. After you add a Daytona key, the first sandbox turn copies the files the agent or the Workspace panel wrote in the agent areas to your volume, in the background. If a file already exists on the volume with different content (an empty file counts), Fleet Pi writes the copy to .fleet/do-import/<path> instead. Files it cannot confirm stay pending until the next sandbox turn. After the handover, your volume is the source of truth and the built-in workspace is read-only for the agent.

agent-workspace/

The durable adaptive layer. The workspace server reads memory, plans, skills, and artifacts directly from files so every change shows up in Git diffs. The contract is versioned (WORKSPACE_CONTRACT_VERSION = 1) and the canonical section set is fixed in workspace-contract.ts: instructions/, system/, memory/, plans/, skills/, evals/, artifacts/, scratch/, pi/, indexes/. Sections have kinds — canonical, temporary, or projection — that the workspace server enforces.

.pi/

Committed project Pi configuration. settings.json is a compatibility bridge that points Pi at workspace-native resource directories. Built-in Pi extensions and committed skills also live here.

External

Fleet Pi calls whichever model provider is configured — Google Gemini by default (gemini-3.5-flash), or any other supported provider (Amazon Bedrock, OpenAI, Anthropic, Mistral, Groq, Vertex, Ollama). Provider selection lives in .pi/settings.json; credentials live in .env. Amazon Bedrock specifically is wrapped by a process-wide opossum circuit breaker (bedrock-api). When you switch Fleet Pi to Bedrock, every Bedrock invocation passes through these settings: When the Bedrock breaker is open, requests fail fast with "Bedrock API is temporarily unavailable due to repeated failures. Please try again later.". Other providers fail through Pi’s standard error path (no breaker), so reliability tuning for non-Bedrock providers happens upstream in @earendil-works/pi-coding-agent. Daytona is an optional external service for authenticated user sandboxes — enabled by setting DAYTONA_API_KEY. See the API reference for the sandbox surface.

Packages

Pi SDK packages: @earendil-works/pi-coding-agent, @earendil-works/pi-ai, and @earendil-works/pi-agent-core at ^1.2.0, plus @earendil-works/pi-durable pinned to 1.2.0. Other key dependencies: @tanstack/react-start, opossum, pino + pino-pretty, zod + @asteasolutions/zod-to-openapi, vitest, @playwright/test, husky + lint-staged. apps/web/src/routeTree.gen.ts is generated. Do not edit by hand.

Request lifecycle

  1. The browser sends a chat message to /api/chat as JSON.
  2. ChatRequestSchema (zod) validates the body.
  3. sanitizePii redacts the message before logging.
  4. createRequestLogger attaches a correlation requestId.
  5. On the Cloudflare deployment, a hosted session is forwarded to the caller’s UserPiDurable object, which streams the turn. Otherwise createPiRuntime returns a warm Pi runtime (TTL controlled by FLEET_PI_RUNTIME_TTL_MS) or builds one.
  6. The Pi runtime invokes the configured model provider. Bedrock calls go through the opossum circuit breaker; other providers go through Pi’s standard provider path.
  7. Events are forwarded as NDJSON via encodeEvent: start, delta, thinking, tool, plan, state, queue, compaction, retry, done, error. Every turn ends with a done frame.
  8. createRunProvenanceRecorder records the session lifecycle and tool events.
  9. On client refresh, POST /api/chat/resume or GET /api/chat/session hydrates the session from the Pi session file or, for hosted sessions, from the Durable Object.

Agent workspace

What lives in agent-workspace/ and why it is reviewable.

Adaptive workspace contract

Canonical sections, manifest, and projection rules.

Runtime SDK seams

Safe extension points around the Pi runtime.

API reference

Endpoint contracts and stream events.
Last modified on November 10, 2026