A desktop app that drives many Claude Code sessions at once. See every session in a sidebar, type into any one, watch it stream, and approve its tool calls inline. Built on the Agent SDK, in three isolated process layers. An experiment I took deep, then set aside: it was time-intensive to maintain, and isn’t something I actively develop now.
Driving several Claude Code sessions at once means juggling ten terminal windows, one per project or task. There is no single surface to see what each session is doing, type into a specific one, or approve its tool calls without alt-tabbing between them.
I wanted a cockpit: one window that drives every session, shows each one’s live state, and lets me approve a tool call in the session that asked for it, without losing the others.
Basalt is one desktop window driving N concurrent Claude Code sessions, split into three processes that each do one job. A Tauri shell in Rust owns the native window and supervises the rest. A React frontend renders the fleet. A Node sidecar holds the engine.
The Rust shell carries no business logic. It opens the window, spawns the sidecar with a random port and auth token, restarts it if it crashes, and kills it on exit. The frontend talks to the sidecar over a localhost-only WebSocket gated by that token, so no other process on the machine can connect. Tauri over Electron keeps the whole app around 10 to 15 MB instead of 100-plus.
Each session is a real Claude Agent SDK query(), not a wrapper around the terminal UI. I chose the SDK over PTY-scraping the TUI (brittle, no structured results) and over headless claude -p (no live tool-approval callback).
The SDK gives what a controller actually needs: a canUseTool callback to intercept every tool call, partial-message streaming for live output, per-session interrupt, model and permission-mode control, and many concurrent sessions in one process. A SessionManager owns the registry; each Session wraps one query() and holds its own transcript. One session erroring is marked errored and never touches its siblings.
Because the SDK emits structured events, the UI is a clean chat-style transcript instead of a terminal emulator. Text deltas coalesce into blocks; each tool call is a card; a card awaiting permission becomes an approval card with approve, deny, or edit-the-input.
A left rail lists every session as a live status row: a colored dot, the label, and a one-line activity such as editing or needs approval. The main pane is the selected session’s transcript and composer. The whole thing wears an Ember identity, a near-black basalt palette with a lava-amber accent, where amber always means a tool is waiting on you.
The harder problem is running several focused sessions in the same repository at once without them overwriting each other. Basalt keeps one working tree and prevents collisions by locking files at edit time.
The lock gate is a PreToolUse hook, deliberately not the canUseTool callback. canUseTool is skipped in the permissive permission modes you actually use for fleet work, while a PreToolUse hook fires in every mode and can block. When a session tries to write a file another session holds, Basalt denies the call with a reason, and the blocked session’s Claude reroutes itself. Zero trust in the models cooperating. A live board, a single-writer .basalt/board.md, shows who is touching what.
All of it shipped: the sidecar engine, the frontend, the Tauri shell, file-lock coordination, and lazy session resume that survives closing the app. It installs as a real Basalt.app and rebuilds with a single studio basalt update.
The sharp edge was packaging. A double-clicked .app gets a minimal PATH, so the user’s node isn’t on it; Basalt resolves the real binary through the login shell at startup and spawns it directly. A fake-engine mode swaps the SDK for a scripted generator, so the entire UI can be built, demoed, and screenshotted without spending a single token. For now it is daily studio tooling, proving it holds up before it ships in the open.
Three experiments, one studio. The harness operates it, Lattice remembers it, and Basalt explored driving a fleet of agents at once: built deep, then set aside as too time-intensive to keep up. Each one exists because the tool I needed didn’t yet.