aigate: Ta Putain de Propre Infrastructure IA

Une 3060 et zéro budget cloud. Des modèles d’IA derrière un seul endpoint: cinq fournisseurs cloud avec des offres gratuites, cinq moteurs locaux qui tournent sur ta propre machine, le reste sur abonnement ou au token en dernier recours.
Pas en théorie. Tout de suite. aigate est une stack Docker derrière un seul port nginx. Génération de texte, génération d’images, synthèse vocale, transcription, recherche web, automatisation de navigateur, stockage objet, exécution de code agentique, exécution de code isolée en sandbox et multi-langage, une passerelle e-mail, ton compte Telegram en outil MCP, des prévisions sur séries temporelles, de la production audio, une boîte à outils vidéo avec lipsync, une file de jobs asynchrones et une interface web. Sur du matériel qui coûte moins qu’un seul mois de facture d’API OpenAI.
Tout est compatible OpenAI. Pointe n’importe quel client sur http://localhost:4000 et ça marche. Ton code existant, tes SDK existants, tes outils existants: ils lui parlent tous comme si c’était OpenAI. Ça ne l’est pas. C’est à toi.

Des Modèles, Gratuits pour la Plupart

Cinq fournisseurs, Groq, OpenRouter, HuggingFace, Mistral, Cohere, ont une offre gratuite. Pas de carte bancaire pour démarrer. Mais soyons honnêtes sur ce que “gratuit” veut dire, parce que les pages marketing ne le seront pas: gratuit veut dire limité en débit et plafonné, pas illimité. Groq te donne 30 RPM et entre 1K et 14,4K requêtes par jour selon le modèle. OpenRouter c’est 50 requêtes par jour à 0 dollar, 1K par jour si tu as déjà mis 10 dollars. HuggingFace te tend 0,10 dollar de crédits par mois, autrement dit un budget d’évaluation, pas une offre. L’essai de Cohere c’est 1K appels par mois et ça s’épuise vite. L’offre “Experiment” de Mistral ne publie aucune limite précise. Cerebras était sur cette liste. Il n’y est plus: au dernier audit chaque modèle chez eux répondait “payment required”, donc il est dans la stack comme fournisseur payant avec trois modèles, pas comme offre gratuite. C’est l’autre truc avec les offres gratuites, elles se retirent, et une stack bâtie sur exactement une d’entre elles est une stack qui casse le jour où elle disparaît.
Donc non, tu ne fais pas tourner une boîte sur des tokens gratuits. Mais tu fais tourner tes propres charges dessus, et quand l’une s’assèche la suivante prend le relais. C’est tout l’intérêt.
Trois autres, claudebox, pibox-zai et pibox, sont des containers d’agent. Les deux premiers tournent sur abonnement, ce qui n’est pas pareil que gratuit: le quota est compté quand même, tu le paies juste au mois plutôt qu’au token. Anthropic et OpenAI sont dans la stack mais en dernier recours. Cinq moteurs locaux tournent sur ta propre machine: pas de réseau, pas de limite de débit, aucun coût.
La philosophie de routage est simple: ne jamais payer un token que tu pouvais avoir gratuitement.
LiteLLM s’en charge. Tu demandes groq-llama-3.3-70b. Groq te limite. LiteLLM bascule en silence sur cerebras-gpt-oss-120b. Cerebras est down. Bascule sur mistral-small. Mistral répond. Tu obtiens ta réponse. Le client n’a jamais su que quoi que ce soit s’était passé. Chaque modèle a sa chaîne de repli: cloud gratuit d’abord, puis abonnement, puis paiement au token, puis local. La chaîne est reconstruite à chaque démarrage de la stack, filtrée sur les seuls fournisseurs que tu as réellement activés.

groq-llama-3.3-70b → 429 rate limited
  ↓ fallback
cerebras-gpt-oss-120b → 503 unavailable
  ↓ fallback
mistral-small → 200 ✓

Tu n’as pas écrit de logique de retry. Tu n’as pas écrit de logique de repli. Tu as envoyé une requête et reçu une réponse.

L’Architecture

nginx :4000                                    ┌──────────── always on ────────────┐
├─► /claudebox/            → claudebox         │ nginx, LiteLLM, PostgreSQL, Redis │
├─► /pibox-zai/            → pibox-zai         │ proxq — everything else is opt-in │
├─► /pibox/                → pibox             └───────────────────────────────────┘
├─► /stealthy-auto-browse/ → HAProxy → [×5]
├─► /storage/              → hybrids3
├─► /q/                    → proxq → LiteLLM (async, returns job ID)
├─► /librechat/            → LibreChat (web UI, LIBRECHAT=1)
├─► /searxng/              → SearXNG (meta-search, SEARXNG=1)
├─► /telethon/             → Telethon (Telegram client, TELETHON=1)
├─► /mailbox/              → mailbox (IMAP+SMTP gateway, MAILBOX=1)
├─► /piston/               → piston (sandboxed code execution, PISTON=1)
├─► /predictalot/          → predictalot (forecasting + tabular ML, PREDICTALOT=1)
├─► /audiolla/             → audiolla (audio production, AUDIOLLA=1)
├─► /flickies/             → flickies (video toolkit, FLICKIES=1)
└─► /                      → LiteLLM (sync)
    ├─ Groq              (free: 30 RPM, 1K-14.4K RPD per model, GROQ=1)
    ├─ Cerebras          (paid plan, 3 models, CEREBRAS=1)
    ├─ OpenRouter        (free: 50 RPD $0 / 1K RPD with $10+, OPENROUTER=1)
    ├─ HuggingFace       (free: $0.10/mo credits — eval only, HUGGINGFACE=1)
    ├─ Mistral           (free "Experiment" tier, MISTRAL=1)
    ├─ Cohere            (trial: 1K calls/MONTH, COHERE=1)
    ├─ Ollama            (local CPU + CUDA, OLLAMA=1 / OLLAMA_CUDA=1)
    ├─ Talkies           (local ASR + TTS, TALKIES=1 / TALKIES_CUDA=1)
    ├─ sd.cpp            (local image gen, SDCPP=1 / SDCPP_CUDA=1)
    ├─ vLLM              (local text LLM + embeddings, VLLM=1 / VLLM_CUDA=1)
    ├─ llama.cpp         (local GGUF + vision-VLM, LLAMACPP=1 / LLAMACPP_CUDA=1)
    ├─ claudebox         (subscription, CLAUDEBOX=1)
    ├─ pibox-zai         (subscription, PIBOX_ZAI=1)
    ├─ pibox             (points back at this stack's LiteLLM, PIBOX=1)
    ├─ Anthropic         (pay-per-token, ANTHROPIC=1)
    └─ OpenAI            (pay-per-token, OPENAI=1)
MCP servers (all optional):
  ├─ stealthy_auto_browse  — run_script: multi-step browser automation (BROWSER=1)
  ├─ hybrids3              — upload, download, list, delete, presign (HYBRIDS3=1)
  ├─ claudebox             — agentic Claude Code via OAuth or API key (CLAUDEBOX=1)
  ├─ pibox_zai             — agentic pi-coding-agent via z.ai/GLM (PIBOX_ZAI=1)
  ├─ pibox                 — agentic pi-coding-agent on your own models (PIBOX=1)
  ├─ telethon              — your Telegram account as a tool (TELETHON=1)
  ├─ mailbox               — IMAP+SMTP across N accounts (MAILBOX=1)
  ├─ predictalot           — time-series forecasting (PREDICTALOT=1)
  ├─ audiolla              — audio production, MIR, mastering, MIDI (AUDIOLLA=1)
  ├─ flickies              — lipsync, face restore, ffmpeg ops (FLICKIES=1)
  └─ mcp_tools             — generate_image + generate_tts + search_web
                             + execute_code (auto-enabled)

Tout est opt-in. Tu bascules des flags dans .env. Pas de clé Anthropic? Ne la mets pas. Seulement un CPU? Saute les flags CUDA. La stack s’adapte à ce que tu as et reconstruit sa config en conséquence. Le noyau toujours actif, c’est cinq choses: nginx, LiteLLM, PostgreSQL, Redis, proxq. Tout le reste est un flag.

Des Outils que N’importe Quel Modèle Peut Appeler

C’est ce qui fait d’aigate autre chose qu’un proxy avec une chaîne de repli. Un tas entier de serveurs MCP, des dizaines d’outils. N’importe quel modèle avec du function calling peut les invoquer tout seul. Toi tu prompts. Le modèle décide des outils qu’il lui faut. Toi tu reçois des résultats.
Le déroulé concret: tu envoies un prompt à Groq, gratuit, rapide. Le modèle décide qu’il doit se renseigner sur quelque chose. Il appelle search_web, SearXNG interroge Google, Bing et DuckDuckGo en même temps et renvoie des résultats. Le modèle veut plus de détail sur un résultat. Il appelle l’outil navigateur. Une instance Camoufox ouvre un vrai Firefox, bouge un vrai curseur de souris, charge la page, extrait le contenu. Le modèle lit, décide de sauvegarder le résultat. Il appelle l’outil de stockage. hybrids3 écrit le fichier et renvoie une URL publique. Le modèle décide qu’il lui faut une image pour le rapport. Il appelle generate_image. stable-diffusion.cpp la génère en local, l’envoie dans le stockage, renvoie une URL. Le modèle te rend une réponse structurée avec les liens vers tout ce qu’il a produit. Un seul appel d’API. Zéro token payé. Le client a vu une requête et une réponse.

stealthy_auto_browse

Cinq réplicas Camoufox derrière un HAProxy. Camoufox est un Firefox durci: vraies entrées souris et clavier au niveau de l’OS via PyAutoGUI, zéro exposition CDP, empreintes persistantes par session. Il passe Cloudflare. Il passe CreepJS. Il passe BrowserScan. Il passe Pixelscan. Pas “il passe à peu près”, il passe vraiment, parce qu’il n’est pas détectable comme automatisation par les moyens que ces systèmes utilisent.
Un seul outil: run_script. Des scripts en plusieurs étapes: naviguer, cliquer, taper, extraire, capturer l’écran, faire défiler, attendre des éléments, exécuter du JavaScript. Le modèle écrit le script, le navigateur l’exécute, tu récupères des données structurées de la page en vrai.

hybrids3

Du stockage objet compatible S3 qui tourne en local. Le bucket uploads est en lecture publique: les fichiers sont accessibles par URL directe, sans signature. URL PUT présignées pour les envois directs. Expiration automatique. Des outils pour put, get, list, delete, info, presign et lister les buckets.
Ça règle un problème précis des workflows agentiques. Quand un modèle produit quelque chose de gros, un jeu de données scrapé, une image générée, un rapport rendu, tu ne le fourres pas dans la fenêtre de contexte. Tu le mets dans le stockage, tu récupères une URL, tu passes l’URL. L’étape suivante peut aller la chercher. Toi aussi. Le contexte reste propre.

Trois Agents de Code: claudebox, pibox-zai et pibox

Trois agents de code agentiques différents, pas trois copies du même. claudebox fait tourner Claude Code sur ton abonnement ou ta clé d’API. pibox-zai fait tourner pi-coding-agent pointé sur z.ai pour les modèles GLM (glm-5.3 et glm-5.3-flash). pibox est le même agent pointé en retour sur le LiteLLM de cette stack, donc il code sur tout ce que tu as déjà activé: tu nommes les modèles avec PIBOX_MODELS, tu en choisis un par défaut avec PIBOX_DEFAULT_MODEL. Deux réserves là-dessus, et les deux mordent: un modèle que tu listes doit vraiment gérer le tool calling, sinon l’agent n’a rien à piloter, et ne redonne pas à un agent son propre modèle dans la liste sauf si la récursion t’amuse. Tous les trois ont un accès shell complet, des workspaces persistants, des I/O fichiers, du tool use, et tous les trois exposent une API REST, un endpoint compatible OpenAI et un serveur MCP.
Tu utilises Groq pour la vitesse. Groq tombe sur un truc qui demande du vrai boulot de code. Groq appelle l’outil claudebox. Claude Code prend le relais, obtient un shell, écrit du code, lance les tests, renvoie des résultats structurés. Retour dans le contexte de Groq. L’orchestration se passe dans la boucle de function calling du modèle, et tu n’as écrit aucune de ces lignes.

piston: Exécution de Code en Sandbox

Les LLM sont catastrophiquement mauvais en arithmétique, en hachage et en parsing. Alors ne les fais pas deviner. execute_code lance du vrai code dans une sandbox nsjail, avec son propre user namespace, un chroot, un filtre seccomp, des limites cgroup et aucun réseau, et rend au modèle un résultat déterministe.
L’installation par défaut, c’est Python plus Node. Tu ajoutes Bash, Deno, Go, Rust, TypeScript ou une quarantaine d’autres langages via PISTON_LANGUAGES et tu reconstruis. Le modèle a besoin d’un SHA-256, d’un écart entre deux dates, d’une regex appliquée à 10K lignes? Il écrit le code, piston le lance dans la cellule, et le chiffre revient juste au lieu d’être halluciné.

mcp_tools

generate_image, generate_tts, search_web et execute_code. Activés automatiquement dès que le backend correspondant tourne. Tous renvoient du JSON structuré, et les fichiers produits partent automatiquement dans hybrids3 avec des URL persistantes. Pas de blocs base64 dans la fenêtre de contexte.
La génération d’images passe par FLUX, DALL-E ou stable-diffusion.cpp selon ce qui est activé. Le TTS passe par Kokoro (CPU plus CUDA), Qwen3-TTS avec clonage de voix, conception de voix et contrôle de l’émotion (CUDA), ou OpenAI TTS. La recherche web interroge SearXNG. L’exécution de code va chez piston. Les outils découvrent dynamiquement les modèles disponibles depuis LiteLLM, donc ils reflètent toujours ce qui tourne vraiment.

telethon

Ton vrai compte Telegram, en outil. Pas la Bot API, du MTProto complet, exactement l’accès que tu as depuis ton téléphone. Lire des messages, en envoyer, lister les conversations, gérer des groupes, transférer du contenu, éditer, supprimer, marquer comme lu, envoyer des fichiers. Le modèle décide du moment. L’agent agit en ton nom.
Un modèle Groq en offre gratuite qui cherche sur le web, scrape une page, génère une image et envoie le résultat dans tes Messages enregistrés: zéro token payé, une conversation, plusieurs appels d’outils. Ou un cron qui résume les dernières 24 heures de ton groupe de boulot et te l’envoie en privé chaque matin. Ton compte, programmable.
Propulsé par telethon-plus. Tu bascules TELETHON=1, tu mets TELETHON_API_ID / TELETHON_API_HASH / TELETHON_SESSION dans .env. La chaîne de session, c’est l’accès complet au compte, alors traite-la comme le mot de passe qu’elle est en pratique.

mailbox: L’E-mail Comme Outil

IMAP plus SMTP sans état, sur N comptes, depuis une seule config YAML. Boîte unifiée, list/search/CRUD par compte, envoi SMTP. API REST plus un jeu d’outils MCP à plat où un paramètre mailbox choisit le compte.
Ce qui veut dire qu’un modèle peut lire ta boîte, trouver la facture, en extraire le total et répondre, sans que tu écrives une seule ligne de gestion IMAP. MAILBOX=1, tu pointes MAILBOX_CONFIG sur ton YAML, terminé.

predictalot: Prévision et ML Tabulaire

Cinq modèles de fondation zero-shot pour séries temporelles, chronos-2, timesfm-2.5, moirai-2, toto-1, sundial-base-128m, sur six types de prévision (univarié, multivarié, covariables passées, covariables futures, échantillons, et ensembles pondérés par type) à /v1/timeseries/<type>/….
Ensuite toute une famille sœur à /v1/tabular/*: neuf backends supervisés (lightgbm, xgboost, hist-gbt, random-forest, logistic, mlp, svm-rbf, knn, naive-bayes) et trois méta-apprenants (calibrated, stacking, diversified). 26 outils MCP couvrent les modèles de fondation; le tabulaire est en REST uniquement. CPU ou CUDA.
Zero-shot veut dire aucun entraînement. Tu lui tends une série, il prévoit. C’est tout.

audiolla: Production Audio

Celui-là est absurde par son étendue. Séparation de pistes (Demucs / UVR). Restauration: dé-réverbération, suppression d’écho, débruitage. Mastering via matchering et des chaînes pedalboard, avec des presets préparés comme master-for-spotify, podcast-cleanup, vocal-cleanup. Analyse MIR: BPM, tonalité, LUFS, temps, onsets, mélodie, accords, segments. Transformations DSP via sox et ffmpeg. Normalisation de loudness. Amélioration de la parole (DeepFilterNet). VAD (silero). Diarisation (pyannote). Embeddings CLAP et classification audio zero-shot. Étiquetage AudioSet. Audio→MIDI via basic-pitch, plus composition / inspection / transformation / rendu MIDI via fluidsynth.
Plus de la génération text-to-audio, stable-audio-open, musicgen, riffusion, audioldm2, en CUDA seulement.
Des jobs asynchrones et des webhooks partout. Le contrat depuis la v1.0 est strict: corps JSON sur chaque endpoint audio, octets bruts uniquement sur PUT /v1/files/{path}, et output_path xor output_url obligatoire sur tout ce qui produit de l’audio.

flickies: Vidéo et Lipsync

Lipsync via LatentSync 1.5 (ByteDance, Apache-2.0, ~8 Go de VRAM, par défaut sur CUDA) et Wav2Lip / Wav2Lip-GAN (LRS2, non commercial, accès restreint). Restauration de visages via GFPGAN v1.4. Opérations ffmpeg: découpe, concaténation, transcodage y compris changements de gif/fps/codec, mise à l’échelle, mux audio, extraction audio, grille de vignettes. Infos ffprobe. Jobs asynchrones et webhooks. 11 outils MCP, le même contrat corps JSON et output_path xor output_url qu’audiolla.
GFPGAN et LatentSync 1.5 sont en CUDA uniquement. L’image CPU fait quand même les opérations ffmpeg et un Wav2Lip très lent.

Inférence Locale: Plus Lente, Mais à Toi

Cinq moteurs locaux. Chacun d’eux a un mode CPU.
C’est la partie “ordinateur du pauvre”. Ton vieux portable avec 16 Go de RAM peut faire de la génération de texte, de la génération d’images, de la transcription et de la synthèse vocale. Pas de clé d’API. Pas de réseau. Pas de limite de débit. Pas de facture. C’est plus lent que le cloud. Parfois beaucoup plus lent. Mais c’est à toi, c’est privé, et ça marche hors ligne.
Ollama: llama3.2:3b, qwen3:4b, smollm2:1.7b, qwen2.5-coder:1.5b, qwen2.5-coder:3b, phi4-mini (raisonnement, contexte de 128K), gemma4:e2b (vision), gemma3:4b (vision), nuextract-v1.5 (extraction texte structuré vers JSON), dolphin-phi, plus deux modèles d’embedding pour le RAG. Au plus petit il faut 1 Go de RAM. OLLAMA_CUDA=1 ajoute le lot lourd: qwen3:8b, gemma4:e4b (vision), deepseek-coder-v2:16b (MoE, contexte de 160K), deepseek-r1:8b (raisonnement), qwen3-abliterated:16b (non censuré), gemma4-abliterated:e4b (vision non censurée), qwen2.5-coder:7b, llama3.1:8b. Flash attention, cache KV quantifié, stockage de modèles partagé avec le service CPU, donc pas de téléchargements en double.
talkies: le service unifié ASR plus TTS, et le remplaçant de ce qui s’appelait Speaches dans cette stack. Le CPU embarque maintenant 13 modèles: whisper-large-v3 et -turbo, canary-180m-flash, nemotron-3.5-asr-0.6b, quatre variantes Sherpa-ONNX Zipformer, Vosk small English, et deux reconnaisseurs de phonèmes qui te rendent un flux brut de phones IPA horodatés phone par phone au lieu de mots; Kokoro pour le TTS. En CUDA on monte à 22, avec en plus parakeet-tdt-0.6b-v3, canary-1b-flash, canary-qwen-2.5b, Chatterbox Turbo et la famille Qwen3-TTS, 0.6B et 1.7B, avec clonage de voix, conception de voix et contrôle de l’émotion via le champ instructions. Chatterbox est le seul d’entre eux à marquer sa sortie: le watermark neuronal PerTh de ResembleAI, activé par défaut, et TALKIES_CHATTERBOX_WATERMARK=false le coupe pour de l’audio propre, tandis que Kokoro et Qwen3-TTS n’incrustent jamais rien. Endpoints compatibles OpenAI, donc tes appels Whisper existants marchent sans changement. TALKIES=1 / TALKIES_CUDA=1.
Les ajouts Sherpa et Vosk arrivent sous forme de dix nouveaux alias de transcription: les cinq modèles sous local-talkies-, puis les mêmes cinq à nouveau sous local-talkies-cuda-. Les builds int8 sont quantifiés (plus petits et plus rapides, précision légèrement moindre); left-64 contre left-128 règle la fenêtre de contexte gauche de l’attention. Les cinq font de l’ASR live natif sur le WebSocket de streaming en plus du POST /v1/audio/transcriptions habituel, et l’image CUDA installe une wheel Sherpa CUDA amont vérifiée par hash, pour qu’ils utilisent réellement son execution provider CUDA au lieu de retomber en silence sur le CPU dans un container avec GPU.
Une subtilité de repli qui vaut le vol: six chaînes de fallbacks.json listaient auparavant un modèle talkies frère en premier, whisper-large-v3whisper-large-v3-turbo, et les équivalents pour la variante CUDA et les deux entrées TTS. Ça ne peut jamais marcher. Chaque container talkies sert un modèle à la fois et évince le précédent à l’admission, donc un saut vers un frère ne trouve pas de capacité libre, il force un changement de modèle, et sous les retries ça devient de l’éviction en boucle. Les chaînes partent maintenant d’abord vers l’autre container talkies, puis vers les fournisseurs cloud. Les sauts CPU↔CUDA restent, parce que ceux-là sont vraiment des processus indépendants.
stable-diffusion.cpp: génération d’images en local. Le CPU fait tourner sd-turbo et sdxl-turbo d’origine. SDCPP_CUDA=1 pour l’accélération matérielle et le jeu complet: sd-turbo, sdxl-turbo, sdxl-lightning, flux-schnell, juggernaut-xi. Les modèles se téléchargent à la première utilisation et sont mis en cache localement. L’endpoint compatible OpenAI /images/generations fait que ton code existant marche tel quel.
vLLM: des LLM de texte en local plus des embeddings. qwen3-0.6b pour la génération, nomic-embed-v2 pour les embeddings. VLLM=1 / VLLM_CUDA=1.
llama.cpp: des modèles GGUF et des VLM de vision, dont Surya OCR 2 pour la compréhension de documents. LLAMACPP=1 / LLAMACPP_CUDA=1.
Les modèles locaux sont en bout de chaîne de repli par défaut. Le cloud tombe? Le local prend le relais. Ou tu les vises directement: "model": "local-ollama-cpu-llama3.2-3b". Zéro réseau. Zéro coût. Plus lent, mais ça répond.

Un Seul GPU, Tout

Voilà le problème d’ingénierie: tu as un seul GPU. Ollama veut de la VRAM pour le LLM. sd.cpp en veut pour la génération d’images. talkies en veut pour la transcription et la voix. audiolla en veut pour la séparation de pistes. flickies en veut environ 8 Go pour LatentSync. Charge-les tous et tu pars en OOM.
Le gestionnaire de ressources règle ça tout seul. Un callback LiteLLM impose une exclusion mutuelle par matériel: un seul job CUDA à la fois. Quand une requête de génération d’images arrive alors qu’un LLM est chargé, le gestionnaire prend le sémaphore, décharge le LLM, puis laisse la génération d’images se faire. Quand une requête TTS suit, il décharge d’abord le générateur d’images. Même logique côté CPU.
Chaque service a sa propre API de déchargement et le gestionnaire les connaît toutes: Ollama prend keep_alive: 0, sd.cpp a POST /sdcpp/v1/unload, talkies / vllm-cuda / llamacpp-cuda prennent DELETE /api/ps/{model_id} par modèle ou POST /unload pour tuer ce qui est chargé, et audiolla a POST /v1/unload pour évincer d’un coup tous les moteurs chargés.
Il y a aussi des endpoints côté opérateur, quand tu veux tout nettoyer à la main: POST /v1/unload/cuda se propage en parallèle vers ollama-cuda, sdcpp-cuda, talkies-cuda, vllm-cuda, llamacpp-cuda, audiolla-cuda et flickies-cuda. POST /v1/unload/cpu fait pareil pour les sept mêmes côté CPU. POST /v1/unload enchaîne les deux et te rend un rapport par service de ce qui a réellement été évincé.
Tu ne gères rien de tout ça. Tu envoies des requêtes. La plateforme jongle avec la VRAM toute seule. Le seul coût, c’est la latence: la première requête après un changement inclut le temps de chargement du modèle. Ensuite c’est rapide, jusqu’à ce que le timeout d’inactivité décharge le modèle pour libérer de la mémoire pour la suite.

Recherche Web

SearXNG à /searxng/. Méta-recherche auto-hébergée: interroge Google, Bing, DuckDuckGo et Wikipédia en même temps. Pas de clé d’API. Tourne entièrement en local.
Répare ta secret_key si tu as déployé ça tôt. Celle de SearXNG était en dur dans le searxng/settings.yml versionné. Elle est générée par déploiement maintenant et lue depuis SEARXNG_SECRET_KEY dans .env: mets-la avec openssl rand -hex 32 quand tu bascules SEARXNG=1, sinon SearXNG démarre sur le placeholder amont. Le settings.yml versionné a entièrement disparu; les réglages se rendent depuis une entrée searxng_config du bloc configs:, de la même façon que proxq_config tirait déjà ${REDIS_PASSWORD}. L’exposition était limitée, /searxng/ est derrière l’auth admin de nginx, le container ne publie aucun port sur l’hôte, et limiter: false voulait dire que la clé ne gardait aucune limite de débit, mais l’ancienne valeur est toujours posée dans l’historique git, et cette partie-là ne se dé-produit pas.
L’outil MCP search_web fait que n’importe quel modèle avec du function calling peut chercher sur le web tout seul. Le modèle décide qu’il doit vérifier quelque chose, appelle l’outil, reçoit des résultats, continue son raisonnement. Tu n’as pas construit d’intégration de recherche. Tu as basculé SEARXNG=1.

LibreChat: Celui de Tous les Jours

Tout ce qui est au-dessus marche depuis l’API. Mais pour l’usage quotidien, il y a LibreChat à /librechat/.
Tous les modèles dans le menu déroulant. Tous les outils MCP branchés. Tu choisis un modèle, tu commences à parler. Le modèle peut toujours invoquer tout seul le navigateur, le stockage, les agents de code, la génération d’images, le TTS et la recherche web: tout ce qui est disponible depuis l’API l’est aussi dans l’interface. L’historique des conversations est sur MongoDB. Envoi de fichiers. Streaming WebSocket.
Le premier utilisateur inscrit devient admin. Tu mets LIBRECHAT_ALLOW_REGISTRATION=false ensuite et tu es le seul à l’intérieur.
Tu l’actives: LIBRECHAT=1 dans .env.

File Asynchrone

Les requêtes d’inférence longues finissent en timeout. Tape sur /q/ au lieu de / et la requête part dans une file adossée à Redis. Tu récupères un ID de job instantanément. L’inférence tourne en arrière-plan. Tu interroges le statut, tu récupères le résultat quand c’est prêt.

# submit — returns 202 immediately
curl http://localhost:4000/q/v1/chat/completions 
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" 
  -d '{"model": "cerebras-gpt-oss-120b", "messages": [{"role":"user","content":"write a novel"}]}'
# → {"jobId": "550e8400-e29b-41d4-a716-446655440000"}
# check status
curl http://localhost:4000/q/__jobs/550e8400-e29b-41d4-a716-446655440000 
  -H "Authorization: Bearer $LITELLM_MASTER_KEY"
# get the result
curl http://localhost:4000/q/__jobs/550e8400-e29b-41d4-a716-446655440000/content 
  -H "Authorization: Bearer $LITELLM_MASTER_KEY"

Concurrence, rétention, timeouts, retries et cache de réponses, tout est configurable. Seuls les chemins de l’API OpenAI passent par la file; les health checks et les requêtes d’admin passent directement.

Il Livre Son Propre Agent Skill

Le dépôt embarque .agents/skills/aigate/, un agent skill publié sur ClawHub qui apprend à un agent de code comment piloter la stack. Donc au lieu d’expliquer ta propre infrastructure à un assistant à chaque session, tu installes le skill et il connaît déjà les endpoints, les flags et les contrats des outils.

Sécurité

Les services internes, PostgreSQL, MongoDB, Redis, le cluster de navigateurs, le backend de stockage, n’ont aucun port sur l’hôte. Ils sont sur des réseaux Docker isolés. Rien ne les atteint depuis l’extérieur de la stack. La seule surface exposée est nginx sur le port 4000, et elle demande une authentification par bearer token.
Les containers applicatifs tournent avec no-new-privileges:true. Deux exceptions, toutes deux délibérées et toutes deux documentées dans le fichier compose: piston a besoin de privileged: true parce que nsjail doit construire des user namespaces et des chroots à chaque exécution, l’isolation réelle vit à l’intérieur du sous-processus nsjail, et le container privilégié est précisément ce qu’il faut à nsjail pour monter cette cellule. tailscale a besoin de NET_ADMIN pour son périphérique tun en userspace. Tout le reste abandonne ses privilèges. make run vérifie que les chemins de fichiers présents dans .env existent réellement avant de démarrer, donc pas de montages de volumes cassés en silence.
Tu le veux joignable publiquement sans ouvrir de port sur le pare-feu? CLOUDFLARED=1, c’est-à-dire Cloudflare Tunnel. Protection DDoS, terminaison TLS, aucun port ouvert, aucune IP à scanner. Tunnel rapide pour une URL *.trycloudflare.com aléatoire, ou tunnel nommé pour un domaine fixe.
Tu ne le veux pas public du tout, mais tu veux quand même l’atteindre depuis ton portable, ton téléphone ou une autre machine? TAILSCALE=1 avec TS_AUTHKEY=tskey-auth-... et un TS_HOSTNAME. Un sidecar Tailscale rejoint ton tailnet et lance tailscale serve en mode forwarding TCP L4 directement vers nginx:4000: pas de correspondance sur l’en-tête Host, pas de configuration de FQDN côté tailscale, pas d’auto-certificat HTTPS (la terminaison TLS, si tu la veux, vit dans nginx). nginx reçoit les octets de la requête d’origine tels quels et les route par sa logique vhost et chemin existante. Accès via http://<TS_HOSTNAME>.<tailnet>.ts.net, et le HTTP simple convient ici parce que WireGuard chiffre déjà chaque octet dans le tailnet. Marche avec Tailscale hébergé ou avec Headscale auto-hébergé (tu utilises TS_EXTRA_ARGS=--login-server=...). L’état persiste dans .data/tailscale/, donc les redémarrages réutilisent la session existante. Combine-le avec l’auth par bearer token et tu as deux couches de contrôle d’accès complètement indépendantes.
Ça, c’est le trafic qui entre. Le trafic qui sort est une autre affaire, que tu peux maintenant activer: l’egress tailnet pour les agents de code, pour que claudebox, pibox-zai et pibox puissent tous atteindre des machines de ton tailnet et pas seulement l’internet public. Ça arrive sous forme d’overlay compose (docker-compose.tailscale.yml) et demande deux valeurs de plus, TS_MAGICDNS_SUFFIX (le suffixe MagicDNS de ton tailnet, directement depuis tailscale status) et TS_FALLBACK_DNS (par défaut 1.1.1.1). La seconde existe parce que le DNS finit coupé en deux: MagicDNS répond pour les noms du tailnet et renvoie SERVFAIL pour tout le reste, donc les noms publics ont besoin d’un résolveur sur lequel retomber.
Des centaines de tests. Health checks, routage, auth, validation des outils MCP, CRUD de stockage, automatisation de navigateur, exécutions de code agentiques, cycle de vie des jobs asynchrones, allers-retours TTS/STT en local, vérification du gestionnaire de ressources CUDA, génération d’images en local, tool calling de bout en bout du LLM au MCP, recherche SearXNG, allers-retours MTProto Telethon sur un vrai compte. Plus la sécurité: isolation entre tokens, tentatives de détournement de session, HTTP request smuggling (CL.TE/TE.CL), smuggling h2c, SSRF via le navigateur et via MCP vers les services internes, extraction de clés par prompt injection, path traversal, abus de presign S3, XSS stocké, injection de nom de modèle, injection d’en-têtes, isolation de la socket Docker. Ce n’est pas la suite de tests d’un projet du dimanche. C’est de la paranoïa érigée en fonctionnalité.

Installation

git clone https://github.com/psyb0t/aigate && cd aigate
make bootstrap        # seeds .env; then edit it: add keys, flip flags
make run-bg

Chaque variable est documentée dans .env.example. Active ce que tu as, ignore ce que tu n’as pas. Tu n’as même plus besoin de copier ce fichier à la main: make bootstrap sème le .env, et toutes les autres cibles le sèment d’abord, donc make run sur un clone tout neuf marche simplement.
Quand tu veux changer quelque chose à la stack elle-même plutôt qu’un flag, mets-le dans docker-compose.override.yml. C’est la couture supportée maintenant: le Makefile assemble COMPOSE_FILE à partir du fichier de base, de l’overlay tailscale quand tu l’as activé, et de ton override en dernier. Édite directement le fichier compose de base et la prochaine mise à jour te mange tes changements; l’override survit.
Si les ressources comptent, et sur un ordinateur normal elles comptent, make limits lit la RAM et le CPU dont tu disposes et écrit des limites recommandées pour chaque service. MAXUSE=80 make limits plafonne toute la stack à 80% des ressources système si tu partages la machine avec d’autres charges. Les services CUDA connaissent le gestionnaire de ressources, donc le budget compte le plus gros d’entre eux, pas chaque service GPU à pleine allocation en même temps.

# free tier, auto-fallback
curl http://localhost:4000/chat/completions 
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" 
  -d '{"model": "cerebras-gpt-oss-120b", "messages": [{"role":"user","content":"hello"}]}'
# local, no network, no limits
curl http://localhost:4000/chat/completions 
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" 
  -d '{"model": "local-ollama-cpu-llama3.2-3b", "messages": [{"role":"user","content":"hello"}]}'
# image generation
curl http://localhost:4000/images/generations 
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" 
  -d '{"model": "hf-flux-schnell", "prompt": "a cat riding a skateboard"}'
# local image generation (no network, no cost)
curl http://localhost:4000/images/generations 
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" 
  -d '{"model": "local-sdcpp-cpu-sd-turbo", "prompt": "a red panda in a forest", "size": "512x512"}'
# transcription (local, CPU)
curl http://localhost:4000/audio/transcriptions 
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" 
  -F "model=local-talkies-whisper-large-v3-turbo" -F "[email protected]"
# text-to-speech (local, multiple voices)
curl http://localhost:4000/audio/speech 
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" 
  -d '{"model": "local-talkies-kokoro-tts", "input": "Hello world", "voice": "af_heart"}' 
  -o speech.mp3
# web search (no API key, self-hosted)
curl http://localhost:4000/chat/completions 
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" 
  -d '{"model": "groq-qwen3-32b", "messages": [{"role":"user","content":"search the web for latest rust release notes"}]}'
# async — submit and poll
curl http://localhost:4000/q/v1/chat/completions 
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" 
  -d '{"model": "cerebras-gpt-oss-120b", "messages": [{"role":"user","content":"write a novel"}]}'

Cinq fournisseurs avec offre gratuite, cinq moteurs locaux, le reste en repli. Plus lent sur un ordinateur normal, mais ça tourne, c’est privé, et personne ne peut te virer de ta propre infrastructure avec une limite de débit.
github.com/psyb0t/aigate

Comment l’Installer dans Ton Agent

Ce skill n’est plus réservé à OpenClaw. Tout ce qui est sous .agents/ est catalogué dans un seul marketplace, donc ça fait deux commandes:

claude plugin marketplace add psyb0t/agents
claude plugin install aigate@psyb0t

Codex utilise le même marketplace avec un autre verbe, codex plugin add aigate@psyb0t, parce que codex plugin install n’existe pas. Il trouve aussi le skill tout seul dans un checkout du dépôt, vu qu’il scanne .agents/skills/ nativement sans rien avoir installé du tout.