Een 3060 en nul cloudbudget. AI-modellen achter één endpoint: vijf cloudaanbieders met een gratis niveau, vijf lokale engines die op je eigen hardware draaien, de rest op abonnement of per token als laatste redmiddel.
Niet in theorie. Nu meteen. aigate is een Docker-stack achter één nginx-poort. Tekstgeneratie, beeldgeneratie, spraaksynthese, transcriptie, zoeken op het web, browserautomatisering, object storage, agentische code-uitvoering, afgeschermde code-uitvoering in meerdere talen, een mailgateway, je Telegram-account als MCP-tool, tijdreeksvoorspelling, audioproductie, een videogereedschapskist met lipsync, een asynchrone jobwachtrij en een webinterface. Op hardware die minder kost dan één maand OpenAI-API-rekening.
Het hele ding is OpenAI-compatibel. Richt welke client dan ook op http://localhost:4000 en het werkt. Bestaande code, bestaande SDK’s, bestaande tools: die praten er allemaal tegen alsof het OpenAI is. Dat is het niet. Het is van jou.
Modellen, de meeste gratis
Vijf aanbieders, Groq, OpenRouter, HuggingFace, Mistral, Cohere, hebben een gratis niveau. Geen creditcard om te beginnen. Maar laten we eerlijk zijn over wat “gratis” betekent, want de marketingpagina’s zijn dat niet: gratis betekent begrensd in snelheid en afgetopt, niet onbeperkt. Groq geeft je 30 RPM en ergens tussen 1K en 14,4K verzoeken per dag, afhankelijk van het model. OpenRouter is 50 verzoeken per dag bij 0 dollar, 1K per dag als je ooit 10 dollar hebt ingelegd. HuggingFace reikt je 0,10 dollar aan tegoed per maand aan, oftewel een testbudget, geen niveau. De proef van Cohere is 1K aanroepen per maand en die is snel op. Het “Experiment”-niveau van Mistral publiceert helemaal geen precieze limieten. Cerebras stond op deze lijst. Nu niet meer: bij de laatste controle antwoordde elk model daar met “payment required”, dus het zit als betaalde aanbieder met drie modellen in de stack, niet als gratis niveau. Dat is het andere met gratis niveaus, ze worden ingetrokken, en een stack die op precies één ervan staat is een stack die breekt op de dag dat die verdwijnt.
Dus nee, je draait geen bedrijf op gratis tokens. Maar je draait er wel je eigen werk op, en als de een opdroogt neemt de volgende het over. Daar gaat het om.
Nog drie, claudebox, pibox-zai en pibox, zijn agentcontainers. De eerste twee draaien op abonnementen, wat niet hetzelfde is als gratis: je tegoed wordt evengoed geteld, je betaalt het alleen per maand in plaats van per token. Anthropic en OpenAI zitten in de stack maar zijn laatste redmiddel. Vijf lokale engines draaien op je eigen hardware: geen netwerk, geen snelheidslimieten, helemaal geen kosten.
De routeringsfilosofie is simpel: betaal nooit voor een token die je gratis had kunnen krijgen.
LiteLLM regelt dat. Je vraagt groq-llama-3.3-70b. Groq knijpt je af. LiteLLM valt geruisloos terug op cerebras-gpt-oss-120b. Cerebras ligt eruit. Valt terug op mistral-small. Mistral antwoordt. Jij krijgt je antwoord. De client heeft nooit geweten dat er iets gebeurde. Elk model heeft zijn terugvalketen: eerst gratis cloud, dan abonnement, dan betalen per token, dan lokaal. De keten wordt bij elke start van de stack opnieuw gebouwd, gefilterd op alleen de aanbieders die je echt hebt aangezet.
groq-llama-3.3-70b → 429 rate limited
↓ fallback
cerebras-gpt-oss-120b → 503 unavailable
↓ fallback
mistral-small → 200 ✓Je hebt geen retry-logica geschreven. Je hebt geen terugvallogica geschreven. Je stuurde één verzoek en kreeg één antwoord.
De architectuur
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)Alles is opt-in. Je zet vlaggen om in .env. Geen Anthropic-key? Zet hem niet. Alleen een CPU? Sla de CUDA-vlaggen over. De stack past zich aan wat je hebt en bouwt zijn config daarop opnieuw. De kern die altijd draait bestaat uit vijf dingen: nginx, LiteLLM, PostgreSQL, Redis, proxq. De rest is een vlag.
Gereedschap dat elk model kan aanroepen
Dit is wat aigate meer maakt dan een proxy met een terugvalketen. Een hele berg MCP-servers, tientallen tools. Elk model met function calling kan ze allemaal zelfstandig aanroepen. Jij prompt. Het model bepaalt welke tools het nodig heeft. Jij krijgt resultaten.
De concrete gang van zaken: je stuurt een prompt naar Groq, gratis, snel. Het model bepaalt dat het iets moet uitzoeken. Het roept search_web aan, SearXNG bevraagt Google, Bing en DuckDuckGo tegelijk en geeft resultaten terug. Het model wil meer detail over één resultaat. Het roept de browsertool aan. Een Camoufox-instantie opent een echte Firefox, beweegt een echte muiscursor, laadt de pagina, haalt de inhoud eruit. Het model leest het, besluit het resultaat te bewaren. Roept de storage-tool aan. hybrids3 schrijft het bestand en geeft een publieke URL terug. Het model bepaalt dat het een afbeelding nodig heeft voor het rapport. Roept generate_image aan. stable-diffusion.cpp rendert die lokaal, zet hem in de opslag, geeft een URL terug. Het model overhandigt je een gestructureerd antwoord met links naar alles wat het heeft gemaakt. Eén API-aanroep. Nul betaalde tokens. De client zag één verzoek en één antwoord.
stealthy_auto_browse
Vijf Camoufox-replica’s achter een HAProxy. Camoufox is een dichtgetimmerde Firefox: echte muis- en toetsenbordinvoer op OS-niveau via PyAutoGUI, nul CDP-blootstelling, blijvende vingerafdrukken per sessie. Komt langs Cloudflare. Langs CreepJS. Langs BrowserScan. Langs Pixelscan. Niet “komt er meestal langs”, maar komt er echt langs, omdat hij niet als automatisering te herkennen is op de manieren waarop die systemen kijken.
Eén tool: run_script. Scripts met meerdere stappen: navigeren, klikken, typen, uitlezen, screenshot maken, scrollen, op elementen wachten, JavaScript uitvoeren. Het model schrijft het script, de browser voert het uit, jij krijgt gestructureerde data terug uit de levende pagina.
hybrids3
S3-compatibele object storage die lokaal draait. De uploads-bucket is publiek leesbaar: bestanden zijn via een directe URL bereikbaar, zonder ondertekening. Voorgetekende PUT-URL’s voor directe uploads. Automatisch verlopen. Tools voor put, get, list, delete, info, presign en buckets opsommen.
Dat lost een specifiek probleem in agentische workflows op. Als een model iets groots oplevert, een gescrapete dataset, een gegenereerde afbeelding, een gerenderd rapport, prop je dat niet in het contextvenster. Je zet het in de opslag, haalt een URL op, geeft de URL door. De volgende stap kan hem ophalen. Jij ook. Het contextvenster blijft schoon.
Drie code-agents: claudebox, pibox-zai en pibox
Drie verschillende agentische code-agents, niet drie kopieën van dezelfde. claudebox draait Claude Code op je abonnement of je API-key. pibox-zai draait pi-coding-agent, gericht op z.ai voor de GLM-modellen (glm-5.3 en glm-5.3-flash). pibox is dezelfde agent, teruggericht op de LiteLLM van deze stack zelf, dus hij programmeert op alles wat je toch al hebt aangezet: je noemt de modellen met PIBOX_MODELS, kiest een standaard met PIBOX_DEFAULT_MODEL. Twee kanttekeningen daarbij, en ze bijten allebei: een model dat je opsomt moet tool calling echt ondersteunen, anders heeft de agent niets om aan te sturen, en geef een agent niet zijn eigen model terug in de lijst tenzij je van recursie houdt. Alle drie krijgen volledige shelltoegang, blijvende workspaces, bestands-I/O en gebruik van tools, en alle drie bieden een REST-API, een OpenAI-compatibel endpoint en een MCP-server.
Je gebruikt Groq voor de snelheid. Groq stuit op iets dat serieus programmeerwerk vraagt. Groq roept de claudebox-tool aan. Claude Code pakt het op, krijgt een shell, schrijft code, draait tests, geeft gestructureerde resultaten terug. Terug in de context van Groq. De orkestratie gebeurt binnen de function-calling-lus van het model, en jij hebt niets van die logica geschreven.
piston: code-uitvoering in een sandbox
LLM’s zijn rampzalig slecht in rekenen, hashen en parsen. Laat ze dus niet gokken. execute_code draait echte code in een nsjail-sandbox, met een eigen user namespace, chroot, seccomp-filter, cgroup-limieten en geen netwerk, en geeft het model een deterministisch resultaat.
De standaardinstallatie is Python plus Node. Je voegt Bash, Deno, Go, Rust, TypeScript of veertig andere talen toe via PISTON_LANGUAGES en bouwt opnieuw. Heeft het model een SHA-256 nodig, een verschil tussen twee datums, een regex over 10K rijen? Het schrijft code, piston draait die in de cel, en het getal komt correct terug in plaats van gehallucineerd.
mcp_tools
generate_image, generate_tts, search_web en execute_code. Gaan vanzelf aan zodra de bijbehorende backend draait. Ze geven allemaal gestructureerde JSON terug, en gegenereerde bestanden gaan automatisch naar hybrids3 met blijvende URL’s. Geen base64-brokken in het contextvenster.
Beeldgeneratie loopt via FLUX, DALL-E of stable-diffusion.cpp, afhankelijk van wat aanstaat. TTS loopt via Kokoro (CPU plus CUDA), Qwen3-TTS met stemklonen, stemontwerp en emotiesturing (CUDA), of OpenAI TTS. Zoeken op het web bevraagt SearXNG. Code-uitvoering gaat naar piston. De tools ontdekken dynamisch bij LiteLLM welke modellen er zijn, dus ze weerspiegelen altijd wat er echt draait.
telethon
Je echte Telegram-account als tool. Niet de Bot-API maar volledig MTProto, precies de toegang die je op je telefoon hebt. Berichten lezen, versturen, gesprekken opsommen, groepen beheren, inhoud doorsturen, bewerken, verwijderen, als gelezen markeren, bestanden sturen. Het model bepaalt wanneer. De agent handelt namens jou.
Een Groq-model op het gratis niveau dat het web doorzoekt, een pagina scrapet, een afbeelding maakt en het resultaat naar je opgeslagen berichten stuurt: nul betaalde tokens, één gesprek, meerdere tool-aanroepen. Of een cronjob die de laatste 24 uur van je werkgroep samenvat en je die elke ochtend privé toestuurt. Je eigen account, programmeerbaar.
Aangedreven door telethon-plus. Je zet TELETHON=1 om, vult TELETHON_API_ID / TELETHON_API_HASH / TELETHON_SESSION in .env in. De sessiestring is volledige toegang tot het account, dus behandel hem als het wachtwoord dat hij in de praktijk is.
mailbox: e-mail als tool
Toestandsloze IMAP plus SMTP over N accounts vanuit één YAML-config. Verenigde inbox, list/search/CRUD per account, versturen via SMTP. REST-API plus een platte set MCP-tools waarin een mailbox-parameter het account kiest.
Wat betekent dat een model je inbox kan lezen, de factuur kan vinden, het totaal eruit kan halen en kan antwoorden, zonder dat jij één regel IMAP-afhandeling schrijft. MAILBOX=1, je richt MAILBOX_CONFIG op je YAML, klaar.
predictalot: voorspellen en tabulaire ML
Vijf zero-shot foundation-modellen voor tijdreeksen, chronos-2, timesfm-2.5, moirai-2, toto-1, sundial-base-128m, over zes soorten voorspelling (univariaat, multivariaat, covariaten uit het verleden, covariaten uit de toekomst, samples, en per soort gewogen ensembles) op /v1/timeseries/<type>/….
Daarnaast een hele zusterfamilie op /v1/tabular/*: negen gesuperviseerde backends (lightgbm, xgboost, hist-gbt, random-forest, logistic, mlp, svm-rbf, knn, naive-bayes) en drie meta-learners (calibrated, stacking, diversified). 26 MCP-tools dekken de foundation-modellen; het tabulaire deel is alleen REST. CPU of CUDA.
Zero-shot betekent zonder trainingsronde. Je geeft het een reeks, het voorspelt. Meer niet.
audiolla: audioproductie
Deze is absurd van omvang. Stems scheiden (Demucs / UVR). Herstel: galm eruit, echo eruit, ruis eruit. Masteren via matchering en pedalboard-ketens, met samengestelde presets als master-for-spotify, podcast-cleanup, vocal-cleanup. MIR-analyse: BPM, toonsoort, LUFS, beats, onsets, melodie, akkoorden, segmenten. DSP-bewerkingen via sox en ffmpeg. Loudness normaliseren. Spraakverbetering (DeepFilterNet). VAD (silero). Diarisatie (pyannote). CLAP-embeddings en zero-shot audioclassificatie. AudioSet-tagging. Audio→MIDI via basic-pitch, plus MIDI componeren / inspecteren / transformeren / renderen via fluidsynth.
Plus tekst-naar-audio-generatie, stable-audio-open, musicgen, riffusion, audioldm2, alleen op CUDA.
Asynchrone jobs en webhooks overal. Het contract vanaf v1.0 is streng: JSON-body op elk audio-endpoint, ruwe bytes alleen op PUT /v1/files/{path}, en output_path xor output_url verplicht bij alles dat audio oplevert.
flickies: video en lipsync
Lipsync via LatentSync 1.5 (ByteDance, Apache-2.0, ~8 GB VRAM, de standaard op CUDA) en Wav2Lip / Wav2Lip-GAN (LRS2, niet-commercieel, met beperkte toegang). Gezichtsherstel via GFPGAN v1.4. ffmpeg-bewerkingen: knippen, aan elkaar plakken, transcoderen inclusief gif-, fps- en codecwissels, schalen, audio muxen, audio eruit halen, rooster met thumbnails. ffprobe-info. Asynchrone jobs en webhooks. 11 MCP-tools, hetzelfde contract met JSON-body en output_path xor output_url als audiolla.
GFPGAN en LatentSync 1.5 draaien alleen op CUDA. De CPU-image doet nog steeds de ffmpeg-bewerkingen en een loeitrage Wav2Lip.
Lokale inferentie: trager, maar van jou
Vijf lokale engines. Stuk voor stuk hebben ze een CPU-modus.
Dit is het gedeelte “computer van de armen”. Je oude laptop met 16GB RAM kan tekstgeneratie, beeldgeneratie, transcriptie en spraaksynthese draaien. Geen API-key. Geen netwerk. Geen snelheidslimiet. Geen rekening. Het is trager dan de cloud. Soms veel trager. Maar het is van jou, het is privé, en het werkt offline.
Ollama: llama3.2:3b, qwen3:4b, smollm2:1.7b, qwen2.5-coder:1.5b, qwen2.5-coder:3b, phi4-mini (redeneren, 128K context), gemma4:e2b (visie), gemma3:4b (visie), nuextract-v1.5 (gestructureerde tekst naar JSON), dolphin-phi, plus twee embeddingmodellen voor RAG. De kleinste heeft 1GB RAM nodig. OLLAMA_CUDA=1 legt de zware set erbij: qwen3:8b, gemma4:e4b (visie), deepseek-coder-v2:16b (MoE, 160K context), deepseek-r1:8b (redeneren), qwen3-abliterated:16b (ongecensureerd), gemma4-abliterated:e4b (ongecensureerde visie), qwen2.5-coder:7b, llama3.1:8b. Flash attention, gekwantiseerde KV-cache, gedeelde modelopslag met de CPU-dienst, dus geen dubbele downloads.
talkies: de verenigde ASR- plus TTS-dienst, en de vervanger van wat vroeger Speaches heette in deze stack. De CPU komt inmiddels met 13 modellen: whisper-large-v3 en -turbo, canary-180m-flash, nemotron-3.5-asr-0.6b, vier Sherpa-ONNX Zipformer-varianten, Vosk small English, en twee foneemherkenners die je in plaats van woorden een ruwe IPA-klankstroom geven met een tijdstempel per klank; Kokoro voor TTS. Op CUDA loopt het op tot 22, met daarbij parakeet-tdt-0.6b-v3, canary-1b-flash, canary-qwen-2.5b, Chatterbox Turbo en de Qwen3-TTS-familie, 0.6B en 1.7B, met stemklonen, stemontwerp en emotiesturing via het veld instructions. Chatterbox is de enige die zijn uitvoer überhaupt van een watermerk voorziet: het neurale PerTh-watermerk van ResembleAI, standaard aan, en TALKIES_CHATTERBOX_WATERMARK=false zet het uit voor schone audio, terwijl Kokoro en Qwen3-TTS er nooit iets in stoppen. OpenAI-compatibele endpoints, dus je bestaande Whisper-aanroepen werken ongewijzigd. TALKIES=1 / TALKIES_CUDA=1.
De toevoegingen van Sherpa en Vosk landen als tien nieuwe transcriptie-aliassen: de vijf modellen onder local-talkies-, en daarna dezelfde vijf nog eens onder local-talkies-cuda-. De int8-builds zijn gekwantiseerd (kleiner en sneller, iets minder nauwkeurig); left-64 tegenover left-128 bepaalt het linker contextvenster van de attention. Alle vijf doen native live ASR over de streaming-WebSocket, naast de gewone POST /v1/audio/transcriptions, en de CUDA-image installeert een op hash gecontroleerde Sherpa CUDA-wheel van upstream, zodat ze de CUDA execution provider daarvan ook echt gebruiken in plaats van binnen een GPU-container stilletjes op de CPU terug te vallen.
Eén terugvalsubtiliteit die het stelen waard is: zes ketens in fallbacks.json zetten vroeger een broer-model van talkies vooraan, whisper-large-v3 → whisper-large-v3-turbo, en de equivalenten voor de CUDA-variant en beide TTS-ingangen. Dat kan nooit werken. Elke talkies-container bedient één model tegelijk en gooit bij toelating het vorige eruit, dus een sprong naar een broer vindt geen vrije capaciteit maar dwingt een modelwissel af, en onder retries is dat gewoon eruit gooien in een lus. De ketens gaan nu eerst naar de andere talkies-container en daarna naar buiten, naar de cloudaanbieders. De sprongen CPU↔CUDA blijven, want dat zijn echt losse processen.
stable-diffusion.cpp: lokale beeldgeneratie. De CPU draait sd-turbo en sdxl-turbo uit de doos. SDCPP_CUDA=1 voor hardwareversnelling en de volledige set: sd-turbo, sdxl-turbo, sdxl-lightning, flux-schnell, juggernaut-xi. Modellen worden bij het eerste gebruik gedownload en lokaal bewaard. Het OpenAI-compatibele endpoint /images/generations betekent dat bestaande code gewoon werkt.
vLLM: lokale tekst-LLM’s plus embeddings. qwen3-0.6b voor generatie, nomic-embed-v2 voor embeddings. VLLM=1 / VLLM_CUDA=1.
llama.cpp: GGUF-modellen en visie-VLM’s, waaronder Surya OCR 2 voor het begrijpen van documenten. LLAMACPP=1 / LLAMACPP_CUDA=1.
Lokale modellen staan standaard achteraan in de terugvalketen. Valt de cloud weg? Lokaal neemt het over. Of je richt er direct op: "model": "local-ollama-cpu-llama3.2-3b". Nul netwerk. Nul kosten. Trager, maar het antwoordt.
Eén GPU, alles
Hier is het technische probleem: je hebt één GPU. Ollama wil VRAM voor het LLM. sd.cpp wil VRAM voor beeldgeneratie. talkies wil VRAM voor transcriptie en spraak. audiolla wil het voor het scheiden van stems. flickies wil er zo’n 8GB van voor LatentSync. Laad ze allemaal en je loopt in OOM.
De resource manager lost dat vanzelf op. Een LiteLLM-callback dwingt wederzijdse uitsluiting per stuk hardware af: één CUDA-job tegelijk. Komt er een verzoek om beeldgeneratie terwijl er een LLM geladen is, dan pakt de manager de semafoor, lost het LLM en laat daarna de beeldgeneratie door. Komt daarna een TTS-verzoek, dan lost hij eerst de beeldgenerator. Dezelfde logica op de CPU.
Elke dienst heeft zijn eigen API om te lossen en de resource manager kent ze allemaal: Ollama neemt keep_alive: 0, sd.cpp heeft POST /sdcpp/v1/unload, talkies / vllm-cuda / llamacpp-cuda nemen DELETE /api/ps/{model_id} per model of POST /unload om te slopen wat er geladen is, en audiolla heeft POST /v1/unload om alle geladen engines in één klap eruit te gooien.
Er zijn ook endpoints voor de beheerder, als je het met de hand wil leegvegen: POST /v1/unload/cuda waaiert gelijktijdig uit naar ollama-cuda, sdcpp-cuda, talkies-cuda, vllm-cuda, llamacpp-cuda, audiolla-cuda en flickies-cuda. POST /v1/unload/cpu doet hetzelfde voor diezelfde zeven aan de CPU-kant. POST /v1/unload draait beide achter elkaar en geeft je per dienst een verslag van wat er daadwerkelijk uit is gegooid.
Jij beheert hier niets van. Jij stuurt verzoeken. Het platform jongleert zelf met het VRAM. De enige prijs is vertraging: het eerste verzoek na een wissel bevat de laadtijd van het model. Daarna gaat het snel, tot de inactiviteitstimeout het model lost om geheugen vrij te maken voor het volgende.
Zoeken op het web
SearXNG op /searxng/. Zelf gehoste metazoekmachine: bevraagt Google, Bing, DuckDuckGo en Wikipedia tegelijk. Geen API-key. Draait volledig lokaal.
Repareer je secret_key als je dit vroeg hebt uitgerold. Die van SearXNG stond hardgecodeerd in de meegeleverde searxng/settings.yml. Hij wordt nu per uitrol gegenereerd en gelezen uit SEARXNG_SECRET_KEY in .env: zet hem met openssl rand -hex 32 wanneer je SEARXNG=1 omzet, anders komt SearXNG op met de placeholder van upstream. De meegeleverde settings.yml is helemaal verdwenen; de instellingen worden gerenderd uit een searxng_config-item in het configs:-blok, net zoals proxq_config al ${REDIS_PASSWORD} ophaalde. De blootstelling was beperkt, /searxng/ zit achter de adminauthenticatie van nginx, de container publiceert geen poorten op de host, en limiter: false betekende dat de sleutel geen snelheidslimieten bewaakte, maar de oude waarde ligt nog steeds in de git-historie, en dat deel ont-gebeurt niet meer.
De MCP-tool search_web betekent dat elk model met function calling zelfstandig op het web kan zoeken. Het model bepaalt dat het iets moet opzoeken, roept de tool aan, krijgt resultaten en redeneert door. Jij hebt geen zoekintegratie gebouwd. Jij hebt SEARXNG=1 omgezet.
LibreChat: de dagelijkse werkbank
Alles hierboven werkt vanaf de API. Maar voor dagelijks gebruik is er LibreChat op /librechat/.
Alle modellen in de keuzelijst. Alle MCP-tools aangesloten. Kies een model, begin te praten. Het model kan nog steeds zelfstandig de browser, de opslag, de code-agents, beeldgeneratie, TTS en het zoeken op het web aanroepen: alles wat via de API kan, kan ook in de interface. Gespreksgeschiedenis op MongoDB. Bestanden uploaden. Streaming over WebSocket.
De eerste geregistreerde gebruiker wordt beheerder. Zet daarna LIBRECHAT_ALLOW_REGISTRATION=false en jij bent de enige binnen.
Aanzetten: LIBRECHAT=1 in .env.
Asynchrone wachtrij
Lange inferentieverzoeken lopen in een timeout. Ga naar /q/ in plaats van / en het verzoek belandt in een wachtrij op Redis. Je krijgt direct een job-ID terug. De echte inferentie draait op de achtergrond. Je vraagt de status op en haalt het resultaat op als het klaar is.
# 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"Gelijktijdigheid, bewaartermijn, timeouts, retries en antwoordcaching, allemaal instelbaar. Alleen de paden van de OpenAI-API gaan door de wachtrij; health checks en beheerdersverzoeken gaan er rechtstreeks langs.
Hij levert zijn eigen agent skill mee
De repo draagt .agents/skills/aigate/ bij zich, een op ClawHub gepubliceerde agent skill die een code-agent leert hoe hij de stack bestuurt. Dus in plaats van je eigen infrastructuur elke sessie aan een assistent uit te leggen, installeer je de skill en kent hij de endpoints, de vlaggen en de toolcontracten al.
Beveiliging
De interne diensten, PostgreSQL, MongoDB, Redis, het browsercluster, de opslagbackend, hebben geen poorten op de host. Ze hangen in afgeschermde Docker-netwerken. Van buiten de stack komt er niets bij. Het enige blootgestelde oppervlak is nginx op poort 4000, en dat vraagt authenticatie met een bearer token.
De applicatiecontainers draaien met no-new-privileges:true. Twee uitzonderingen, allebei bewust en allebei vastgelegd in het compose-bestand: piston heeft privileged: true nodig omdat nsjail per uitvoering user namespaces en chroots moet bouwen, de echte afscherming zit binnen het nsjail-subproces, en de bevoorrechte container is precies wat nsjail nodig heeft om die cel op te trekken. tailscale heeft NET_ADMIN nodig voor zijn tun-apparaat in userspace. Al het andere laat rechten vallen. make run controleert voor het starten of bestandspaden in .env ook echt bestaan, dus geen stilletjes kapotte volume-mounts.
Wil je hem publiek bereikbaar zonder een poort in de firewall open te zetten? CLOUDFLARED=1, oftewel Cloudflare Tunnel. DDoS-bescherming, TLS-terminatie, geen open poorten, geen IP om te scannen. Een snelle tunnel voor een willekeurige *.trycloudflare.com-URL, of een benoemde tunnel voor een vast domein.
Wil je hem helemaal niet publiek, maar er wel bij vanaf je laptop, je telefoon of een andere bak? TAILSCALE=1 met TS_AUTHKEY=tskey-auth-... en een TS_HOSTNAME. Een Tailscale-sidecar sluit zich aan bij je tailnet en draait tailscale serve in L4 TCP-doorstuurmodus rechtstreeks naar nginx:4000: geen matchen op de Host-header, geen FQDN-configuratie aan de tailscale-kant, geen automatisch HTTPS-certificaat (de TLS-terminatie zit, als je die wil, in nginx). nginx krijgt de oorspronkelijke bytes van het verzoek ongewijzigd binnen en routeert ze door zijn bestaande vhost- en padlogica. Toegang via http://<TS_HOSTNAME>.<tailnet>.ts.net, en kaal HTTP kan hier prima, omdat WireGuard binnen het tailnet elke byte al versleutelt. Werkt met gehost Tailscale of met zelf gehoste Headscale (je gebruikt TS_EXTRA_ARGS=--login-server=...). De staat blijft bewaard in .data/tailscale/, dus na een herstart wordt de bestaande login hergebruikt. Combineer het met de authenticatie op bearer token en je hebt twee volledig losse lagen toegangscontrole.
Dat is het verkeer dat naar binnen gaat. Het verkeer dat naar buiten gaat is een aparte zaak die je nu kunt aanzetten: tailnet-egress voor de code-agents, zodat claudebox, pibox-zai en pibox allemaal bij machines in je tailnet kunnen en niet alleen bij het publieke internet. Het komt als compose-overlay (docker-compose.tailscale.yml) en vraagt twee waarden extra, TS_MAGICDNS_SUFFIX (het MagicDNS-achtervoegsel van je tailnet, zo uit tailscale status) en TS_FALLBACK_DNS (standaard 1.1.1.1). Die tweede bestaat omdat het DNS uiteindelijk gesplitst raakt: MagicDNS beantwoordt tailnet-namen en geeft SERVFAIL terug voor al het andere, dus publieke namen hebben een resolver nodig om op terug te vallen.
Honderden tests. Health checks, routering, authenticatie, validatie van MCP-tools, CRUD op de opslag, browserautomatisering, agentische coderondes, de levensloop van asynchrone jobs, lokale TTS/STT heen en terug, verificatie van de CUDA-resource manager, lokale beeldgeneratie, tool calling van begin tot eind van LLM naar MCP, zoeken met SearXNG, MTProto heen en terug met Telethon tegen een echt account. Plus beveiliging: isolatie tussen tokens, pogingen tot sessiekaping, HTTP request smuggling (CL.TE/TE.CL), h2c-smuggling, SSRF via de browser en via MCP naar interne diensten, sleutels onttrekken met prompt injection, path traversal, misbruik van S3-presign, opgeslagen XSS, injectie van modelnamen, header-injectie, isolatie van de Docker-socket. Dit is niet de testsuite van een hobbyproject. Dit is paranoia als functie.
Opzetten
git clone https://github.com/psyb0t/aigate && cd aigate
make bootstrap # seeds .env; then edit it: add keys, flip flags
make run-bgElke variabele staat gedocumenteerd in .env.example. Zet aan wat je hebt, negeer wat je niet hebt. Je hoeft dat bestand ook niet meer met de hand te kopiëren: make bootstrap zaait de .env, en elk ander target zaait hem eerst, dus make run op een verse clone werkt gewoon.
Wil je iets aan de stack zelf veranderen in plaats van aan een vlag, zet het dan in docker-compose.override.yml. Dat is nu de ondersteunde naad: de Makefile stelt COMPOSE_FILE samen uit het basisbestand, de tailscale-overlay als je die hebt aangezet, en jouw override als laatste. Bewerk het basis-composebestand rechtstreeks en de volgende update vreet je wijzigingen op; de override overleeft.
Als resources uitmaken, en op een normale computer doen ze dat, leest make limits je beschikbare RAM en CPU uit en schrijft aanbevolen limieten voor elke dienst. MAXUSE=80 make limits topt de hele stack af op 80% van de systeembronnen als je de machine met ander werk deelt. De CUDA-diensten weten van de resource manager, dus het budget rekent met de grootste ervan, niet met elke GPU-dienst op volle toewijzing tegelijk.
# 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"}]}'Vijf aanbieders met een gratis niveau, vijf lokale engines, de rest als terugval. Trager op een normale computer, maar het draait, het is privé, en niemand kan je met een snelheidslimiet uit je eigen infrastructuur zetten.
github.com/psyb0t/aigate
Hoe je hem in je agent installeert
Die skill is niet langer alleen voor OpenClaw. Alles onder .agents/ staat in één marketplace gecatalogiseerd, dus het zijn twee commando’s:
claude plugin marketplace add psyb0t/agents
claude plugin install aigate@psyb0tCodex gebruikt dezelfde marketplace met een ander werkwoord, codex plugin add aigate@psyb0t, omdat codex plugin install niet bestaat. Hij vindt de skill bovendien zelf in een checkout van de repo, aangezien hij .agents/skills/ native scant zonder dat er iets geïnstalleerd is.