Bereitstellung und Clients
Control Center ist kein einzelnes Programm. Es ist eine Thin-Client-Architektur: ein Headless-Server, der deine Daten besitzt, und Clients, die darüber eine Oberfläche zeichnen, angebunden über eine einzige RPC-Verbindung. Diese Trennung erklärt, warum etwas dort läuft, wo es läuft, und was jeder Client erreichen kann.
Der Server besitzt die Daten
Abschnitt betitelt „Der Server besitzt die Daten“Ein Prozess namens cc_server hält die Datenbanken: eine kleine global.db
für den serverweiten Zustand und pro
Arbeitsbereich eine SQLite-Datei,
die erst bei Bedarf geöffnet wird. Dort läuft die langlebige Arbeit:
Pipeline-Engines, Reconciler, die MCP-Tool-Oberfläche und der
Orchestrierungs-Listener. Alles, was ein Fenster überdauern muss oder was zwei
Clients identisch sehen sollen, liegt dort.
Kein Client öffnet selbst eine Datenbankdatei. Jeder Client hält einen
RemoteRpcClient und schickt darüber Repo-Operationen und Abonnements. Der
Server authentifiziert die Verbindung mit dem Kopplungsschlüssel des gekoppelten
Geräts, und danach spielt die Verbindung selbst keine Rolle mehr. Eine
Sitzung ist an keinen Arbeitsbereich gebunden. Der Dispatcher ist
zustandslos: jeder arbeitsbereichsbezogene Aufruf trägt seine eigene
workspace_id in den Argumenten, und ein Aufruf ohne sie wird abgelehnt.
Das ist so gewollt. Zwei Clients an einem Server können so in verschiedenen Arbeitsbereichen sitzen, ohne dass der Server einen „aktuellen Arbeitsbereich“ vorhält, über den sie sich uneins werden könnten. Isolation wird stattdessen bei jedem Aufruf erzwungen, in vier Schritten:
- Die Operation gibt an, ob sie arbeitsbereichsbezogen ist. Ist sie das und
kommt keine
workspace_idan, wird der Aufruf abgelehnt. - Die genannte ID muss ein registrierter Arbeitsbereich sein. Geprüft wird
das, bevor irgendetwas eine Datenbank öffnet. Sonst würde eine unbekannte ID
bei jeder Anfrage eine leere Geister-
workspace.dbanlegen. Ein Fehltreffer ist Nicht-gefunden. - Die
WorkspaceRoledes Aufrufers muss die Mindestrolle der Operation erfüllen, abgeleitet von ihrer Art: Lesen braucht Gast, Ändern braucht Mitglied, Zerstören braucht Admin. - Eine Operation, die Repo-Inhalte offenlegt, verlangt zusätzlich eine
Berechtigung pro Repo auf der Stufe, die sie nennt —
none/read/review/write—, damit die Mitgliedschaft im Arbeitsbereich dem Forge nie stillschweigend mehr Rechte einräumt.
Der Satz, auf den es ankommt: Ein Kopplungsschlüssel ist nicht die Zugriffsgrenze; die Mitgliedschaft ist es.
Warum die Trennung so aussieht
Abschnitt betitelt „Warum die Trennung so aussieht“Drei Zwänge haben zu einem Server geführt:
- Eine Quelle der Wahrheit. Desktop und Smartphone, oder Laptop und Desktop, sollen denselben Zustand der Fleet sehen. Besäße jedes Gerät eine eigene Datenbank, liefen die Daten auseinander. Ein einzelner Server heißt: jeder Client liest dieselben Live-Daten.
- Das Web kann den Betrieb nicht selbst übernehmen. Ein Browser kann keinen Subprozess starten und keine lokale Datei öffnen. Ein Web-Build kann deshalb die Datenbank nie besitzen und nie einen Agenten ausführen. Es muss mit einem Server sprechen, der beides kann. Statt eines zweiten Datenpfads nur fürs Web wurde der Desktop auf denselben Pfad gezogen.
- Headless-Betrieb. Ein Server, den du auf einem kleinen Rechner laufen lassen kannst und den ein Smartphone im Zug oder ein Browser von überall erreicht, ist nützlicher als eine Oberfläche, die offen bleiben muss.
Der Preis: der Server ist jetzt eine Abhängigkeit. Läuft er nicht, funktioniert nichts. Die Standardantwort des Desktops ist, selbst einen zu starten.
Die Clients
Abschnitt betitelt „Die Clients“Fünf Clients erreichen den Server, jeder mit anderem Zuschnitt und anderem Vertrauensprofil.
Desktop-App
Abschnitt betitelt „Desktop-App“Die Flutter-Desktop-Anwendung (macOS, Windows, Linux) ist der vollständige Client. Beim ersten Start fragt sie nach der Betriebsart und merkt sich die Wahl:
- Lokal: startet und beaufsichtigt einen
cc_serverauf diesem Rechner. Das ist die Vorgabe, in sich geschlossen, für eine einzelne Person. Der Desktop startet den Server. Der Server besitzt die Datenbank unter dem App-Support-Verzeichnis, und der Desktop spricht über Loopback mit ihm. - Entfernt: verbindet sich über ein gesichertes WebSocket mit einem
cc_server, der woanders läuft. Die Daten liegen auf diesem Server; dieser Desktop ist nur noch Darstellung.
In beiden Fällen ersetzt der entstehende RPC-Client den Daten-Provider der App. Die gesamte Oberfläche, jeder Bildschirm und jede Funktion, liest und schreibt über den Server. Die Datenbankdatei fasst der Desktop nie direkt an.
Web-Client
Abschnitt betitelt „Web-Client“Der Web-Build ist dieselbe Flutter-App, für den Browser übersetzt, gehostet
unter app.usectrl.dev, damit nichts zu installieren
ist. Ein Browser kann keinen Subprozess starten. Der Web-Client ist deshalb
immer entfernt: er baut eine Verbindung zu einem cc_server über wss://
auf und zeichnet die vollständige Desktop-Oberfläche. Einmal verbunden zeigt
er dieselben Bildschirme derselben Fleet, weil es derselbe Code über dasselbe
RPC ist. Die Lücken sind die wenigen Stellen, die eine native Fähigkeit
brauchen, die ein Browser nicht hat. Die Qualitätsschicht der
Besprechungsaufnahme weiter unten ist die wichtigste, Benachrichtigungen
des Betriebssystems eine weitere.
Deshalb sind „kein Download“ und „kein Server“ zwei verschiedene Behauptungen.
Der Web-Client nimmt die Installation weg. Der Server bleibt. Anwählen kann
der Web-Client einen cc_server auf demselben Laptop —
ws://localhost:9030, der kein Zertifikat braucht, weil Browser Loopback als
vertrauenswürdig behandeln — oder einen auf einem anderen Rechner, der über
wss:// mit einem Zertifikat erreichbar sein muss, dem der Browser vertraut.
cc_server weigert sich, eine Adresse außerhalb von Loopback ohne TLS zu
binden. Ein entfernter Server braucht deshalb ein eigenes Zertifikat, einen
TLS-terminierenden Proxy oder einen Tunnel oder ein VPN, das ein Zertifikat
mitliefert.
Das Bundle selbst liegt getrennt von dem Server, mit dem es spricht. Ein
eigenständiger cc_server bedient den RPC-Endpunkt, die MCP-Oberfläche und
seine Proxys, ein Web-Bundle aber nicht. Der Pfad für statische Dateien
ist im Server vorhanden, am eigenständigen Binary ist aber kein Web-Root
konfiguriert, deshalb hat er unter / nichts auszuliefern. Das veröffentlichte
Container-Image cc-webapp hostet das Bundle, und scripts/build_web.sh baut
es. Ein Server akzeptiert Browser-Origins nur auf seiner Liste
--allowed-origins, die standardmäßig https://app.usectrl.dev enthält. Wer
das Bundle selbst hostet, trägt dessen Origin dort ein.
Medien (Avatare, Feed-Bilder, PR-Screenshots) laufen über den Proxy-Endpunkt des Servers, statt die vorgelagerten Hosts direkt anzufragen. Ein Browser ruft so keine beliebige Origin ab. Der ausgelieferte Client setzt bei jeder Anfrage eine Content-Security-Policy anhand eines nicht sensiblen Cookies, sodass nur die Origin des verbundenen Servers erlaubt ist.
Smartphone-Begleiter (cc_remote)
Abschnitt betitelt „Smartphone-Begleiter (cc_remote)“Die Remote-App ist ein eigener, schlankerer Client, eine Flutter-Web-PWA unter remote.usectrl.dev. Sie deckt bewusst nur einen Ausschnitt ab. Sie koppelt sich über ein vermitteltes WebSocket-Relay, dessen Frames Ende zu Ende versiegelt sind, und spricht einen überwiegend lesenden Ausschnitt der Tool-Oberfläche: Tickets, Nachrichten und den Newsfeed lesen, eine Antwort senden oder ein Ticket aktualisieren.
Ein Smartphone ist ein Prinzipal mit weniger Rechten als ein lokaler Agent. Eine Tool-Richtlinie mit Default-Deny, ein Kopplungsschlüssel, der jeden Frame versiegelt, eine Session-Capability, die privilegierte Operationen (die Kopplung zum Beispiel) aus einem Begleit-Client aussperrt, die Rollen- und Repo-Prüfungen pro Aufruf von oben und eine Begrenzung der Aufrufrate halten eine freigegebene oder durchgesickerte Kopplung davon ab, zur vollen Fernsteuerung zu werden. Das Sicherheitsmodell steht unter Fernsteuerung und Mobil.
MCP-Clients
Abschnitt betitelt „MCP-Clients“Externe Werkzeuge, also Editoren, andere Agenten und CLIs, die das Model Context Protocol sprechen, verbinden sich mit der MCP-Oberfläche des Servers und nutzen die typisierte Tool-Registry. Das ist der Weg für die programmatische Integration, ohne grafische Oberfläche. Siehe Den MCP-Server nutzen und die MCP-Tools.
Fleet-Worker (cc_worker)
Abschnitt betitelt „Fleet-Worker (cc_worker)“Ein cc_worker ist ein Headless-Binary in reinem Dart. Es koppelt sich mit
einem cc_server, meldet die Fähigkeiten seines Hosts und holt befristet
zugewiesene Jobs zur Ausführung. Es streamt Prozessereignisse über dasselbe
RPC zurück und hält keinen dauerhaften Zustand: keine Datenbank, keine
Authentifizierung, keine Freigaben, keine Budgets. Das alles verlässt
cc_server nie. Ein Worker ist ein Arm, kein zweites Gehirn. Ein maßgeblicher
Server, N dumme Arme. Konsens gibt es nicht, und Worker sprechen nicht
miteinander.
Die Lease-Ausführung ist bisher nur Verrohrung. agentRun ist die einzige
Art, die einen echten Befehl ausführt (CC_JOB_COMMAND, oder einen
zurückgespiegelten Prompt, wenn keiner mitgegeben wurde). pipelineStep,
codeIndex, goldenRender, benchmark und evalBatch sondieren derzeit mit
git --version / rev-parse HEAD, damit der Streaming-Pfad durchlaufen wird,
ohne so zu tun, als wären diese Jobs gelaufen.
Gemeldet werden Betriebssystem, CPU-Architektur, Kernzahl, RAM, ob ein
Flutter-SDK erreichbar ist und welche Sandbox-Backends der Host hat (unter
Windows keine). Ein dedizierter Worker meldet zusätzlich immer alwaysOn und
acceptsParallel. Genau daran platziert der Scheduler in den Stufen pin →
prefer → spill. Nach GPU oder ML wird nicht gesucht.
Ein Desktop allein merkt davon nichts, weil gewöhnliches Dispatch gar nicht
über die Fleet läuft. Für den Host des Servers ist ein impliziter Worker im
Prozess registriert. Das ist nur eine Registrierungsnaht, an der keine Runner
hängen. Ein Job, der tatsächlich auf dem lokalen Worker landet, schlägt fehl,
statt zu laufen. Befristet zugewiesene Jobs führt derzeit nur ein echter
cc_worker aus.
Was wo läuft
Abschnitt betitelt „Was wo läuft“| Thema | Server (cc_server) |
Fleet-Worker (cc_worker) |
Desktop-Client | Web- / Smartphone-Client |
|---|---|---|---|---|
| Datenbanken (Drift/SQLite) | besitzt sie | — | — | — |
| Authentifizierung, Freigaben, Budgets, Identität | besitzt sie | — | — | — |
| Pipelines, Reconciler, Orchestrierungs-Listener | führt sie aus | — | — | — |
| MCP-Tool-Oberfläche | stellt sie bereit | — | — | — |
| Agentenausführung | führt sie aus (oder vergibt sie per Lease) | führt zugewiesene Jobs aus | — | — |
| Oberflächendarstellung | — | — | vollständig | vollständig (Web) / überwiegend lesend (Smartphone) |
| Besprechungsaufnahme (Mikrofon + Besprechungston) | — | — | nativ | nur Web, über den Ton der Bildschirmfreigabe |
| Kalender-OAuth (Google) | speichert Tokens | — | — | — |
Die beiden Ebenen setzen die Sandbox nicht gleich ein. cc_server hüllt die
Läufe, die er selbst ausführt, in die OS-Sandbox des Hosts, wo ein Backend
vorhanden ist: Seatbelt unter macOS, bubblewrap unter Linux und WSL2. Unter
Windows oder ohne bwrap und socat greifen die übrigen Grenzen.
cc_worker setzt überhaupt keine Sandbox. Er meldet eine Fähigkeit
sandbox, damit der Server Jobs nach Plattform platzieren kann. Im Worker
wendet nichts eine Sandbox an: ein befristet zugewiesener Job wird direkt
gestartet, begrenzt durch die Umgebung des Lease und den Worktree, den er
bekommen hat. Arbeit per Lease nach außen zu geben verlagert die Ausführung
damit aus der Sandbox, auch auf einem Host, der eine hat. Was einen Lauf im
jeweiligen Fall begrenzt, steht unter
Sandbox und Sicherheit.
Die Besprechungsaufnahme läuft auf einem Client, weil sie ein Mikrofon und den Ton der Besprechung braucht. Ein Server im Schrank kann dein Gespräch nicht aufnehmen. Danach fasst die Pipeline des Servers die Aufnahme zusammen, und die Notizen liegen dort, wo jeder Client sie lesen kann.
Auch der Web-Client nimmt auf. Die Form der Tabelle kann das verdecken: er erfasst im Browser das Mikrofon und eine Audio-Spur der Bildschirmfreigabe und streamt 16-kHz-PCM an den Server. Diese Spur braucht er. Wo der Browser sie nicht liefert, scheitert er ausdrücklich. Wirklich nur auf dem Desktop liegt die Qualitätsschicht um die Aufnahme: Echounterdrückung auf Signalebene über den nativen Prozessor, die Eingangspegelanzeige, die Warnung bei stummem Mikrofon, eine Zusammenfassung erneut laufen lassen, die Verarbeitung abbrechen und die Kalenderaktion zum Aufnehmen und Verknüpfen.
Kopplung und Vertrauen
Abschnitt betitelt „Kopplung und Vertrauen“Jeder Client weist sich mit einem Kopplungsschlüssel aus, der einmal eingerichtet wird:
- Der Desktop im lokalen Modus erzeugt bei jedem Start einen frischen Schlüssel und übergibt ihn dem gestarteten Server über die Umgebung. Auf dem Desktop bleibt nichts Geheimes gespeichert.
- Entfernte Clients (entfernter Desktop, Web, Smartphone) verwenden einen
Schlüssel, den der Befehl
pairdes Servers ausstellt. Er liegt im Schlüsselbund des Betriebssystems, auf dem Smartphone lokal.
Der Widerruf ist sofort, nicht aufgeschoben. Der Server beobachtet die Tabelle der gekoppelten Geräte und trennt jede offene Sitzung, deren Gerät die aktive Menge verlassen hat, innerhalb von Sekunden, auf dem direkten WebSocket-Pfad und auf dem Relay-Pfad gleichermaßen. Du musst nicht warten, bis sich das Gerät wieder verbindet, um es abzutrennen.
Der Schlüssel läuft nie über eine URL, die ein statischer Host sehen kann.
Kommt er über einen Link, auf dem Smartphone oder im Browser, reist er im
URL-Fragment, dem Teil nach #, den der Browser nie an den Server
schickt, der die Seite ausliefert. Diese Invariante gilt für beide, und sie
ist die, die sich zu merken lohnt. Der Web-Client hat zwei weitere Einstiege.
Im Verbindungsformular fügst du Server und Schlüssel direkt ein. Ein
Einladungscode wird gegen eine Zugangsberechtigung eingelöst, die nur für
diesen Browser ausgestellt wurde.
Verwandte Konzepte
Abschnitt betitelt „Verwandte Konzepte“- Arbeitsbereiche und Isolation: warum jeder RPC-Aufruf seine eigene Arbeitsbereichs-ID mitbringt und wie die Aufteilung der Datenbanken Isolation zur Struktur macht
- Architektur: wie die Codebasis um den Server und seine Clients gebaut ist
- Fernsteuerung und Mobil: Kopplung des Smartphones und das Sicherheitsmodell
Verwandte Anleitungen
Abschnitt betitelt „Verwandte Anleitungen“- Einen Headless-Server betreiben:
cc_serverstarten und einen Client koppeln - Mit einem Remote-Server verbinden: den Desktop- oder Web-Client auf einen Server woanders ausrichten
- Ein Gerät koppeln: ein Smartphone, einen zweiten Browser oder einen weiteren Desktop hinzufügen
- Einen Fleet-Worker betreiben: einen
cc_workerhinzufügen, der befristet zugewiesene Jobs abholt