agent-service

le point d’entrée IA du dépôt — un agent de l’équipe, appelable en HTTPOuvrir le chat →

Ce que c’est

Une requête HTTP met un de nos agents au travail : Sinda, Omar, Farah, Dali, SoloBro ou Control. Le service ne fait pas un appel d’API — il lance le CLI claudedéjà installé et connecté sur la machine, avec les outils du dépôt. Aucune clé API n’est utilisée : ANTHROPIC_API_KEY est retirée de l’environnement avant le lancement.

Le service écoute sur 127.0.0.1:3060. Publié aussi sur https://ai.offer.brimind.pro, derrière mot de passe.

Démarrer

Le plus court chemin — poser une question et lire la réponse :

curl -X POST http://127.0.0.1:3060/api/ask \
  -H 'content-type: application/json' \
  -d '{"prompt":"Résume ce dépôt en 3 lignes","agent":"control"}'

Les routes

MéthodeRoutePour
GET/api/healthl’état réel : CLI présent + version, roster, file, limites. Un ok:false vient toujours avec un why.
GET/api/agentsle roster, relu dans agents/registry.json à chaque appel
POST/api/askla demande — voir le corps ci-dessous
—/chatla page de chat : on écrit, on joint, on choisit l’agent, on lit

Le corps de /api/ask

{
  "prompt": "obligatoire — la demande",
  "agent": "web-agent (défaut) | control | solobro | … voir /api/agents",
  "sessionId": "s_ab12cd34ef56   — reprend cette conversation (voir plus bas)",
  "timeoutMs": 600000,
  "attachments": [
    { "name": "fiche.txt", "contentType": "text/plain", "text": "…" },
    { "name": "photo.jpg", "contentType": "image/jpeg", "data": "<base64>" }
  ]
}

agent omis → web-agent (Web, l’agent de ce service : il existe pour lui seul, donc un agent d’équipe éteint ne fait pas tomber le point d’entrée). Une pièce jointe porte soit text, soit data en base64. Le nom est ramené à un basename assaini : ../../.ssh/id_rsa devient id_rsa.

Ce qui revient

CodeQuand
200{ok:true, answer, agent, durationMs, sessionId}
400demande invalide : pas de prompt, agent inconnu, pièce jointe hors limites
429la file est pleine — refusé tout de suite, avec le chiffre
502l’agent a échoué, a dépassé le délai, ou n’a rien rendu

Une réponse vide sort en 502, jamais en 200 : un échec doit rester une donnée, jamais un brouillon blanc qui part chez un client.

Les sessions — l’agent se souvient

Chaque conversation est une session. La première réponse rend unsessionId ; renvoyez-le au tour suivant et l’agent reprend là où il s’était arrêté — il ne relit pas votre historique, c’est sa session qui reprend.

# tour 1 — la session s'ouvre
curl -s -X POST http://127.0.0.1:3060/api/ask \
  -H 'content-type: application/json' \
  -d '{"prompt":"On travaille sur le garage Hurley Bros.","agent":"control"}'
# → { "sessionId": "s_ab12cd34ef56", "session": { "was": "new", "turns": 1 } }

# tour 2 — la même conversation, demain si besoin
curl -s -X POST http://127.0.0.1:3060/api/ask \
  -H 'content-type: application/json' \
  -d '{"prompt":"Rappelle-moi son prix.","sessionId":"s_ab12cd34ef56"}'
# → { "session": { "was": "resumed" | "woken", "turns": 2 } }
ÉtatCe que ça veut dire
openutilisée récemment — le prochain tour enchaîne
closedinactive depuis plus de 60 min : fermée, pas supprimée. Redemander avec son id la réveille (was: "woken").
suppriméele magasin était plein : les plus vieilles partent en premier. Son id revient alors en 400 unknown session, jamais en silence.
RoutePour
GET /api/sessionsles conversations en cours, la plus récente d’abord, + l’état du magasin
GET /api/sessions/<id>une conversation avec tous ses tours
DELETE /api/sessions/<id>l’effacer maintenant
—/chat?session=<id> rouvre la conversation à l’écran

Trois garde-fous : on ne reprend qu’une session créée ici (l’id d’une autre machine est refusé, jamais passé à --resume) ; un tour à la fois par session (sinon 409) ; et si le transcript du CLI a disparu, l’agent repart à neuf en le disant (session.warning) plutôt que d’oublier en silence. Un appel purement ponctuel peut passer "session": false et ne rien laisser.

Les agents

chargement…

Depuis un outil du dépôt

Jamais un fetch maison : le client partagé scripts/agent.js porte l’URL, le délai et la forme d’un échec.

const agent = require('../scripts/agent');
const r = await agent.ask('Rédige la relance…', { agent: 'followup-agent' });
if (!r.ok) monFilet(r.error);   // un échec est une donnée, jamais un blanc
else utiliser(r.answer);
node scripts/agent.js health
node scripts/agent.js agents
node scripts/agent.js ask "décris cette image" --agent control --file ./shot.png

Les limites, et pourquoi

LimiteValeurPourquoi
Requêtes simultanées1chaque tour est un Claude Code complet — ce chiffre est une affirmation sur la RAM de cette machine, pas sur le service
File d’attente20une file sans porte transforme une rafale en sockets tenues des heures ; au-delà, 429 immédiat avec le chiffre
Délai d’une demande10 mincouvre l’exécution, pas l’attente en file
Pièces jointes10 · 10 Mo · 25 Movérifié avant tout décodage
Prompt sur stdinpas d’argvLinux plafonne un argument à 128 Ko : passé en argument, un prompt plus long ne démarre pas du tout (Argument list too long). Il part donc sur l’entrée standard du CLI
Magasin de sessions1 Goplein, les sessions les plus anciennes sont effacées en premier — un dossier qui grandit sans fin est un disque qui se remplit à 3 h du matin
Session inactive60 minfermée, pas supprimée : son id la réveille
Prompt100 000 caractères—

Ce que ce service ne fait pas