DisPatch: Eine selbstgehostete Chat-App für lokale KI-Agenten
- Kategorie
- KI & lokale LLMs
- Veröffentlicht
- 11 Juli 2026
- Aktualisiert
- 11 Juli 2026
- Von
- Jacob Lloyd — mit KI-Unterstützung geschrieben, im Nachhinein
- Lesezeit
- 11 Min. Lesezeit
Kurz gesagt: DisPatch ist drei Dinge in einem, komplett auf dem eigenen Rechner gehostet: eine Chat-App für den Austausch mit KI-Assistenten, die ebenfalls dort laufen, ein Dateiserver und ein Terminal — auf allen Geräten jederzeit verfügbar, ohne dass etwas an einen Cloud-Dienst geht. Der Dateiserver bringt Dateien von jedem Gerät auf den Rechner, auf dem die Assistenten laufen, damit sie mit dem, was hochgeladen wird, arbeiten können. Auf gemeinsam genutzten Geräten steht ohne PIN eine sichere Teilmenge der Assistenten zur Verfügung; die Eingabe der PIN schaltet die vollausgestatteten frei.
DisPatch ist die App, die ich gebaut habe, damit ich keine 30-Container-Rocket.Chat-Installation mehr betreiben muss, nur um mit meinen eigenen KI-Agenten zu schreiben. Am Ende wurden daraus drei Dinge in einem — eine lokal gehostete LLM-Chat-App, ein Dateiserver und ein Terminal — verfügbar auf jedem Gerät, das ich besitze, jederzeit. Es ist ein ~720-KB-Zip, es ist kostenlos, und es ist das einzige Chat-Frontend, das ich noch benutze.
Kurzfassung
- Was es ist: Drei Dinge in einem, selbstgehostet: ein Web-Chat für eine Riege lokaler KI-Agenten (ein OpenClaw-Gateway), ein Drag-and-Drop-Dateiserver und ein echtes Terminal — auf jedem Gerät in deinem LAN oder Tailscale-Netzwerk, jederzeit.
- Was es kostet: 0 €. Kein SaaS-Abo, kein Konto.
- Was du brauchst: Linux, Python 3.12+ und
uv. Ein OpenClaw-Gateway, falls du getippte Antworten willst — alles andere funktioniert auch ohne. - Was dabei herauskommt: Threads pro Bot, Streaming-Markdown, ein Drag-and-Drop-Dateiserver, ein Coding-Agent-Terminal direkt in der App, ein PIN-sperrbarer "Safe Mode", eine Push-API für Cron-Jobs und 187 Tests, die direkt aus dem Zip heraus durchlaufen.
Was dabei herauskommt
Bevor es um die Einrichtung geht: So sieht es bei der Arbeit aus. Die Screenshots stammen aus einer isolierten Kopie mit Testdaten — niemand muss meine echten Bot-Logs sehen.



Was es tatsächlich kann
- Threads pro Bot, überall. Öffne es mitten in einer Unterhaltung auf dem Smartphone, und es ist derselbe Thread, dieselbe Historie, kein erneutes Anmelden nötig.
- Ein Dateiserver — Dateien per Drag-and-Drop zwischen jedem Gerät und der Box austauschen. Das Feature, das ich am meisten nutze; es bekommt weiter unten einen eigenen Abschnitt.
- Ein Terminal (Terminex) — eine echte Shell im Browser, in der ein Coding-Agent läuft. Ebenfalls ein eigener Abschnitt weiter unten.
- Streaming-Markdown-Antworten mit Syntax-Highlighting im Code (inklusive Kopieren-Button), Bilder/Videos mit Zoom-Lightbox, installierbar als PWA.
- Ein Live-"Arbeitet gerade"-Panel. Klick auf die Tippen-Punkte, um dem Modell live bei Tool-Aufrufen und beim Denken zuzusehen — es liest live das Transkript des Agenten mit, kein vorgetäuschter Spinner.
- Zweistufige Sperre. Eine PIN trennt die vollausgestatteten Agenten von einer sicheren Teilmenge mit allgemeinen Infos — Details weiter unten im Abschnitt zur zweistufigen Sperre.
- Eine Push-API —
POST /api/inject— damit ein Cron-Job oder ein anderer Agent eine Nachricht (oder ein Bild) in einen Thread fallen lassen kann. So landet ein tägliches Briefing auf deinem Smartphone, ohne dass du danach fragst. - Lokale Bilderzeugung im Thread. Bittet man einen Agenten um ein Bild, kann er eine lokale ComfyUI-Instanz aufrufen; das Ergebnis landet im Chat wie jedes andere Medium, inklusive Lightbox. Keine Bild-API, kein Upload an irgendjemanden.
- Optional, standardmäßig aus: ein ComfyUI-Panel, um diesen Bildgenerierungsdienst aus dem Chat heraus zu starten oder zu stoppen.
Der Dateiserver
Von allem in DisPatch ist der Dateiserver der Teil, den ich am meisten nutze. Es ist ein Drag-and-Drop-Dateibereich, der von derselben Box ausgeliefert wird: DisPatch auf einem beliebigen Gerät öffnen, eine Datei darauf ablegen, und die Datei liegt auf dem Server — gruppiert nach Tag, mit Miniaturansichten für Bilder und Videos.

Was ihn zu mehr als einer Annehmlichkeit macht, ist, wo die Dateien landen: auf derselben Maschine, auf der die Agenten leben. Ein vom Smartphone hochgeladenes Foto kann Sekunden später von jedem Agenten von der Festplatte gelesen werden — beschrieben, einsortiert, auf eine Website gestellt, in die Bildwerkzeuge gefüttert. Es ist die Brücke zwischen "eine Datei auf dem Gerät, das ich gerade in der Hand halte" und "eine Datei, mit der meine Agenten arbeiten können", ganz ohne Cloud-Laufwerk dazwischen und ohne Kabel.
Die unglamourösen Teile sind erledigt: Uploads werden vor dem Puffern auf ihre Größe geprüft (ein Smartphone kann die Box nicht mit einem OOM lahmlegen), es gibt eine Gesamtspeichergrenze, Schreibvorgänge sind atomar, sodass ein unterbrochener Upload nie eine halb geschriebene Datei hinterlässt, und der Dateispeicher wird in dasselbe rotierende Backup-Schema wie der Chatverlauf gespiegelt. Ein Konfigurations-Flag kann das Ganze außerdem in eine einseitige, nur-Upload-Ablagebox verwandeln — Geräte können Dateien hinzufügen, aber nicht durchstöbern — für Aufbauten, bei denen mehr Leute die App erreichen können, als ihr Archiv lesen dürfen sollten.
Terminex: ein Terminal im Chat
Das andere Feature, das sich täglich auszahlt: DisPatch kann ein echtes Terminal in den Browser stellen. Es erscheint in der Bot-Leiste wie jeder andere Bot, aber beim Öffnen wird man an ein serverseitiges PTY angebunden, das über einen WebSocket in xterm.js gestreamt wird — eine echte Shell-Sitzung auf der Box, keine Attrappe.

Meins startet direkt in Reasonix, den Claude-Code-artigen Coding-Agenten, den ich auf DeepSeek betreibe — das Setup, das die Assistenten-Übersichtsseite Terminex nennt. Das verwandelt einen Chat-Tab in eine vollständige Coding-Agent-Konsole: gib ihm eine Aufgabe, sieh ihm bei der Arbeit zu, von jedem Gerät aus, das die App öffnen kann. Zusammen mit dem Dateiserver macht das die Box vom Smartphone aus vollständig steuerbar — eine Datei von unterwegs ablegen, das Terminal öffnen, dem Agenten sagen, was damit zu tun ist.
- Nur im entsperrten Modus. Das Terminal existiert im Safe Mode nicht, und es bleibt komplett aus, solange man es nicht aktiviert (
LOCAL_CHAT_TERMINAL). - Die Sitzung liegt im Arbeitsspeicher. Start / Neustart / Stopp aus der App heraus; ein Server-Neustart beendet sie. Es gibt keine dauerhafte Shell, die sich im Hintergrund versteckt.
Warum es das gibt
Es hat eine komplette Rocket.Chat-Installation abgelöst — eine Datenbank und ein Haufen Container, nur damit eine Person mit ein paar lokalen Modellen sprechen konnte. Es funktionierte, aber das ist eine Menge Infrastruktur für "Text senden, Text zurückbekommen, überall synchronisieren".
Und die tiefere Antwort auf "warum überhaupt eine Chat-App selbst hosten": Sobald die eigenen Modelle lokal laufen, ist die Chat-Oberfläche das letzte Stück, das noch in Richtung Cloud-Dienst lockt. Sie selbst zu hosten bedeutet, dass die eigenen Prompts, die Ausgaben der Agenten und die Nachrichten der Familie nie das Haus verlassen, und der ganze Kreislauf — UI, Gateway, Modelle, Bilderzeugung — funktioniert auch, wenn man das Internet abstöpselt. Wer sich bereits für lokale LLMs entschieden hat, für den ist ein gehostetes Chat-Frontend der letzte Ausreißer.
Eine Designentscheidung, die es wert ist, erwähnt zu werden: DisPatch hat absichtlich keinen Build-Schritt. Das Frontend ist Vanilla JS (ES-Module) und einfaches CSS/HTML. Kein Bundler, kein npm run build. Meine eigentliche Anforderung von Anfang an war "leicht für einen KI-Agenten, später Änderungen daran vorzunehmen". Eine JS-Datei bearbeiten, neu laden, fertig. Ich würde dieselbe Entscheidung wieder treffen.
Der Stack
| Schicht | Was es ist |
|---|---|
| Backend | Python 3.12+, FastAPI + uvicorn, aiosqlite (eine SQLite-Datei, WAL-Modus), Pillow, verwaltet mit uv |
| Backend-Umfang | ~7.200 Zeilen in backend/app/ (allein main.py ~3.600), plus ~2.700 Zeilen Tests |
| Frontend | Vanilla JS + HTML/CSS ohne Build-Schritt — ~4.900 Zeilen JS, ~2.100 CSS/HTML/Service-Worker |
| Mitgelieferte Bibliotheken | marked, highlight.js, DOMPurify, xterm.js — keine CDN-Aufrufe |
| Daten | Außerhalb des Codes unter ~/.local/share/local-chat/: DB, config.yaml, security.yaml, media/, files/, rotierende DB-Backups |
Die Einrichtung
Zusammengefasst aus README-SETUP.md im Zip (Download-Box weiter unten).
unzip dispatch-2026-07.zip
cd dispatch
./install.sh
./run.sh
Das startet die App unter http://0.0.0.0:8765 und gibt die LAN- und Tailscale-URLs aus, sodass man eine davon vom Smartphone aus antippen kann. Von dort aus:
~/.local/share/local-chat/config.yamlbearbeiten (wird automatisch erstellt, dokumentiert inconfig.yaml.example), damit die Bot-ids zu deinen OpenClaw-Agenten-IDs passen. Wird per Hot-Reload übernommen.- Optional:
./install.sh --systemderzeugt eine Benutzer-systemd-Unit. - Optional: über das Zahnrad-Symbol → Security eine PIN setzen.
security.yaml.exampledokumentiert die Datei, inklusiveapi_tokenfür alle, die/api/injectvon außerhalb der Box aufrufen. - Umgebungsvariablen:
LOCAL_CHAT_HOST/PORT/DATA_DIR/AGENT_TIMEOUT/MAX_CONCURRENCY, dazuLOCAL_CHAT_COMFY=0/LOCAL_CHAT_TERMINAL=0, um die optionalen Panels auszulassen.
Die openclaw-CLI selbst ist nicht enthalten — DisPatch erwartet, dass irgendwo erreichbar bereits ein OpenClaw-Gateway läuft. Noch kein Gateway vorhanden? Die App startet trotzdem, warnt in den Logs, und alles außer den getippten Antworten funktioniert: Threads, die Inject-API, der Dateiserver, PIN und Safe Mode. So habe ich dieses Zip auch getestet — frisch entpackt, uv sync, freier Port, kein Gateway — und die mitgelieferte Testsuite lief mit 187 passed in 8.4s durch.
Was tatsächlich passiert, wenn eine Nachricht getippt wird
Du tippst etwas und drückst Senden. Das ist der Weg, den das nimmt:
Das ist der glückliche Pfad. Die eigentliche Ingenieursarbeit steckt in den unglücklichen: Die CLI läuft in ein Timeout, stürzt ab, oder ein Subagent antwortet zehn Minuten zu spät. Vier unabhängige Quellen — der Live-Watcher, die JSON-Nutzlast der CLI, ein Abgleicher nach dem Zug, der das Transkript erneut durchsucht, und ein "Follower", der noch etwa 30 Minuten weiter mitliest — speisen einen einzigen Zustellungstrichter mit zugbezogener Deduplizierung (kanonisierter Text plus Medien-Fingerabdruck). Jeder einzelne Pfad kann versagen, und die Antwort kommt trotzdem genau einmal an. Selbst bei einem CLI-Fehler läuft zuerst der Abgleicher, weil das Modell seine Antwort oft schon ins Transkript geschrieben hat, bevor es abstürzte.
Die zweistufige Sperre: normaler Modus vs. Safe Mode
Diese Unterscheidung ist wichtig, weil die entsperrte Seite kein Spielzeug ist. Diese Agenten können tatsächlich Dinge tun: Quellcode bearbeiten, Änderungen auf meine Live-Website pushen, Tools ausführen, die Maschine verwalten, auf der sie laufen. Die gesperrte Seite bietet nur allgemeine Informationen — trotzdem wirklich nutzbar, man kann Fragen stellen, chatten und echte Hilfe bekommen — aber bewusst von all dieser Macht abgeschottet. Der Grund ist einfach: Ich habe ein dreijähriges Kind, und das Familien-Smartphone ist nicht immer in Erwachsenenhänden. Man stelle sich vor, was ein Dreijähriger mit vollem Zugriff über mein Smartphone anstellen könnte — die Website bestünde nur noch aus Paw-Patrol-Videos. Safe Mode ist also Kindersicherung, serverseitig durchgesetzt. Eine kleine Komfortverbesserung: Der gesperrte Startbildschirm zeigt inzwischen dieselben Bot-Avatare wie die entsperrte Seite, statt der alten Buchstabenblock-Platzhalter. So läuft es ab, wenn eine Anfrage ohne eingegebene PIN eintrifft:
Sperrt man sich selbst aus, gibt es eine Recovery-Code-Datei plus eine editierbare YAML, kein Support-Ticket. Und falls security.yaml beim Schreiben beschädigt wird, ist der Fehlerfall "bleibt gesperrt", nicht "liefert alles an jeden im LAN aus". Das war eine bewusste Entscheidung.
Fallstricke
- Das API-Feld heißt
content, nichttext.POST /api/injecterwartet{"bot_id": "...", "content": "..."}. Der mit Abstand häufigste Integrationsfehler, einschließlich solcher, die ich selbst gemacht habe. - Lange Nachrichten nicht als CLI-Argument übergeben. Der Kernel begrenzt argv auf 128 KB (
MAX_ARG_STRLEN); eine lange Antwort lässt den Start mit E2BIG abstürzen. Der Code verwendet--message-file <tmpfile>— bitte nicht zurück zu einem Argument "vereinfachen". - Bot-IDs unterscheiden Groß-/Kleinschreibung an genau einer Stelle. OpenClaws Gateway schreibt Session-Keys in seinem Index klein, aber die eigenen IDs sind gemischt (
Doxy,DS_Brain). Ein case-sensitiver Abgleich lässt das Arbeitet-gerade-Panel und den Spät-Antwort-Follower still ausfallen, während alles andere weiterläuft — unangenehm zu bemerken. Erst exakter Abgleich, dann Fallback auf Kleinschreibung. - Die CLI gibt nur den letzten Block zurück. Alles, was ein Agent zwischen Tool-Aufrufen sagt, existiert nur in seinem Transkript, nicht im Rückgabewert der CLI — daher der Watcher und der Abgleicher, und die Sonderbehandlung für das "hier ist, was ich bisher gefunden habe" eines Agenten, während er auf einen Subagenten wartet.
- Cross-Site-WebSocket-Hijacking wird durch eine Origin-Prüfung blockiert. Beide WS-Routen lehnen einen Handshake ab, dessen
Origin-Host nicht zuHostpasst; Nicht-Browser-Clients senden kein Origin und kommen problemlos durch. - SVGs werden absichtlich nicht als Medien ausgeliefert. Ein Browser behandelt Inline-SVG als Skript derselben Herkunft — eines als Bild zurückzugeben, wäre ein gespeichertes XSS-Loch. Medien-Antworten bekommen stattdessen eine Sandboxing-CSP und nosniff.
- Service-Worker-Updates lassen alte Tabs auf alter JS hängen. Sieht genau wie eine Regression aus, bis man merkt, dass der Tab nie den neuen Worker übernommen hat. Behoben mit einem Auto-Reload bei
controllerchange.
Die Idee nachbauen
Selbst wenn man mein Zip nie ausführt, lohnt es sich, die Architektur zu übernehmen. Das Ganze besteht aus vier unabhängigen Teilen, jeder für sich ersetzbar:
- Ein Chat-Backend, das die Historie besitzt: ein kleiner Webserver (hier FastAPI + eine einzelne SQLite-Datei im WAL-Modus) mit einem WebSocket für Live-Updates und einem einfachen HTTP-Endpunkt, um Nachrichten von außen einzuspeisen. Das ist der komplette Ersatz für "Datenbank und Container-Haufen".
- Ein Agenten-Gateway, das das Backend per Subprozess oder HTTP aufruft — hier OpenClaw, aber alles, was "Session-ID + Nachricht rein, Text raus" beherrscht, funktioniert. Es als separaten Prozess zu halten bedeutet, dass die Chat-App überlebt, wenn der Agenten-Stack abstürzt, aktualisiert oder komplett ausgetauscht wird.
- Lokale Modellserver hinter dem Gateway (LM Studio, vLLM, Ollama — das Problem des Gateways, nicht der Chat-App).
- Optionale Medien-Dienste, wie ein lokales ComfyUI für Bilderzeugung, die Agenten als Tool aufrufen und deren Ausgabe das Backend einfach als Medium speichert.
Die Lehren, die unabhängig vom Stack übertragbar sind: den Safe-/Locked-Modus auf dem Server durchsetzen, niemals per CSS; "die Antwort des Modells erreicht den Nutzer genau einmal" als echtes Problem verteilter Systeme behandeln, nicht als Annahme für den Glückspfad; und das Frontend build-frei halten, wenn KI-Agenten es später warten sollen.
Downloads
Kostenlos für die private Nutzung. Wenn es dir einen Nachmittag erspart, ist der Kaffee-Button gleich in der Nähe.