DisPatch : une appli de chat auto-hébergée pour agents IA locaux

Publié
11 juillet 2026
Mis à jour
11 juillet 2026
Par
Jacob Lloyd — rédigé avec l'aide de l'IA, une fois le projet terminé
Temps de lecture
14 min de lecture

En clair : DisPatch, c'est trois choses en une, hébergées entièrement sur votre propre ordinateur : une appli de chat pour parler à des assistants IA qui vivent eux aussi sur cette machine, un serveur de fichiers, et un terminal — disponibles sur tous vos appareils, à tout moment, sans rien qui parte vers un service cloud. Son serveur de fichiers fait passer des fichiers de n'importe quel appareil vers l'ordinateur qui fait tourner les assistants, pour qu'ils puissent travailler avec ce que vous déposez. Sur les appareils partagés, un sous-ensemble sûr d'assistants est disponible sans code PIN ; entrer le code déverrouille les assistants à pleine puissance.

DisPatch est l'appli que j'ai construite pour arrêter de faire tourner une installation Rocket.Chat à 30 conteneurs juste pour texter mes propres agents IA. Au final, c'est trois choses en une — une appli de chat LLM auto-hébergée, un serveur de fichiers et un terminal — disponibles sur tous les appareils que je possède, à tout moment. C'est un zip d'environ 720 Ko, c'est gratuit, et c'est aujourd'hui le seul frontend de chat que j'utilise.

En bref

  • Ce que c'est : trois choses en une, auto-hébergées : un chat web pour toute une écurie d'agents IA locaux (une passerelle OpenClaw), un serveur de fichiers en glisser-déposer, et un vrai terminal — sur tous les appareils de votre LAN ou de votre réseau Tailscale, à tout moment.
  • Ce que ça coûte : 0 €. Pas de siège SaaS, pas de compte.
  • Ce qu'il vous faut : Linux, Python 3.12+, et uv. Une passerelle OpenClaw si vous voulez des réponses tapées par un agent — tout le reste fonctionne sans elle.
  • Ce que vous obtenez : des fils de discussion par bot, du Markdown en streaming, un serveur de fichiers en glisser-déposer, un terminal d'agent de codage intégré, un « mode sécurisé » verrouillable par code PIN, une API de push pour les tâches cron, et 187 tests qui passent tels quels dès la sortie du zip.

Ce que vous obtenez

Avant de parler installation, voici à quoi ça ressemble au travail. Les captures viennent d'une copie en bac à sable avec des données factices — personne n'a besoin de voir mes vrais journaux de bots.

Disposition de DisPatch sur bureau à 1440px : une colonne de bots avec des avatars en blocs de lettres colorées à gauche, une liste de fils de discussion, et un panneau de chat affichant un briefing matinal en Markdown avec un bloc de code coloré et un badge de modèle.
Bureau, 1440px : colonne de bots, liste de fils, panneau de chat. Le briefing et le bloc de code sont du Markdown rendu, pas des captures d'écran collées.
Disposition mobile de DisPatch à 390px : la même conversation dans un seul panneau avec une barre d'onglets en bas pour Bots, Chats et Messages, et des actions Copier, Régénérer et Supprimer sur chaque message.
La même conversation à 390px. La colonne se replie en barre d'onglets en bas ; chaque message reçoit Copier / Régénérer / Supprimer.
DisPatch en mode sécurisé après avoir défini un code PIN : la colonne de bots est réduite à trois bots approuvés, avec un bouton cadenas ambré en bas pour déverrouiller le reste.
Mode sécurisé avec un code PIN défini : seuls les bots marqués « sûrs » apparaissent, avec les mêmes avatars que côté déverrouillé. Ce n'est pas un filtre CSS — le serveur n'envoie tout simplement jamais le reste.

Ce que ça fait vraiment

  • Des fils de discussion par bot, partout. Ouvrez-le sur votre téléphone en pleine conversation, c'est le même fil, le même historique, sans cérémonie de reconnexion.
  • Un serveur de fichiers — glissez-déposez des fichiers entre tous vos appareils et la machine. La fonctionnalité que j'utilise le plus ; elle a sa propre section plus bas.
  • Un terminal (Terminex) — un vrai shell dans le navigateur qui fait tourner un agent de codage. Aussi sa propre section plus bas.
  • Des réponses en Markdown streamé avec du code coloré (bouton de copie inclus), des images/vidéos avec une visionneuse en zoom, installable en tant que PWA.
  • Un panneau « en cours » en direct. Cliquez sur les points de saisie pour voir en direct les appels d'outils et le raisonnement du modèle — ça suit la transcription en direct de l'agent, ce n'est pas un simple spinner de façade.
  • Un verrouillage à deux niveaux. Un code PIN sépare les agents à pleine puissance d'un sous-ensemble sûr, à usage général — détails dans la section sur le verrouillage à deux niveaux plus bas.
  • Une API de pushPOST /api/inject — pour qu'une tâche cron ou un autre agent puisse déposer un message (ou une image) dans un fil. C'est comme ça qu'un briefing quotidien atterrit sur votre téléphone sans que vous ayez rien demandé.
  • Génération d'images locale dans le fil. Demandez une image à un agent, et il peut appeler une instance ComfyUI locale ; le résultat atterrit dans le chat comme n'importe quel autre média, visionneuse zoom incluse. Pas d'API image, pas d'upload chez qui que ce soit.
  • Optionnel, désactivé par défaut : un panneau ComfyUI pour démarrer ou arrêter ce service de génération d'images depuis le chat.

Le serveur de fichiers

De tout ce que contient DisPatch, le serveur de fichiers est la pièce que j'utilise le plus. C'est une zone de fichiers en glisser-déposer servie depuis la même machine : ouvrez DisPatch sur n'importe quel appareil, déposez un fichier dessus, et le fichier se retrouve sur le serveur — groupé par jour, avec des vignettes pour les images et les vidéos.

Vue du serveur de fichiers de DisPatch : une zone de dépôt en haut, puis des fichiers groupés par jour avec des vignettes d'images et des cartes par type de fichier.
Le serveur de fichiers : zone de dépôt en haut, uploads groupés par jour en dessous. Depuis un téléphone, c'est tout le flux de travail.

Ce qui en fait plus qu'une simple commodité, c'est atterrissent les fichiers : sur la même machine où vivent les agents. Une photo envoyée depuis mon téléphone peut être lue sur le disque par n'importe quel agent quelques secondes plus tard — décrite, classée, mise sur un site web, passée aux outils d'image. C'est le pont entre « un fichier sur l'appareil que je tiens en main » et « un fichier avec lequel mes agents peuvent travailler », sans drive cloud au milieu et sans câbles.

Les aspects peu glamour sont pris en charge : les uploads sont vérifiés en taille avant d'être mis en tampon (un téléphone ne peut pas faire planter la machine par OOM), il y a un plafond de stockage global, les écritures sont atomiques pour qu'un upload interrompu ne laisse jamais un fichier à moitié écrit, et le stock de fichiers est répliqué dans le même schéma de sauvegarde en rotation que l'historique de chat. Un indicateur de configuration peut aussi basculer tout le système en un mode dépôt à sens unique, upload seulement — les appareils peuvent ajouter des fichiers mais pas les parcourir — pour les configurations où plus de monde peut atteindre l'appli que ne devrait pouvoir lire son archive.

Terminex : un terminal dans le chat

L'autre fonctionnalité qui justifie sa place au quotidien : DisPatch peut mettre un vrai terminal dans le navigateur. Il apparaît dans la colonne de bots comme n'importe quel autre bot, mais l'ouvrir vous connecte à un PTY côté serveur, streamé sur un WebSocket dans xterm.js — une vraie session shell sur la machine, pas une simulation.

Vue du terminal de DisPatch : un panneau xterm.js dans l'appli de chat avec des commandes Start, Restart et Stop, faisant tourner une CLI d'agent de codage.
Terminex : un PTY côté serveur dans l'appli de chat, avec des commandes Start / Restart / Stop.

Chez moi, il se lance directement dans Reasonix, l'agent de codage façon Claude Code que je fais tourner sur DeepSeek — la configuration que la page du hub d'assistants appelle Terminex. Ça transforme un onglet de chat en une véritable console d'agent de codage : donnez-lui une tâche, regardez-le travailler, depuis n'importe quel appareil capable d'ouvrir l'appli. Combiné au serveur de fichiers, ça rend la machine entièrement pilotable depuis un téléphone — déposez un fichier d'où que vous soyez, ouvrez le terminal, dites à l'agent quoi en faire.

  • Mode déverrouillé uniquement. Le terminal n'existe pas en mode sécurisé, et il reste entièrement désactivé tant que vous ne l'activez pas (LOCAL_CHAT_TERMINAL).
  • La session est en mémoire. Start / Restart / Stop depuis l'appli ; un redémarrage du serveur y met fin. Il n'y a pas de shell persistant caché en arrière-plan.

Pourquoi ça existe

Ça a remplacé un déploiement Rocket.Chat complet — une base de données et un tas de conteneurs pour qu'une seule personne puisse parler à quelques modèles locaux. Ça marchait, mais c'est beaucoup d'infrastructure pour « envoyer du texte, recevoir du texte, synchroniser partout ».

Et la réponse plus profonde à « pourquoi auto-héberger une appli de chat, de toute façon » : une fois que vos modèles tournent en local, l'interface de chat est la dernière pièce qui vous tente encore vers un service cloud. L'auto-héberger signifie que vos prompts, les sorties de vos agents, et les messages de votre famille ne quittent jamais la maison, et que toute la boucle — interface, passerelle, modèles, génération d'images — continue de fonctionner même internet débranché. Si vous vous êtes déjà engagé vers des LLM locaux, un frontend de chat hébergé est le seul élément qui détonne encore.

Un choix de conception qui mérite d'être souligné : DisPatch n'a délibérément aucune étape de build. Le frontend est du JS vanilla (modules ES) et du CSS/HTML brut. Pas de bundler, pas de npm run build. Mon exigence réelle au départ était « facile pour un agent IA à modifier plus tard ». On édite un fichier JS, on rafraîchit, c'est fait. Je referais le même choix.

La stack

CoucheCe que c'est
BackendPython 3.12+, FastAPI + uvicorn, aiosqlite (un seul fichier SQLite, mode WAL), Pillow, géré avec uv
Taille du backend~7 200 lignes dans backend/app/ (main.py à lui seul fait ~3 600), plus ~2 700 lignes de tests
FrontendJS vanilla sans build + HTML/CSS — ~4 900 lignes de JS, ~2 100 de CSS/HTML/service worker
Bibliothèques embarquéesmarked, highlight.js, DOMPurify, xterm.js — aucun appel CDN
DonnéesHors du code, dans ~/.local/share/local-chat/ : BDD, config.yaml, security.yaml, media/, files/, sauvegardes de BDD en rotation

L'installer

Condensé depuis README-SETUP.md à l'intérieur du zip (boîte de téléchargement plus bas).

unzip dispatch-2026-07.zip
cd dispatch
./install.sh
./run.sh

Ça le démarre sur http://0.0.0.0:8765 et affiche les URL LAN et Tailscale, pour que vous puissiez en taper une depuis votre téléphone. À partir de là :

  • Éditez ~/.local/share/local-chat/config.yaml (créé automatiquement, documenté dans config.yaml.example) pour que les id de bots correspondent à vos ids d'agents OpenClaw. Ça se recharge à chaud.
  • Optionnel : ./install.sh --systemd génère une unité systemd utilisateur.
  • Optionnel : définissez un code PIN depuis l'icône d'engrenage → Security. security.yaml.example documente le fichier, y compris api_token pour quiconque interroge /api/inject depuis l'extérieur de la machine.
  • Variables d'environnement : LOCAL_CHAT_HOST/PORT/DATA_DIR/AGENT_TIMEOUT/MAX_CONCURRENCY, plus LOCAL_CHAT_COMFY=0 / LOCAL_CHAT_TERMINAL=0 pour garder les panneaux optionnels désactivés.

La CLI openclaw elle-même n'est pas incluse — DisPatch s'attend à ce qu'une passerelle OpenClaw tourne déjà quelque part qu'elle peut atteindre. Pas encore de passerelle ? L'appli démarre quand même, prévient dans les journaux, et tout fonctionne sauf les réponses tapées par un agent : fils de discussion, API d'injection, serveur de fichiers, PIN et mode sécurisé. C'est comme ça que j'ai testé ce zip en conditions réelles — décompression neuve, uv sync, port libre, aucune passerelle — et la suite de tests fournie a donné 187 passed in 8.4s.

Ce qui se passe vraiment quand un message est tapé

Vous tapez quelque chose et appuyez sur envoyer. Voici le chemin que ça emprunte :

Ça, c'est le chemin heureux. Le vrai travail d'ingénierie a porté sur les chemins malheureux : la CLI qui expire, qui plante, ou un sous-agent qui répond dix minutes trop tard. Quatre sources indépendantes — l'observateur en direct, le payload JSON de la CLI, un réconciliateur post-tour qui rescanne la transcription, et un « suiveur » qui continue de suivre pendant encore ~30 minutes — alimentent un seul entonnoir de livraison avec une déduplication limitée au tour (texte canonicalisé plus empreinte du média). N'importe lequel de ces chemins peut échouer, la réponse arrive quand même exactement une fois. Même en cas d'erreur de la CLI, le réconciliateur passe en premier, parce que le modèle a souvent déjà écrit sa réponse dans la transcription avant de mourir.

Le verrouillage à deux niveaux : mode normal vs. mode sécurisé

Cette distinction compte parce que le côté déverrouillé n'est pas un jouet. Ces agents peuvent réellement faire des choses : éditer du code source, pousser des changements vers mon site web en production, exécuter des outils, administrer la machine sur laquelle ils vivent. Le côté verrouillé, c'est de l'information générale uniquement — toujours réellement utilisable, vous pouvez poser des questions, discuter, obtenir de vraies réponses — mais délibérément tenu à l'écart de tout ce pouvoir. La raison est simple : j'ai un enfant de trois ans, et le téléphone familial n'est pas toujours entre des mains adultes. Imaginez ce qu'un enfant de trois ans pourrait faire avec un accès complet depuis mon téléphone — le site deviendrait entièrement des vidéos de la Pat' Patrouille. Le mode sécurisé, c'est donc un verrou de sécurité enfant, appliqué côté serveur. Une petite amélioration de confort : l'écran d'accueil verrouillé affiche désormais les mêmes avatars de bots que le côté déverrouillé, au lieu des anciens blocs de lettres génériques. Voici ce qui se passe quand une requête arrive sans code PIN encore saisi :

Si vous vous verrouillez vous-même dehors, il y a un fichier de code de récupération plus un YAML éditable, pas un ticket de support. Et si security.yaml se corrompt en cours d'écriture, le mode de défaillance est « reste verrouillé », pas « sert tout à n'importe qui sur le LAN ». C'était un choix délibéré.

Pièges

  • Le champ de l'API est content, pas text. POST /api/inject attend {"bot_id": "...", "content": "..."}. L'erreur d'intégration la plus fréquente, et je l'ai moi-même faite plus d'une fois.
  • Ne passez pas de longs messages en argument de CLI. Le noyau plafonne argv à 128 Ko (MAX_ARG_STRLEN) ; une longue réponse fait planter le lancement avec un E2BIG. Le code utilise --message-file <tmpfile> — ne le « simplifiez » pas en argument.
  • Les ids de bots sont sensibles à la casse à un seul endroit précis. La passerelle d'OpenClaw met en minuscules les clés de session dans son index, mais vos ids sont en casse mixte (Doxy, DS_Brain). Une recherche sensible à la casse tue silencieusement le panneau « en cours » et le suiveur de réponse tardive, tandis que tout le reste continue de fonctionner — difficile à repérer. Correspondance exacte d'abord, repli en minuscules ensuite.
  • La CLI ne renvoie que le dernier bloc. Tout ce qu'un agent dit entre deux appels d'outils n'existe que dans sa transcription, pas dans la valeur de retour de la CLI — d'où l'observateur et le réconciliateur, et le traitement spécial pour un « voici ce que j'ai trouvé jusqu'ici » pendant qu'il attend un sous-agent.
  • Le détournement WebSocket intersite est bloqué par une vérification d'Origin. Les deux routes WS rejettent toute poignée de main dont l'hôte Origin ne correspond pas à Host ; les clients non-navigateurs n'envoient pas d'Origin et passent sans problème.
  • Les SVG ne sont délibérément pas servables comme médias. Un navigateur traite un SVG inline comme du script de même origine — en renvoyer un comme image serait une faille de XSS stocké. Les réponses médias reçoivent à la place un CSP de sandboxing et un nosniff.
  • Les mises à jour du service worker laissent les vieux onglets coincés sur l'ancien JS. Ça ressemble exactement à une régression jusqu'à ce qu'on réalise que l'onglet n'a jamais récupéré le nouveau worker. Corrigé avec un rechargement automatique sur controllerchange.

Reproduire l'idée

Même si vous ne faites jamais tourner mon zip, l'architecture vaut le coup d'être copiée. L'ensemble se résume à quatre pièces indépendantes, chacune remplaçable :

  • Un backend de chat qui possède l'historique : un petit serveur web (ici FastAPI + un unique fichier SQLite en mode WAL) avec un WebSocket pour les mises à jour en direct et un point d'entrée HTTP brut pour pousser des messages. C'est tout le remplacement de « base de données et tas de conteneurs ».
  • Une passerelle d'agents que le backend appelle en sous-processus ou via HTTP — ici OpenClaw, mais n'importe quoi qui accepte « id de session + message en entrée, texte en sortie » fonctionne. Garder ça comme un processus séparé signifie que l'appli de chat survit à un plantage, une mise à jour, ou un remplacement complet de la stack d'agents.
  • Des serveurs de modèles locaux derrière la passerelle (LM Studio, vLLM, Ollama — le problème de la passerelle, pas celui de l'appli de chat).
  • Des services média optionnels, comme un ComfyUI local pour la génération d'images, que les agents appellent comme des outils et dont le backend se contente de stocker la sortie comme un média.

Les leçons qui se transposent quelle que soit la stack : appliquez votre mode sûr/verrouillé côté serveur, jamais en CSS ; traitez « la réponse du modèle est arrivée à l'utilisateur exactement une fois » comme un vrai problème de systèmes distribués, pas comme une hypothèse de chemin heureux ; et gardez le frontend sans étape de build si vous comptez laisser des agents IA le maintenir.

Téléchargements

Gratuit pour un usage personnel. Si ça vous fait gagner un après-midi, le bouton café n'est pas loin.


← Plus de IA et LLM locaux