← Zurück zum Portfolio
B
Fallstudie · Archiviertes Experiment5 Min. Lesezeit

Ein Fenster für
eine Flotte von Claude- Sitzungen.

Eine Desktop-App, die viele Claude-Code-Sitzungen gleichzeitig steuert. Sieh jede Sitzung in einer Seitenleiste, tippe in eine beliebige, beobachte den Stream und genehmige ihre Tool-Aufrufe direkt. Gebaut auf dem Agent SDK, in drei isolierten Prozess-Schichten. Ein Experiment, das ich tief vorangetrieben, dann aber zur Seite gelegt habe: zu wartungsintensiv, und ich entwickle es heute nicht mehr aktiv.

N
Sitzungen zugleich
1
Fenster
3
Prozess-Schichten
0
Terminals
Zum Lesen scrollen
00Das Problem

Zehn Terminals, kein Cockpit.

Mehrere Claude-Code-Sitzungen gleichzeitig zu fahren bedeutet, zehn Terminal-Fenster zu jonglieren, eines pro Projekt oder Aufgabe. Es gibt keine einzige Oberfläche, um zu sehen, was jede Sitzung tut, in eine bestimmte zu tippen oder ihre Tool-Aufrufe zu genehmigen, ohne zwischen ihnen hin- und herzuwechseln.

Ich wollte ein Cockpit: ein Fenster, das jede Sitzung steuert, den Live-Zustand jeder einzelnen zeigt und es mir erlaubt, einen Tool-Aufruf in genau der Sitzung zu genehmigen, die ihn angefragt hat, ohne die anderen zu verlieren.

01Die Form

Ein Fenster, drei isolierte Schichten.

Basalt ist ein Desktop-Fenster, das N parallele Claude-Code-Sitzungen steuert, aufgeteilt in drei Prozesse, die je eine Aufgabe erledigen. Eine Tauri-Hülle in Rust besitzt das native Fenster und überwacht den Rest. Ein React-Frontend rendert die Flotte. Ein Node-Sidecar hält die Engine.

Die Rust-Hülle trägt keine Geschäftslogik. Sie öffnet das Fenster, startet das Sidecar mit einem zufälligen Port und Auth-Token, startet es bei einem Absturz neu und beendet es beim Schließen. Das Frontend spricht mit dem Sidecar über einen nur-lokalen WebSocket, abgesichert durch dieses Token, sodass kein anderer Prozess auf der Maschine sich verbinden kann. Tauri statt Electron hält die ganze App bei etwa 10 bis 15 MB statt über 100.

Tech Stack
Tauri 2 (Rust)React + ViteNode sidecarlocalhost WebSocket
Wie es funktioniert
Tauri übergibt dem Frontend beim Start Port und Token; das Frontend öffnet einen 127.0.0.1-WebSocket mit dem Token. Lebenszyklus in Rust, Hirn im Sidecar, Pixel im Frontend.
Warum es zählt
Drei Prozesse mit sauberen Nähten. Das Sidecar kann sterben und neu starten, ohne das Fenster mitzureißen, und das Frontend synchronisiert sich von ihm als Quelle der Wahrheit.
In der Praxis
Etwa 10 bis 15 MB, gegenüber über 100 für ein Electron-Äquivalent.
02Die Engine

Auf dem Agent SDK gebaut, nicht auf einem Terminal.

Jede Sitzung ist ein echtes Claude-Agent-SDK-query(), kein Wrapper um die Terminal-Oberfläche. Ich wählte das SDK statt PTY-Scraping der TUI (fragil, keine strukturierten Ergebnisse) und statt headless claude -p (kein Live-Callback für Tool-Genehmigung).

Das SDK liefert, was ein Controller wirklich braucht: einen canUseTool-Callback, um jeden Tool-Aufruf abzufangen, Teil-Nachrichten-Streaming für Live-Ausgabe, Interrupt pro Sitzung, Modell- und Permission-Mode-Steuerung und viele parallele Sitzungen in einem Prozess. Ein SessionManager besitzt das Register; jede Session umhüllt ein query() und hält ihr eigenes Transkript. Eine fehlerhafte Sitzung wird als errored markiert und berührt ihre Geschwister nie.

Tech Stack
Claude Agent SDKcanUseToolincludePartialMessagesconcurrent query()
Wie es funktioniert
Sitzungen nutzen deinen bestehenden Claude-Code-Login (das SDK teilt sich die Auth der CLI), es gibt also keine neuen Anmeldedaten zu handhaben.
Warum es zählt
Strukturierte Events schlagen abgekratzten Text. Basalt rendert, was das Modell tatsächlich getan hat, nicht die Zeichen, die es ausgab.
In der Praxis
interrupt(), setModel(), setPermissionMode() pro Sitzung, alles in einem Node-Prozess.
03Die Flotte steuern

Ein Transkript, kein Terminal.

Weil das SDK strukturierte Events ausgibt, ist die Oberfläche ein sauberes Chat-artiges Transkript statt eines Terminal-Emulators. Text-Deltas verschmelzen zu Blöcken; jeder Tool-Aufruf ist eine Karte; eine Karte, die auf Erlaubnis wartet, wird zur Genehmigungskarte mit Genehmigen, Ablehnen oder Eingabe-bearbeiten.

Eine linke Leiste listet jede Sitzung als Live-Statuszeile: ein farbiger Punkt, das Label und eine einzeilige Aktivität wie bearbeitet oder braucht Genehmigung. Der Hauptbereich ist Transkript und Eingabezeile der gewählten Sitzung. Das Ganze trägt eine Ember-Identität, eine fast schwarze Basalt-Palette mit einem Lava-Amber-Akzent, wobei Amber immer bedeutet, dass ein Tool auf dich wartet.

Tech Stack
ReactZustandStructured event streamNo xterm.js
Wie es funktioniert
Ein einziger Zustand-Store ist das Einzige, was den Socket berührt; WebSocket-Events reduzieren in ihn hinein, und schlanke Aktionen senden Befehle zurück.
Warum es zählt
Du steuerst jede Sitzung, ohne die anderen zu verlieren. Genehmige den Aufruf in der Sitzung, die ihn angefragt hat, während der Rest weiterstreamt.
In der Praxis
Kein xterm.js. Strukturierte Events rein, ein Chat-Log raus.
04Koordination

Viele Sitzungen, ein Repo, keine Kollisionen.

Das schwierigere Problem ist, mehrere fokussierte Sitzungen gleichzeitig im selben Repository laufen zu lassen, ohne dass sie sich gegenseitig überschreiben. Basalt behält einen Arbeitsbaum und verhindert Kollisionen, indem es Dateien beim Bearbeiten sperrt.

Das Sperr-Gate ist ein PreToolUse-Hook, bewusst nicht der canUseTool-Callback. canUseTool wird in den permissiven Permission-Modes übersprungen, die man für Flottenarbeit tatsächlich nutzt, während ein PreToolUse-Hook in jedem Modus feuert und blockieren kann. Versucht eine Sitzung, eine Datei zu schreiben, die eine andere hält, verweigert Basalt den Aufruf mit einer Begründung, und der blockierte Claude leitet sich selbst um. Null Vertrauen darauf, dass die Modelle kooperieren. Ein Live-Board, ein .basalt/board.md mit einem einzigen Autor, zeigt, wer was anfasst.

Tech Stack
PreToolUse hookLockRegistryPer-file locks.basalt/board.md
Wie es funktioniert
Ein LockRegistry bildet jeden Pfad auf seine besitzende Sitzung ab; toolTargets extrahiert den geschriebenen Pfad aus Edit, Write, MultiEdit und NotebookEdit. Konflikt heißt Ablehnen mit Begründung; frei heißt Beanspruchen und Erlauben.
Warum es zählt
Sperren schlagen hier die git-worktree-Isolation: ein Baum, kein Branch-Jonglieren, und Kollisionen sind unmöglich statt später zusammengeführt.
In der Praxis
Basalt ist der einzige Autor der Board-Datei, also gibt es kein Rennen um die Koordinationsdatei selbst.
05Für den täglichen Betrieb gebaut

Gebaut, und täglich im Einsatz.

Alles ist ausgeliefert: die Sidecar-Engine, das Frontend, die Tauri-Hülle, die Datei-Sperr-Koordination und das verzögerte Wiederaufnehmen von Sitzungen, das das Schließen der App überlebt. Es installiert sich als echte Basalt.app und baut sich mit einem einzigen studio basalt update neu.

Die scharfe Kante war das Packaging. Eine doppelt geklickte .app bekommt einen minimalen PATH, der node des Nutzers ist also nicht darauf; Basalt löst das echte Binary beim Start über die Login-Shell auf und startet es direkt. Ein Fake-Engine-Modus tauscht das SDK gegen einen geskripteten Generator, sodass die gesamte Oberfläche gebaut, vorgeführt und gescreenshottet werden kann, ohne ein einziges Token auszugeben. Vorerst ist es tägliches Studio-Werkzeug und beweist, dass es hält, bevor es offen veröffentlicht wird.

Tech Stack
pnpm tauri buildSidecar as Tauri resourceLogin-shell PATHFake-engine mode
Wie es funktioniert
pnpm tauri build bündelt das Sidecar als Tauri-Ressource (ein echtes, deployedes node_modules); Rust löst es im gebündelten Zustand aus dem Ressourcen-Verzeichnis auf.
Warum es zählt
Es verdient seinen Platz, indem es das Werkzeug ist, mit dem ich das Studio tatsächlich jeden Tag fahre.
In der Praxis
Verzögertes Wiederaufnehmen: frühere Sitzungen erscheinen als gedimmte Zeilen und verbinden sich erst, wenn du sie anklickst.
Das Große Ganze

Die dritte
Säule.

Drei Experimente, ein Studio. Der Harness betreibt es, Lattice erinnert sich daran, und Basalt erkundete, eine ganze Flotte von Agenten gleichzeitig zu steuern: tief gebaut, dann als zu wartungsintensiv zur Seite gelegt. Jedes existiert, weil das Werkzeug, das ich brauchte, es noch nicht gab.