Pour qui : développeurs d'agents, architectes.
1. Trois modèles
| A. Appel par l'API Agent Runtime (outil) | B. A2A via Agent Registry (RemoteA2aAgent) | C. Sous-agent dans le même code | |
|---|---|---|---|
| Principe | L'orchestrateur a un outil qui interroge l'agent distant (runtimes.get(...).async_stream_query) | L'orchestrateur charge l'agent distant comme sous-agent à partir de sa carte A2A trouvée dans le registre | Les deux agents sont dans le même déploiement |
| L'agent appelé doit… | être un agent Agent Runtime (n'importe quel AdkApp) | parler A2A (Agent Runtime A2A, ou Cloud Run to_a2a) | être dans le même dépôt |
| Cross-project | Oui | Oui | Non |
| Droits | roles/aiplatform.viewer sur l'agent appelé | Selon l'hébergement (run.invoker, ou fournisseur d'authentification lié dans le registre) | Aucun |
| Qui garde la main | L'orchestrateur (l'agent distant est un outil) | L'agent distant prend la conversation pendant sa tâche | — |
| Quand l'utiliser | Par défaut entre équipes : simple, marche avec tout agent Agent Runtime existant | Agents d'éditeurs différents, autres frameworks, interopérabilité | Même équipe, même cycle de vie |
Ce projet implémente A (avec découverte par le registre), car c'est celui qui fonctionne avec les agents ADK déjà déployés par les équipes, sans leur demander de changer de protocole.
2. Modèle A, le code (agents/orchestrator/orchestrator_agent/agent.py)
2.1 Trouver l'agent dans le registre de l'autre équipe
@functools.cache # une seule recherche par démarrage
def stock_agent_name() -> str:
if os.getenv("STOCK_AGENT"): # repli : nom passé en dur par Terraform
return os.environ["STOCK_AGENT"]
agents = AgentRegistry(project_id=AGENTS_PROJECT, location=REGISTRY_LOCATION).list_agents().get("agents", [])
match = next((a for a in agents if a.get("displayName") == "stock-agent"), None)
return urn_to_resource(match["agentId"])Un agent Agent Runtime s'enregistre automatiquement dans le registre de son projet. Son identifiant est un URN :
urn:agent:projects-123:projects:123:locations:europe-west1:reasoningEngines:456
↓ urn_to_resource()
projects/123/locations/europe-west1/reasoningEngines/456
Pourquoi passer par le registre : l'équipe Supply peut redéployer ou recréer son agent (nouvel ID) sans que l'équipe Magasins change une ligne. Et un agent absent du registre n'est pas utilisable : la gouvernance est appliquée par construction.
2.2 L'appeler
async def ask_stock_agent(question: str, tool_context: ToolContext) -> dict:
name = stock_agent_name()
_, project, _, location, *_ = name.split("/")
remote = agentplatform.Client(project=project, location=location).runtimes.get(name=name)
session = await remote.async_create_session(user_id=tool_context.user_id)
async for event in remote.async_stream_query(user_id=tool_context.user_id, session_id=session["id"], message=question):
... # on garde le texte des réponses
return {"status": "success", "answer": ...}- L'appel part avec l'identité de l'orchestrateur (son compte de service sur Agent Runtime).
- L'utilisateur final est transmis (
user_id) : l'agent appelé le voit dans ses journaux (traçabilité), mais il n'agit pas avec ses droits. Si l'agent appelé doit accéder à des données au nom de l'utilisateur, il faut OAuth (ressource d'autorisation Gemini Enterprise), pas ce modèle. - Erreur (droit manquant, agent supprimé) → l'outil renvoie
status: error, l'orchestrateur l'explique au lieu de planter.
2.3 Les droits (terraform/20_orchestrator.tf)
# Posé SUR l'agent stock, dans le projet agents : appeler CET agent, et seulement lui.
resource "google_vertex_ai_reasoning_engine_iam_member" "orchestrator_calls_stock" {
provider = google.agents
reasoning_engine = module.stock_agent.name
role = "roles/aiplatform.viewer" # contient reasoningEngines.get + .query
member = module.orchestrator.service_account
}
# Lire le registre du projet agents (pour la découverte).
resource "google_project_iam_member" "orchestrator_reads_registry" {
provider = google.agents
role = "roles/agentregistry.viewer"
member = module.orchestrator.service_account
}Pourquoi un droit sur l'agent et pas sur le projet : aiplatform.user sur le projet permettrait d'appeler (et de
modifier, supprimer, créer) tous les agents de l'équipe Supply. Ici, l'orchestrateur ne peut qu'interroger stock-agent.
2.4 Le prouver
python scripts/test_agents.py # hors ligne : découverte, URN, appel, échec propre
python scripts/query.py orchestrator "Il reste des tentes 8771234 à Lyon ?"
# -> outil : ask_stock_agent(...)
# Preuve négative : retirer le droit sur l'agent (en recette) -> l'orchestrateur répond « Agent stock indisponible : 403 »3. Modèle B, A2A par le registre (variante)
Si l'agent appelé parle A2A et que sa carte est dans le registre, ADK le transforme en sous-agent en une ligne :
from google.adk.integrations.agent_registry import AgentRegistry
registry = AgentRegistry(project_id="acme-agents-supply", location="europe-west1")
stock = registry.get_remote_a2a_agent("projects/…/locations/europe-west1/agents/stock-agent") # -> RemoteA2aAgent
root_agent = Agent(name="assistant_magasin", ..., sub_agents=[stock])- L'authentification est résolue depuis les liaisons (bindings) du registre, qui associent un agent source, une
cible, et un fournisseur d'authentification (
google_agent_registry_bindingen Terraform). - Rendre un agent ADK compatible A2A : sur Cloud Run,
google.adk.a2a.utils.agent_to_a2a.to_a2a(root_agent); sur Agent Runtime, le modèleagentplatform.frameworks.a2a.A2aAgent. - À choisir quand les agents viennent d'éditeurs ou de frameworks différents.
4. Bonnes pratiques entre agents
| Pratique | Pourquoi |
|---|---|
| Une identité par agent, droits sur l'agent appelé uniquement | Un orchestrateur compromis ne compromet pas tout le projet d'en face |
| Model Armor dans chaque agent | La réponse d'un agent est une entrée pour l'autre : une injection peut se propager |
| L'agent appelé reste spécialisé et en lecture | Les actions qui modifient restent avec confirmation humaine (require_confirmation) |
| Descriptions précises | Le modèle choisit l'outil ou l'agent sur la description |
| Délai et erreurs gérés dans l'outil | Un agent lent ou absent ne doit pas bloquer la conversation |
| Sortie par Agent Gateway quand elle est en place | Les appels inter-agents sont journalisés et soumis aux politiques |
| Éviter les cycles (A appelle B qui appelle A) | Coûts et boucles infinies |