Guide IA · Avancé · VPS Linux / GPU

Faire tourner des agents IA 24/7 sur un VPS : architecture, systemd, supervision

Un agent IA qui travaille pendant que vous dormez, c'est d'abord de l'infrastructure : une file qui retient les tâches, des workers qui ne meurent jamais, un modèle toujours chaud et une supervision qui vous réveille quand ça casse. Voici l'architecture que nous déployons chez nos clients GPU — avec son unit systemd, ses garde-fous de coût et ses limites honnêtes.

1. Ce qu'est un agent en production — définissons honnêtement

Dans la presse, « agent » désigne tout, donc rien. En production, un agent IA est une boucle déterministe : un prompt système qui définit le rôle et les outils, une étape de raisonnement où le modèle décide quoi faire, une exécution d'outil (HTTP, code, SQL, navigateur, script maison), puis retour au modèle avec le résultat, en boucle jusqu'à complétion ou épuisement du budget. Tout le reste — mémoire, planification multi-étapes, sous-agents — est de la composition de cette boucle. Le démon n'est pas magique ; c'est un processus qui bloque sur un appel de modèle, l'exécute, et recommence.

Cette constatation change tout pour l'infrastructure : si un agent est un processus qui consomme des appels LLM et du temps CPU, alors ses besoins se déduisent. Il lui faut une machine toujours allumée, un état persistant entre les redémarrages, un moyen de relancer le processus quand il tombe, et un compte qui borne ses dépenses. Ce sont exactement les quatre points des sections suivantes.

2. L'architecture de référence : file + worker + modèle

Le montage que nous voyons tenir la charge réelle, du prototype solo au parc d'une dizaine de workers, tient en trois briques reliées :

  • La file — un Redis ou une table Postgres qui retient les tâches à traiter. C'est elle qui absorbe les pics : si votre déclencheur produit 2 000 tâches à 3 h du matin, la file les garde et les distribue à votre rythme. Sans file, chaque tâche lance un processus — et la machine meurt de ses propres enfants.
  • Le worker — un processus unique par machine (ou par quelques cœurs) qui pioche dans la file, exécute la boucle agent et rend le résultat. Écrivez-le à l'état pur : s'il crashe au milieu d'une tâche, la file la rend disponible et le worker suivant la reprend. L'idempotence n'est pas un luxe, c'est ce qui vous évite les traitements en double après chaque redémarrage.
  • Le modèle — soit une API externe (OpenAI, Anthropic, Mistral…) appelée par HTTPS, soit votre propre serveur local vLLM/Ollama sur une GPU-1 ou GPU-2. Le worker n'a pas à savoir lequel : il parle le protocole OpenAI dans les deux cas, et vous pouvez basculer d'un fournisseur à l'autre en changeant deux variables d'environnement.

La règle d'or : le worker ne doit jamais être un cron qui lance des scripts, mais un démon qui consomme une file. Le cron lance ; le démon vit. C'est la différence entre une démo qui marche une semaine et une infrastructure qui marche un an.

3. Pourquoi une machine dédiée — et pourquoi offshore

Commençons par la partie qui n'a rien d'idéologique : votre PC n'est pas fait pour ça. Un agent 24/7 suppose une IP stable (les fournisseurs d'API bannissent les plages résidentielles dynamiques à la première vague d'erreurs), une veille qui ne coupe pas les processus, un disque qui tient les journaux, et une consommation électrique qui ne s'additionne pas à votre facture de salon. Un VPS-2 Reef à 11,99 $/mois remplit tout cela avec de la marge ; un laptop ouvert en permanence ne coche aucune de ces cases.

L'argument offshore est plus subtil et mérite d'être dit sans langue de bois. Quand vos agents consomment des API tierces avec des clés liées à un compte nominatif, chaque requête porte votre identité fournisseur — et les rate limits, suspensions et revues de modération s'appliquent à votre compte. Faire transiter vos appels depuis une infrastructure que vous louez sans lien d'identité ne rend pas anonyme la clé API elle-même (le fournisseur la voit toujours), mais découple votre infrastructure d'exécution — IPs, journaux d'accès, données de contexte — de votre identité civile. Pour les charges sensibles (données clients, veille concurrentielle, automatismes internes), c'est une séparation réelle, pas cosmétique.

Le cas limpide est celui du modèle local : déployez vLLM sur une GPU-1 (voir notre guide d'inférence self-hosted) et vos agents n'appellent plus aucun tiers — ni pour le raisonnement, ni pour les embeddings. La boucle complète vit dans votre serveur, sous votre UFW, avec vos règles de rétention. C'est la configuration maximale de contrôle que nous fournissons.

4. Choisir la machine selon la charge

La question « quel serveur pour mes agents » a une réponse honnête : cela dépend de où vit le modèle.

Cas d'usage Plan conseillé Pourquoi
Orchestrateur + API externes (≤ 5 workers)VPS-2 Reef (2 vCPU / 4 Go)La charge est I/O ; 4 Go suffisent pour Redis, le worker et la supervision
Orchestrateur lourd + Postgres + monitoring (10–30 workers)VPS-3 Horizon (4 vCPU / 8 Go)Marges pour la base, Prometheus, les journaux et les pics de file
Modèle local 8–32 B pour la boucle agentGPU-1 RTX 4090 (24 Go)Un 8B en Q8 tient 60+ tok/s par flux — confort pour des boucles courtes
Modèle local 70 B ou plusieurs modèles servisGPU-2 / GPU-348–80 Go de VRAM, parallélisme tensoriel vLLM sur la GPU-2

Le montage fréquent est hybride : un VPS-2 ou VPS-3 fait tourner la file, les workers et la supervision, et appelle un GPU-1 soit sur le même serveur (si vous êtes sur la ligne GPU), soit sur une seconde instance dédiée au modèle. Séparer l'orchestration de l'inférence vous permet de redémarrer, mettre à jour ou remplacer le modèle sans toucher aux tâches en cours — et l'inverse.

5. Le worker : code minimal et unit systemd

Le worker est délibérément court : boucle infinie, brpop bloquant sur la file, exécution bornée, accusé de réception. Python avec redis-py et le client OpenAI — mais n'importe quel langage fait l'affaire si le schéma est respecté.

/opt/agent/worker.py — boucle du worker
#!/usr/bin/env python3 — le squelette à faire vôtre, pas à copier aveuglément
import os, redis
from openai import OpenAI

r = redis.Redis(host="127.0.0.1", port=6379, decode_responses=True)
llm = OpenAI(base_url=os.environ["LLM_BASE_URL"],
             api_key=os.environ["LLM_API_KEY"])

MAX_STEPS = int(os.environ.get("AGENT_MAX_STEPS", "12"))

while True:
    # BRPOP bloque jusqu'à une tâche — aucun polling, aucune charge à vide
    _, payload = r.brpop(["tasks"], timeout=0)
    task = Task.from_json(payload)

    history = [{"role": "system", "content": SYSTEM_PROMPT}]
    try:
        for step in range(MAX_STEPS):
            resp = llm.chat.completions.create(
                model=os.environ["LLM_MODEL"],
                messages=history + task.messages(),
                max_tokens=2048,
            )
            msg = resp.choices[0].message.content
            history.append({"role": "assistant", "content": msg})
            call = parse_tool_call(msg)  # votre grammaire d'outils
            if call is None:
                break
            history.append({"role": "user", "content": run_tool(call)})
        task.complete(history[-1]["content"])
    except Exception as exc:
        task.fail(repr(exc))  # la file gère la reprise

Ce qui rend ce squelette industrialisable tient à quatre choix : la tâche est prise depuis la file puis acquittée (pas l'inverse — sinon une panne entre les deux la perd), les étapes sont bornées (AGENT_MAX_STEPS), le budget est borné (max_tokens à chaque appel), et toute exception rend la tâche à la file plutôt que de la détruire. Ajoutez un timeout sur run_tool — un outil HTTP sans timeout est la source n°1 de workers gelés.

Le service systemd qui ne lâche jamais

Le démon lui-même est trivial à écrire, mais trois directives en font un citoyen de production : Restart=always pour survivre aux OOM et aux plantages de dépendance, MemoryMax pour qu'un worker fuyard ne prenne pas la machine en otage, et EnvironmentFile pour sortir les secrets du code.

/etc/systemd/system/agent-worker.service
[Unit]
Description=Agent IA — worker de file de tâches
After=network-online.target redis-server.service
Wants=network-online.target

[Service]
Type=simple
User=agent
Group=agent
WorkingDirectory=/opt/agent
EnvironmentFile=/opt/agent/.env
ExecStart=/opt/agent/venv/bin/python worker.py
Restart=always
RestartSec=5
MemoryMax=3G
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/opt/agent/data
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
Terminal · activation et contrôle
useradd -r -d /opt/agent agent
chown -R agent:agent /opt/agent

systemctl daemon-reload
systemctl enable --now agent-worker

# Journal en direct : ce que l'agent décide, tape et casse
journalctl -u agent-worker -f

6. Borner le budget : le garde-fou qui compte

L'accident classique de l'agent 24/7 n'est pas un plantage, c'est une boucle. Un prompt qui échoue, un outil qui répond une erreur, le modèle qui réessaie — et à 3 h du matin votre worker a consommé la facture mensuelle d'une API en quatre heures. Trois garde-fous, dans cet ordre d'efficacité :

  • Bornes dans la boucle — étapes maximum, tokens maximum par appel, tokens maximum par tâche, calculés et journalisés. La borne qui n'est pas comptée n'existe pas.
  • Plafond côté fournisseur d'API — configurez un spending limit sur le compte de l'API externe quand l'option existe, et une clé dédiée à l'agent (jamais votre clé personnelle) : une révocation ne coûte alors rien.
  • Circuit-breaker — si le taux d'échec d'outils dépasse un seuil sur N minutes, le worker se met en pause et vous alerte. Un agent qui échoue à 90 % ne répare pas ses outils en tapant plus fort ; il vous attend.

Version locale, le calcul s'inverse agréablement : un GPU-1 à 119 $/mois coûte 3,9 $/jour quel que soit le nombre d'appels. Vos boucles infinies deviennent un problème de latence, pas de facture — mais surveillez quand même la file : une file qui grandit indéfiniment signifie un modèle devenu trop lent pour la charge, et c'est un problème de dimensionnement, pas de garde-fou.

7. Supervision : voir l'invisible pendant que vous dormez

Un agent ne vous parle pas quand ça va mal ; il se tait. La supervision doit donc mesurer trois signaux : le worker vit-il (heartbeat), la file avance-t-elle (longueur, âge de la tête de file), le modèle répond-il (latence, taux d'erreur). Trois sondes, trois alertes, et vous avez couvert 95 % des pannes réelles.

/opt/agent/healthcheck.sh — sonde toutes les 5 min
#!/usr/bin/env bash
# Échoue (exit 1) si le worker est mort, la file bloquée ou le modèle muet
systemctl is-active --quiet agent-worker || exit 1

LLEN=$(redis-cli llen tasks)
[ "$LLEN" -gt 500 ] && exit 1   # file en explosion : consommateur en panne

curl -sf -m 10 "$LLM_BASE_URL/models" -H "Authorization: Bearer $LLM_API_KEY" || exit 1

Poussez le résultat vers un service d'uptime externe (un simple curl vers une URL healthchecks.io fonctionne bien et reste gratuit à petit volume) ou vers votre Prometheus si la stack existe déjà. Sur la ligne GPU, ajoutez nvidia-smi --query-gpu=utilization.gpu,memory.used,temperature.gpu --format=csv,noheader à la sonde : un GPU à 0 % à 14 h signifie que vos agents dorment ou que le worker est mort, et seul le journal tranchera. Enfin, archivez le journal : journalctl -u agent-worker est votre boîte noire quand une décision d'agent devra être expliquée — rétention par défaut d'un mois, ajustez selon vos obligations.


Questions fréquentes

Mes agents ont-ils besoin d'un GPU ?
Non — si le raisonnement est délégué à une API externe. Dans ce cas, l'orchestrateur (file + workers + supervision) tourne très bien sur un VPS-2 Reef ou VPS-3 Horizon, et seule la charge I/O compte. Le GPU devient nécessaire quand vous servez le modèle vous-même : un GPU-1 RTX 4090 pour un 8–32 B quantifié, une GPU-2/GPU-3 pour du 70 B. Beaucoup de clients commencent en API externe sur VPS-2 et ajoutent le GPU-1 le mois où la facture API dépasse les 119 $. C'est le point de bascule le plus rentable que nous observons.
Pourquoi ne pas faire tourner tout ça sur mon PC ou un Raspberry Pi ?
Trois raisons structurelles : l'IP dynamique des connexions résidentielles (les fournisseurs d'API et les services appelés par vos outils bannissent vite les plages grand public), l'absence de continuité (une mise en veille, une coupure, une mise à jour Windows interrompt la boucle et perd les tâches en mémoire), et la consommation (un VPS-2 à 11,99 $/mois coûte moins cher que l'électricité d'une machine toujours allumée, sans compter l'usure). Le Pi souffre en plus de RAM trop juste dès que Redis, le worker et les journaux cohabitent. Votre PC reste le poste de développement ; le VPS est la production.
Comment empêcher un agent de dépenser sans limite ?
En superposant trois garde-fous, jamais un seul : bornes dans la boucle (étapes max, tokens max par appel et par tâche, tout comptabilisé dans le journal), plafond côté fournisseur d'API (spending limit + clé API dédiée à l'agent, révocable sans toucher vos autres usages), et un circuit-breaker qui met le worker en pause si le taux d'échec explose. Avec un modèle local sur GPU-1, le coût devient fixe (3,9 $/jour) et le risque bascule : ce n'est plus la facture qu'il faut surveiller, mais la file — une file qui grandit indéfiniment signale un dimensionnement à revoir.
Que voit VPSPLEX de mes agents et de leurs données ?
Ce que voit tout hébergeur : les métriques hyperviseur (CPU, RAM, réseau, disque) pour opérer la machine — et rien d'autre. Nous n'inspectons pas le contenu de vos VPS, n'accédons pas à vos processus et ne conservons aucun log de vos flux. C'est la promesse no-KYC/no-logs, et elle s'applique aussi aux workloads IA. Une limite, qui n'est pas de la comptabilité : notre AUP interdit phishing, spam, malware et abus — des agents qui enfreindraient ces règles nous exposeraient légalement et seraient suspendus après avertissement, comme n'importe quel usage.
Redis, Postgres ou SQLite pour la file de tâches ?
SQLite suffit largement pour un worker unique et des tâches par centaines — une table, un verrou, zéro service supplémentaire. Choisissez Redis dès que deux workers tournent en parallèle ou que des déclencheurs poussent vite : les listes avec BRPOP sont faites pour ça, et l'opérationnel est trivial. Postgres est le bon choix si la base existe déjà chez vous : SELECT ... FOR UPDATE SKIP LOCKED donne une file robuste et durable dans un système que vous sauvegardez de toute façon. Évitez les files « maison » en mémoire Python : elles meurent avec le processus et vous perdez tout ce qui n'était pas encore traité.

Vos agents peuvent commencer à travailler cette nuit.

VPS-2 Reef à 11,99 $/mois pour l'orchestrateur, GPU-1 RTX 4090 à 119 $/mois pour le modèle local. Payé en crypto, déployé en une minute, sans aucun nom rattaché.

Déployer un VPS