C-04 Gestionnaire de sous-agents
Brique C: Agents, autonomie, exécution. Version anglaise: C-04-sub-agent-manager
1. Rôle et responsabilités
Crée des runs enfants à partir d'un run parent, avec limites de profondeur et de nombre et héritage restreint des permissions. Côté moteur, enterprise_ai.subagents.SubagentManager lance les sous-agents en parallèle (run_parallel), suit leur état et injecte leurs résultats dans le tour du parent (inject_results).
2. Position dans l'architecture et interactions
- Enfants distants :
POST /v1/runs/{id}/subagentspuis suivi parGET /v1/runs/{id}; enfants locaux : agents ou fonctions exécutés dans un pool de threads. - Le parent reste maître de la décision : les résultats arrivent dans son contexte au tour suivant.
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.
4. Modèle de données
Tables (schéma eai, RLS tenant_id):
eai.runs(parent_run_id, depth)
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é:
execution-contract - Schéma JSON:
contracts/v1/execution-contract.schema.json - Champs clés:
parent_run_id, depth, budgets - Validation :
POST /v1/contracts/{name}/validate; application d'un manifeste :POST /v1/manifests/apply.
6. Endpoints API
POST /v1/runs/{id}/subagentsGET /v1/runs/{id}/subagents
Requête POST /v1/runs/{id}/subagents:
{
"agent_id": "agt_researcher",
"task": {
"input": "Find similar incidents"
},
"budgets": {
"max_steps": 10
}
}
Réponse:
{
"run_id": "run_01JA",
"parent_run_id": "run_01J9",
"depth": 1,
"state": "queued"
}
7. Exemples SDK
Python:
from enterprise_ai import EnterpriseAI
from enterprise_ai.subagents import SubagentManager
client = EnterpriseAI(base_url="<control-plane-url>", api_key="eai_xxx_secret")
child = client.runs.spawn_subagent("run_01J9", agent="agt_researcher", task="Find the related incidents")
manager = SubagentManager(client=client, max_parallel=4)
results = manager.run_parallel([("agt_researcher", "INC-42 history"), ("agt_billing", "INC-42 invoices")])
TypeScript:
import { EnterpriseAI } from "@enterprise-ai/sdk";
const client = new EnterpriseAI({ baseUrl: "<control-plane-url>", apiKey: process.env.EAI_API_KEY });
const child = await client.subagents.spawn("run_01J9", { agent: "agt_researcher", task: { input: "..." } });
Java:
EnterpriseAIClient client = EnterpriseAIClient.builder()
.baseUrl("<control-plane-url>")
.apiKey(System.getenv("EAI_API_KEY"))
.build();
JsonNode child = client.runs().spawnSubagent("run_01J9", Map.of("agent", "agt_researcher", "task", Map.of("goal", "Collect the sources")));
Go:
c, _ := eai.NewClient(eai.WithBaseURL("<control-plane-url>"), eai.WithAPIKey(os.Getenv("EAI_API_KEY")))
child, _ := c.Runs.SpawnSubagent(ctx, "run_01J9", map[string]any{"agent": "agt_researcher", "task": "Collect the INC-42 timeline"})
8. Configuration
- Variables d'environnement :
EAI_DATABASE_URL - Clés Helm :
controlPlane.config - Côté client :
SubagentManager(max_parallel=..., poll_interval=..., timeout=..., max_depth=..., max_subagents=...)
9. Contrôles de sécurité
- Aucune élévation : l'enfant n'a jamais plus de scopes que le parent
- Budget enfant déduit du budget parent
- 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_subagents_spawned_total,eai_subagent_depth - 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:
run.create
11. Unités FinOps
- Coût enfant agrégé sur le run parent et l'agent racine.
- Émis comme Usage Event (
POST /v1/finops/usage) avec tenant, projet, agent et run.
12. Modes de défaillance et résilience
- Profondeur dépassée : 409
subagent_limit - Annulation du parent propagée aux enfants
- Erreurs normalisées (enveloppe error, message_i18n en/fr)
13. Adoption autonome (client d'un client)
- enterprise_ai.agent + Control Plane.
- SDK:
enterprise_ai.agent(AgentsClient,RunsClient,SessionsClient) - Déploiement minimal : chart Helm avec
controlPlane.enabled=trueet PostgreSQL ; composants externes désactivés utilisent leur repli in-process.
14. Tests d'acceptation
- Lancement à profondeur max + 1 refusé
- Annulation du parent -> enfants
cancelled - Accès inter-tenant refusé
- Erreur renvoyée en fr avec
Accept-Language: fr - Un sous-agent reçoit une délégation restreinte depuis celle de son parent (scopes intersectés avec les siens, classification non supérieure, même expiration au plus, profondeur + 1) : jamais de droits plus larges ; une délégation parente révoquée bloque le lancement suivant et l'étape sensible suivante de chaque descendant (
tests/test_identity_context_delegation.py, voir identity-context-delegation.md)