Skip to content
Crumble
Esc
↑↓navigate↵open⌘Jpreview
On this page

Architecture

How the server is built, from sign-in to storage to workspaces.

The server (apps/server) is one Bun process that serves the /v1 API and the web app (apps/web). Inside it, each account runs in its own private runtime with its own storage, so most of the code never has to think about other accounts.

On Disk

Every account gets its own directory under CRUMBLE_DATA_DIR:

CRUMBLE_DATA_DIR/
├── auth.sqlite              accounts and sessions, shared by all accounts
├── .auth-secret
└── users/
    └── USER_ID/
        ├── .secret.key      or .secret.key.wrapped with a master key
        ├── store.sqlite     product store
        ├── push.sqlite      push store
        ├── agents/
        │   └── BOT_ID.sqlite    one Pi Durable store per bot
        ├── groups/
        │   └── GROUP_ID.sqlite  one per group chat
        └── claude/          Claude Code config for this account

Boundaries

Server code lives in feature folders under apps/server/src/, such as bots/, group-chats/, jobs/, workspaces/, and credentials/.

Accounts

app/auth.ts, app/server.ts, and app/tenant.ts. Better Auth stores accounts and sessions in auth.sqlite. Requests authenticate before they’re routed to a private user runtime under users/USER_ID. Each account owns its databases, .secret.key, bot runtimes, provider logins, and workspace namespace. Startup recovers every account runtime, so schedules run without anyone signed in.

Effect Services

app/tenant.ts. Each account is one Effect Layer graph: Drizzle repositories over ProductDatabase, then feature services (Bots, BotRuntime, GroupChats, BotSkills, Scheduler, BotWorkspaces, Plugins). Features register their bot tools in BotExtensions. Tagged errors (shared/errors.ts) map to HTTP status codes at the Hono boundary.

Product Store and Pi Durable

The two stores split along one line: the product store holds what you configure, and Pi Durable holds what happens.

Store File Holds
Product SQLite store.sqlite Agent profiles, group definitions, memories, skill packages, learned_changes, schedules, MCP servers, and encrypted credentials.
Pi Durable, per bot agents/<bot id>.sqlite Conversation entries, working context, tool calls, compaction, coding and desktop job conversations, and post-turn reviews.
Pi Durable, per group groups/<group id>.sqlite The group chat’s shared transcript.

Plugins and Claude Code

plugins.ts and providers/. Compiled-in plugins contribute providers (Melious, Claude Code), job kinds (coding, desktop), and message gateway adapters. The Claude Code provider runs each model call through a fresh claude -p process with an inert MCP tool list, a loopback admission relay, and per-account users/USER_ID/claude config directories.

Workspaces and the Credential Proxy

workspaces/, credentials/, and bot-desktop/. MachineWorkspaces manages lazy boot, holds, idle stop, and pi-env streams across Microsandbox microVMs (MicrosandboxWorkspaces) and Railway sandboxes (RailwayWorkspaces).

A per-workspace TLS credential proxy swaps host-bound credential placeholders, using a per-bot ECDSA P-256 certificate authority, and scrubs secrets from responses. Desktop viewers bridge noVNC to the guest’s Xvnc over an exec stream, with an RFB input filter gated by the control lease.

Web App

apps/web/. A React app grouped by feature under apps/web/features/, using TanStack Router, TanStack Query, Tailwind CSS, and shadcn/ui. Bun bundles it on demand.

Decisions

Architecture decision records are in bot/docs/adr.

Was this page helpful?