Une seule politique d'approbation pour tous les sous-agents
Un agent principal livre une fonctionnalité : il lance un testeur et un rédacteur de documentation, attend les deux, puis fait son compte rendu. Leurs outils demandent une approbation. Vous ne voulez pas d'un gestionnaire par agent enfant : une seule politique, définie sur la session de l'agent principal, doit répondre pour toute l'arborescence.
Les agents enfants s'exécutent en tant que sous-agents, chacun dans sa propre session. Leurs demandes d'approbation sont transmises au gestionnaire de la session principale pendant qu'un appel sur cette session reste en attente : nous utilisons d'abord l'appel qui pilote la session, puis un bot qui répond pour une session qu'il ne pilote pas.
Démonstration : sous-agents, demandes d'approbation traitées pour une arborescence de sessions, suivi des événements d'une session, délai d'expiration.
Exécution : placez les blocs de code dans un seul fichier, subagent_approvals.py, et exécutez python subagent_approvals.py
(configuration).
L'agent principal et ses agents enfants
import asyncio
from mistralai import agents
PUBLISHED: list[str] = []
@agents.tool
def run_tests(feature: str) -> str:
"""Run the test suite of a feature."""
return f"{feature}: 128 passed"
@agents.tool
def publish_docs(feature: str, text: str) -> str:
"""Publish the docs of a feature."""
PUBLISHED.append(feature)
return f"published the docs of {feature}"
lead = agents.Agent(
model="mistral-large-4",
name="release-lead",
instructions=(
"You ship features. For each feature, start one tester and one docs writer, "
"wait for both, then say in one line what was done."
),
subagents=[
agents.Subagent(
name="tester",
description="Runs the tests of one feature and reports the result.",
instructions="Call run_tests once, then answer with the result.",
tools=[run_tests],
builtins=agents.Builtins(subagents=False),
),
agents.Subagent(
name="docs-writer",
description="Writes two lines of docs for one feature and publishes them.",
instructions="Write two lines of docs, call publish_docs once, then answer in one line.",
tools=[publish_docs],
builtins=agents.Builtins(subagents=False),
),
],
permission_mode="ask", # the default "auto" asks nothing, so no handler runs
)Un agent enfant hérite de chaque paramètre de l'agent principal qu'il ne définit pas lui-même ; une liste remplace la valeur héritée et une liste vide
l'efface. agents.Builtins(subagents=False) empêche les agents enfants de lancer leurs propres
agents enfants. Les agents enfants s'exécutent
avec le permission_mode de l'agent principal : leurs outils demandent donc aussi une approbation.
Une seule politique d'approbation pour tous les sous-agents
def ci_policy(
ctx: agents.ApprovalCallbackContext, request: agents.ApprovalRequest
) -> agents.AllowApproval | agents.KeepCallbackOpen:
ids = ("client.run_tests", "client.publish_docs", "agents.spawn") # the children's tools, and starting them
if request.tool.id in ids:
return agents.AllowApproval()
return agents.KeepCallbackOpen()ci_policy autorise les deux outils utilisés par les agents enfants, ainsi que agents.spawn, que l'agent principal appelle pour lancer
chaque agent enfant. En mode ask, le lancement d'un agent enfant demande aussi une approbation. La politique autorise donc agents.spawn :
les lignes Allow tools.agents.spawn? dans la sortie ci-dessous correspondent à ces approbations. Pour toute autre demande, la politique renvoie
agents.KeepCallbackOpen(), afin de laisser une personne décider.
Approbations entre sous-agents explique quel gestionnaire répond à une
demande d'approbation d'un agent enfant.
ship définit la politique sur la session et l'appel reste en attente pendant que la politique répond aux demandes d'approbation des agents
enfants. Les agents enfants peuvent continuer à s'exécuter après la fin du tour de l'agent principal ; session.subagents() permet de les trouver.
session.cancel() dans finally met fin à la session et à ses agents enfants. Jusque-là, la politique répond
à chaque demande d'approbation de chaque agent enfant de la session, même après la fin du tour de l'agent principal. Limitez donc sa portée : une
politique qui autorise tout approuverait aussi un outil dangereux appelé plus tard par un agent enfant.
async def ship(lead: agents.Agent) -> agents.TurnState:
session = await lead.start_session(on_approval=ci_policy) # answers the whole tree
try:
state = await session.run("Ship feature X", timeout=600) # children's approvals too
for child in await session.subagents(): # they can still run after the lead's turn
print(f" child session {child.id}")
return state
finally:
await session.cancel()Un bot qui répond pour la session d'un autre client
Un autre client démarre un tour sans gestionnaire. Un bot de relecture suit la session et, à chaque tour, attend sa fin en appliquant la politique. Il répond ainsi pour toute l'arborescence pendant cette attente :
async def keep_answering(lead: agents.Agent, session_id: str) -> None:
"""A review bot: answers for a session that it does not drive."""
session = lead.session(session_id, on_approval=ci_policy)
async for event in session.iter_events(): # replays from the start, then follows
if isinstance(event, agents.TurnStarted):
await session.turn(event.turn_id).result() # answers the whole tree while it waitsmain exécute les deux exemples : ship, puis une session où le bot répond. Il affiche chaque demande d'approbation ouverte
sur la session qu'il surveille :
async def main() -> None:
state = await ship(lead)
print(f"ship: {type(state).__name__}: {state.text}")
# Another client starts a turn with no handler; the review bot answers its approvals.
session = await lead.start_session()
bot = asyncio.create_task(keep_answering(lead, session.id))
try:
await session.send_message("Ship feature Y")
async for event in session.iter_events():
if isinstance(event, agents.CallbackOpened):
print(f" open: {event.callback.title}")
if isinstance(event, agents.TurnEnded) and event.session_id == session.id:
print(f"bot: {type(event.state).__name__}: {event.state.text}")
break
finally:
bot.cancel()
await session.cancel()
print(f"docs published: {PUBLISHED}")
if __name__ == "__main__":
asyncio.run(main())Sortie
ship a répondu aux demandes d'approbation des deux agents enfants et a listé leurs sessions. Dans la seconde session, le
client qui a envoyé « Livrer la fonctionnalité Y » n'avait pas de gestionnaire : le bot a répondu aux demandes d'approbation ouvertes sur
cette session, les outils des agents enfants se sont exécutés (docs published liste les deux fonctionnalités) et le tour s'est terminé.
Et après
- Sous-agents : les agents enfants, leurs sessions et
session.subagents(). - Approbations : quel gestionnaire répond à quelle demande d'approbation.