Architecture¶
The system follows a decoupled client-server model. The backend serves a JSON API and WebSockets; the frontend is a React SPA. In production, the backend also serves the built frontend as static files.
High-Level Overview¶
graph TD
User[User Mobile/Desktop] -->|HTTPS / Tailscale| App[FastAPI + React SPA]
subgraph Host Machine
API[FastAPI Backend] -->|LibTmux| Tmux[Tmux Server]
API -->|Subprocess| Git[Git CLI]
API -->|TinyDB| DB[(JSON Metadata)]
API -->|AI Providers| AI[Ollama / OpenAI / Anthropic / Google / Cloud]
Tmux -->|pty| Shell[Zsh / Bash]
Shell -->|Run| Claude[Claude Code / Cursor / Aider / etc.]
Git -->|File System| Worktrees[Git Worktrees]
BG[Background Services]
BG -->|5s poll| DiffCache[Diff Cache]
BG -->|2s poll| IdleMonitor[Idle Monitor]
BG -->|5m poll| BackupSched[Backup Scheduler]
end
subgraph Browser Client
React[React SPA]
Store[TanStack Query]
Term[XTerm.js]
end
React -->|REST / JSON| API
Term -->|WebSocket| API
Tech Stack¶
| Component | Choice | Rationale |
|---|---|---|
| Language | Python 3.11+ | Required for libtmux and subprocess management |
| Web Framework | FastAPI | Async support for WebSockets, auto Swagger docs |
| Frontend | React + Vite + TypeScript | Robust ecosystem, PWA support |
| State | TanStack Query | Server state caching, ETag-aware polling for diffs |
| Terminal | xterm.js | Industry standard terminal emulator |
| Styling | Tailwind CSS | Utility-first CSS for responsive layouts |
| Persistence | TinyDB | Serverless, portable, human-readable JSON |
| AI Providers | Ollama, OpenAI, Anthropic, Google, OpenAI-compatible, Lumbergh Cloud | Multi-provider via common interface |
Middleware Stack¶
Requests pass through these layers in order:
- CORS -- allow cross-origin requests (dev mode)
- AuthMiddleware -- cookie-based ASGI middleware. Blocks unauthenticated requests to
/api/*(except/api/auth/*and/api/health). Works for both HTTP and WebSocket. - ETagMiddleware -- computes MD5 hash of GET response bodies. Returns
304 Not Modifiedif the client'sIf-None-Matchheader matches. Reduces bandwidth for polling clients.
Data Flow¶
Terminal Stream (WebSocket)¶
- Client opens WebSocket to
ws://host/api/session/{name}/stream - Backend spawns a PTY attached to the tmux pane via session pooling
- Backend streams raw bytes to client via
xterm.write() - Client keystrokes sent as JSON
{type: "input", data: "..."} - Backend injects via
send-keys(small text) orload-buffer+paste-buffer(large text)
Live Diffs (Background Cache + ETag Polling)¶
- A background
DiffCacheservice runs every 5 seconds for active sessions - Fingerprinting: checks
.git/HEAD,.git/index,.git/refs/mtimes +git status --porcelainhash. Skips expensive git commands if nothing changed. - Compute: runs
git diff HEAD(including untracked files) in a thread pool - Serve:
GET /api/sessions/{name}/git/diffreturns cached data instantly - Client: TanStack Query polls with
If-None-Match. Gets304when unchanged, full response when data is fresh.
Idle Detection¶
- Background
IdleMonitorpolls all live tmux sessions every 2 seconds - Captures recent pane content and matches against patterns (Claude spinner, approval prompts, rate limit messages, shell prompts, etc.)
- States:
unknown→idle→working→error→stalled(working > 10 min) - Persisted to TinyDB per-session with timestamps
Authentication¶
- Password set via
LUMBERGH_PASSWORDenv var or Settings UI POST /api/auth/loginvalidates password, setslumbergh_sessioncookie (HMAC-SHA256 signed, 30-day expiry)- ASGI middleware checks cookie on every
/api/*request - If no password is set, auth is completely bypassed
Worktree Management¶
- User configures a repo search directory in settings
- App scans for directories containing
.git - On session creation with worktree mode:
- Validates the parent repo exists
- Runs
git worktree addwith the specified branch - Creates tmux session in the worktree directory
- Stores session metadata in TinyDB