C-01 Runtime d'agents
Brique C: Agents, autonomie, exécution. Version anglaise: C-01-agent-runtime
1. Rôle et responsabilités
Exécute les agents déclarés par un Agent Manifest selon l'architecture Navin : résolution de la version, prise en charge des messages, tour d'exécution (appel modèle puis appels d'outils jusqu'à la réponse finale), appels modèles via le Model Gateway, appels d'outils via le Tool Gateway, émission des étapes dans le contrat d'exécution.
- Charger l'agent et sa version immuable (
eai.agent_versions) - Démarrer un run (état
pendingpuisrunning) et appliquer les budgets AgentLoop(enterprise_ai.loop) : intake des messages (bus, runs en attente, sessions suivies), contexte, sessions, reprise après crash, arrêt, cron, heartbeat,serve()AgentRunner(enterprise_ai.runtime) : un tour complet avec les règles de continuation (travail restant, l'utilisateur a déjà dit go, vérifier avant de finir)- Gérer
suspend,resumeetkillau niveau agent
2. Position dans l'architecture et interactions
- Appelants : Portail, SDK, CLI, Channel Gateway (D-13), Scheduler (C-09), Event Triggers (C-10).
- Chemin d'exécution : création du run synchrone (
POST /v1/agents/{id}/runs), puis le processusAgentLoop.serve()du client prend le run en attente et l'exécute ; événements SSE surGET /v1/runs/{id}/events. - Le
MessageBus(en mémoire, ouNatsMessageBussur NATS) relie les canaux au moteur : file entrante lue parserve(), file sortante lue par les canaux. - Côté serveur,
python -m eai.workerassure le tick du scheduler, le vidage de l'outbox, l'expiration des quotas, les heartbeats périmés et la rétention. - Toute interaction passe par l'API Control Plane v1 (
/v1) derrière la passerelle API (D-14).
3. Composant open source et partie développée
- Moteur : architecture Navin du SDK Python, sans graphe déclaré.
enterprise_ai.loop.AgentLoopreçoit les messages, prépare le contexte, gère les sessions, reprend un run après un crash, traite l'arrêt, le cron et le heartbeat, et exposeserve();enterprise_ai.runtime.AgentRunnerexécute un tour ;enterprise_ai.subagents.SubagentManagerlance les sous-agents en parallèle ;enterprise_ai.loop.plan.RunPlanporte le plan ;enterprise_ai.runtime.bus.MessageBusrelie canaux et moteur. - Adaptateurs optionnels : LangGraph (
enterprise_ai.runtime.langgraph.build_graph) et Temporal (enterprise_ai.runtime.temporal.build_workflow) réutilisent les mêmes étapes ; ils ne sont pas requis. - Côté serveur,
python -m eai.workerexécute les tâches périodiques : tick du scheduler, vidage de l'outbox d'événements, expiration des réservations de quota, détection des heartbeats périmés, rétention. - Développé :
AgentLoop,AgentRunneret ses règles de continuation (enterprise_ai.runtime.continuation.default_rules),SubagentManager,RunPlan,MessageBus, le domaineservices/control-plane/src/eai/domains/agents(registre, runs, checkpoints, heartbeats, scheduler, triggers).
4. Modèle de données
Tables (schéma eai, RLS tenant_id):
eai.agentseai.agent_versionseai.runseai.run_steps
Colonnes communes : id uuid, tenant_id uuid NOT NULL, created_at, updated_at ; politique RLS current_setting('app.tenant_id', true)::uuid.
5. Contrat
- Contrat appliqué:
agent-manifest+execution-contract - Schéma JSON:
contracts/v1/agent-manifest.schema.json,contracts/v1/execution-contract.schema.json - Champs clés:
spec.runtime, spec.models, spec.tools, spec.permissions, spec.autonomy, spec.budgets; run: state, steps, budgets, retry_policy, trace - Validation :
POST /v1/contracts/{name}/validate; application d'un manifeste :POST /v1/manifests/apply.
6. Endpoints API
GET /v1/agentsPOST /v1/agentsGET /v1/agents/{id}PATCH /v1/agents/{id}DELETE /v1/agents/{id}GET /v1/agents/{id}/versionsPOST /v1/agents/{id}/runsGET /v1/runsGET /v1/runs/{id}GET /v1/runs/{id}/events
Requête POST /v1/agents/{id}/runs:
{
"task": {
"input": "Summarize ticket INC-42"
},
"session_id": "ses_7f2",
"budgets": {
"max_steps": 20,
"max_cost": {
"amount": "0.50",
"currency": "EUR"
}
}
}
Réponse:
{
"run_id": "run_01J9",
"state": "queued",
"agent": {
"id": "agt_support",
"version": 3
},
"trace": {
"trace_id": "4bf92f35..."
}
}
7. Exemples SDK
Python:
from enterprise_ai import EnterpriseAI
from enterprise_ai.loop import AgentLoop
from enterprise_ai.runtime.bus import BusMessage, MessageBus
client = EnterpriseAI(base_url="<control-plane-url>", api_key="eai_xxx_secret")
bus = MessageBus()
loop = AgentLoop(client=client, model="claude-sonnet-eu", agent_id="agt_support", bus=bus,
watch_sessions=["ses_7f2"])
result = loop.run_once("Summarize INC-42", session_id="ses_7f2") # one AgentRunner turn
print(result.stop_reason, result.final_text)
loop.resume("run_01J9") # restart a crashed run from its last checkpoint
# asyncio.run(loop.serve()) # intake: bus, pending runs, watched sessions
TypeScript:
import { EnterpriseAI } from "@enterprise-ai/sdk";
const client = new EnterpriseAI({ baseUrl: "<control-plane-url>", apiKey: process.env.EAI_API_KEY });
const agent = await client.agents.create({ manifest });
const run = await client.agents.run(agent.id, { task: { objective: "Summarize INC-42" } });
Java:
EnterpriseAIClient client = EnterpriseAIClient.builder()
.baseUrl("<control-plane-url>")
.apiKey(System.getenv("EAI_API_KEY"))
.build();
var agent = client.agents().create(manifest);
var run = client.runs().start("agt_support", Map.of("task", "Summarize INC-42"));
Go:
c, _ := eai.NewClient(eai.WithBaseURL("<control-plane-url>"), eai.WithAPIKey(os.Getenv("EAI_API_KEY")))
_, _ = c.Agents.Create(ctx, manifest)
run, _ := c.Runs.Start(ctx, "agt_support", map[string]any{"task": "Summarize INC-42"})
8. Configuration
- Variables d'environnement :
EAI_DATABASE_URL,EAI_HEARTBEAT_STALE_SECONDS,EAI_NATS_URL(bus NATS optionnel),EAI_TEMPORAL_URL(adaptateur Temporal optionnel) - Clés Helm :
controlPlane.replicas,controlPlane.config,worker.enabled,worker.replicas - Côté client : options de
AgentLoop(model,agent_id,bus,watch_sessions,poll_interval,history_limit,context_budget,budget)
9. Contrôles de sécurité
- Scope
agents:runrequis ; relation OpenFGAcan_runsur l'agent - Politique OPA
agent.runévaluée avant démarrage - Secrets jamais injectés dans le prompt ; seulement des leases (D-11)
- Toutes les requêtes portent le tenant issu du credential ; RLS PostgreSQL
- Chaque mutation et décision de politique écrit un événement d'audit chaîné
10. Observabilité
- Métriques Prometheus:
eai_runs_total{state},eai_run_duration_seconds,eai_run_steps_total - Traces: spans OpenTelemetry avec conventions sémantiques GenAI (
gen_ai.operation.name,gen_ai.agent.id,gen_ai.tool.name), attributseai.tenant_id,eai.run_id, propagationtraceparent. - Actions d'audit:
agent.apply,run.create,run.transition
11. Unités FinOps
- Tokens modèles par run, nombre d'étapes, durée worker ; attribution agent_id/run_id dans l'Usage Event.
- Émis comme Usage Event (
POST /v1/finops/usage) avec tenant, projet, agent et run.
12. Modes de défaillance et résilience
- Crash du processus client :
AgentLoop.resume(run_id)relit le dernier checkpoint (GET /v1/runs/{id}/checkpoints) et reprend le tour - Arrêt demandé : le heartbeat répond
action=stopetAgentRunners'arrête avant le prochain appel modèle ou outil ;AgentLoop.shutdown()termine le tour courant avant de sortir Idempotency-KeysurPOSTruns : pas de run en double- Dépassement de budget : arrêt du tour avec la raison de la limite atteinte
- Erreurs normalisées (enveloppe error, message_i18n en/fr)
13. Adoption autonome (client d'un client)
- SDK Python
enterprise_ai.loop+enterprise_ai.runtime+ Control Plane + PostgreSQL ; NATS, LangGraph et Temporal sont optionnels. - SDK :
enterprise_ai.agent(AgentsClient,RunsClient,SessionsClient),enterprise_ai.loop.AgentLoop,enterprise_ai.runtime.AgentRunner - Déploiement minimal : chart Helm avec
controlPlane.enabled=true,worker.enabled=trueet PostgreSQL ; les composants externes non configurés utilisent leur repli in-process.
14. Tests d'acceptation
- Créer un agent depuis un manifeste valide puis lancer un run
- Run refusé sans scope
agents:run(403) - Même
Idempotency-Key-> même run_id - Accès inter-tenant refusé
- Erreur renvoyée en fr avec
Accept-Language: fr