A single-user AI chat workspace with router-selected session styling, resumable NDJSON streaming, conversation branching, interactive UI blocks, and VM-backed tool execution.
BetterClaude is a local-first AI assistant interface designed around the idea that different questions deserve different response styles. Instead of a one-size-fits-all chat, every session is classified on first message — selecting both a shape (response structure) and style (tone/voice) from a library of skillpacks. Conversations support branching and forking, so you can explore alternative directions without losing context. An integrated VM space provides real execution capabilities: file I/O, bash, Python, web search, and persistent memory.
The assistant is backed by multiple LLM providers (Anthropic Claude and OpenAI GPT) and streams responses over a resumable NDJSON protocol that survives reconnects.
betterclaude/
├── apps/
│ ├── api/ Fastify backend — sessions, streaming, tools, spaces
│ └── web/ React 19 + Vite SPA — chat UI, brain mode, sidebar
├── packages/
│ ├── pi-runtime/ Agent runtime — tool registration, prompt composition, event emission
│ └── ui-catalog/ JSON-render component registry + Zod validation schemas
├── shared/ TypeScript contracts shared between API and web
├── docs/ Phase-by-phase implementation specs
└── scripts/ Dev tooling (startup scripts)
| Layer | Technology |
|---|---|
| Backend | Fastify, TypeScript, SQLite + Drizzle ORM |
| Frontend | React 19, Vite 7, React Router v7 |
| Agent runtime | pi-agent-core, pi-ai |
| UI rendering | json-render + shadcn component catalog |
| Data viz | Recharts (charts), Leaflet (maps), TanStack Table |
| Search | Exa Web Search API |
| Session routing | Cerebras API (fast classification) |
| VM execution | OrbStack (macOS) — per-user Linux space |
| Package manager | pnpm workspaces |
Sessions, branches, and entries form a tree:
Session (model, title, router decision)
└─ Branch (root entry, leaf entry)
└─ Entry (role, content, parent → child chain)
└─ Run (status, model, token usage, memory access log)
Entries link via parentEntryId, enabling transcript reconstruction by walking from any branch's leaf back to its root. Forking creates a new branch that shares parent entries up to the fork point.
Chat responses stream over POST /v1/chat/stream as newline-delimited JSON. Event types:
delta— incremental text tokensthinking_start/thinking_delta/thinking_end— extended thinkingtool_start/tool_progress/tool_end— tool execution lifecycleui/ui_patch— interactive UI block specssync— reconnect recovery (textSoFar + sequence number)final— completion with metadataerror/ping— error reporting and keepalive
On reconnect, clients pass the last runId to receive a sync event with buffered state, enabling seamless resumption.
On the first message of every session, a fast classifier (Cerebras) selects:
- Shape skillpack — controls response structure (e.g., "Balanced Answer", "Deep Dive")
- Style skillpack — controls tone and voice (e.g., "Professional", "Warm")
- Voice and emotion profiles — JSON personality descriptors locked for session lifetime
Skillpacks are Markdown files with YAML frontmatter, stored in apps/api/skillpacks/ and hot-reloaded into SQLite via Chokidar. The router runs once per session; all subsequent messages inherit the locked style.
- Fork from any entry to create a new branch exploring an alternative direction
- Branches share parent history up to the fork point — no duplication
- Each branch maintains independent leaf state and run history
- Branch tabs in the UI allow switching between parallel explorations
Every assistant response includes contextual follow-up suggestions (2-4 options) generated via the response_meta_emit tool. Options are grounded in the specific content of the turn — not generic prompts — and branch the conversation in genuinely different directions.
The assistant can emit structured UI components inline:
- Tables — sortable, filterable, with column definitions
- Charts — line, bar, area, pie via Recharts
- Maps — markers, polylines, polygons via Leaflet
- Cards, badges, lists, buttons — general layout primitives
UI blocks are defined as JSON specs (json-render format) and rendered client-side with the shadcn-based catalog. Buttons can trigger actions that feed back into the conversation.
The default landing view — an infinite chronological timeline of all sessions. New messages are routed intelligently: the brain resolver decides whether to create a new session or continue an existing one based on content classification.
Each user gets an OrbStack Linux VM ("space") with:
- File operations — read, write, edit files in
/home/space/ - Bash execution — full shell access with Python, DuckDB, Polars
- Public file serving — nginx serves
/home/space/public/for generated assets - File upload/download — via API endpoints
- Skills index — auto-generated
SKILLS.mdlisting available CLI tools
- Documents written to the space are chunked and indexed into SQLite FTS5
memory_getretrieves specific documents by pathmemory_searchperforms full-text search across all memory- Memory access is logged per-run for observability
- Manual consolidation via the
/v1/sleependpoint
Sessions can target different LLM backends:
- Anthropic — Claude Opus 4.5
- OpenAI — GPT 5.2
Model selection is per-session and can be changed between runs.
| Endpoint | Description |
|---|---|
POST /v1/sessions |
Create session with model |
GET /v1/sessions |
List sessions (paginated) |
GET /v1/sessions/:id |
Get session + branches |
POST /v1/chat/stream |
Send message, stream response (NDJSON) |
POST /v1/runs/:id/cancel |
Cancel active run |
POST /v1/branches |
Fork branch from entry |
GET /v1/branches/:id/entries |
Get transcript (root to leaf) |
POST /v1/brain/resolve-target |
Route message to new or existing session |
GET /v1/brain/sessions |
List all sessions for brain timeline |
GET /v1/brain/space/files |
Browse space filesystem |
POST /v1/brain/space/uploads |
Upload file to space |
GET /v1/brain/space/download |
Download file from space |
POST /v1/sleep |
Trigger memory consolidation |
GET /v1/models |
List available models |
- Node.js >= 20
- pnpm (
corepack enable && corepack prepare) - OrbStack (macOS, for VM space features)
Copy apps/api/.env.example to apps/api/.env and fill in:
ANTHROPIC_API_KEY— for Claude modelsOPENAI_API_KEY— for GPT modelsCEREBRAS_API_KEY— for session routingEXA_API_KEY— for web search
pnpm install
pnpm db:migrate
pnpm devThis starts both services:
- API:
http://localhost:8787 - Web:
http://localhost:5173
pnpm dev:api # API only
pnpm dev:web # Web only
pnpm typecheck # Type-check all packages
pnpm lint # Lint with oxlint
pnpm db:seed # Seed database (optional)apps/api/src/
├── config/ Constants, base system prompt
├── db/ Drizzle schema, migrations, seed
├── routes/ Fastify route handlers
├── runs/ Chat runner orchestration
├── services/ Session router, skillpack sync
└── spaces/ VM space management, tooling, settings
apps/web/src/
├── components/
│ ├── brain/ Brain mode timeline, space settings, file browser
│ ├── chat/ Transcript, message bubbles, streaming, UI blocks
│ └── sidebar/ Session list, navigation
├── hooks/ Zustand stores (session, branch, stream, brain)
└── lib/ API client, markdown rendering
packages/pi-runtime/ Agent creation, tool schemas, prompt composition
packages/ui-catalog/ Component registry, Zod validation, json-render integration
shared/src/contracts/ TypeScript types shared across API and web