Délégation et sous-agents
Un SubAgent est une exécution enfant éphémère générée par un agent parent dont elle hérite par défaut de l’identité : même alias d’agent, même SecurityPolicy, même liste d’autorisation mémoire, même fournisseur de modèle configuré, même registre d’outils. Auditable en tant qu’enfant via un span de traçage agent.<alias>.subagent.<run_id>.
Les SubAgents ne sont pas un concept de configuration distinct. Il n’existe pas de bloc [subagents.*] dans le schéma. L’identité de chaque SubAgent correspond au parent dont la boucle d’agent l’a généré.
Quand utiliser spawn_subagent plutôt que delegate
Deux outils sont situés à proximité. Ils ne sont pas interchangeables.
spawn_subagent: exécute à nouveau le MÊME agent sous sa propre identité pour une sous-tâche ciblée. L’agent enfant voit l’enveloppe complète des permissions du parent, moins toute restriction éventuelle. À utiliser lorsque le parent souhaite isoler une sous-tâche interne de son historique de conversation principal sans changer d’identité.delegate: transfère la requête à un agent configuré DIFFÉRENT (identifié par son alias). L’agent cible s’exécute avec sa propre identité et son propre fournisseur de modèle, mais la délégation est soumise à une restriction : le profil de risque de l’appelant doit définirdelegation_policy mode = "allow"(la valeur par défaut est"forbidden"), et la cible doit être joignable en tant que pair du même profil ou via une entréedelegatesexplicite. Les entrées explicites choisissentmode = "bounded"oumode = "independent", ce qui détermine si la limite d’outils de l’appelant s’applique encore. Utilisez-le lorsqu’un autre spécialiste configuré doit prendre en charge la tâche. Voir Contrôle de la délégation ci-dessous.
Cette page documente spawn_subagent de bout en bout. delegate se trouve dans crates/zeroclaw-runtime/src/tools/delegate.rs et constitue une surface distincte.
Comment un SubAgent est instancié
Deux sites de spawn convergent vers SubAgentSpawn (crates/zeroclaw-runtime/src/subagent/mod.rs:97) :
- Depuis une boucle d’agent : le modèle appelle l’outil
spawn_subagentavec une chaîneprompt. L’outil est enregistré comme n’importe quel autre dans le registre (crates/zeroclaw-runtime/src/tools/mod.rs,SpawnSubagentTool::new). - Depuis cron : les jobs
JobType::Agents’exécutent viarun_agent_job(crates/zeroclaw-runtime/src/cron/scheduler.rs), qui construit le mêmeSubAgentContextmais marque l’enfant comme une exécution de premier niveau (pas un SubAgent), afin qu’il puisse lui-même lancer un niveau de sous-agent.
Les deux chemins invoquent :
#![allow(unused)]
fn main() {
SubAgentSpawn::for_agent(config, parent_alias)? // résoudre l'identité parente
.build(SubAgentOverrides::default())? // valide tout rétrécissement de type
}
for_agent lit le risk_profile du parent et [agents.<alias>.workspace.read_memory_from] pour construire la liste d’autorisations héritée ; l’alias propre du parent est toujours ajouté afin qu’un SubAgent voie toujours les lignes de mémoire propres à son parent. build applique un rétrécissement facultatif (voir Héritage des permissions ci-dessous) et renvoie un SubAgentContext validé.
Cycle de vie
Synchrone, in-process, runtime tokio unique. Rien ne franchit la limite du processus.
- La boucle d’outils du parent distribue
spawn_subagent. L’outil lit son argumentpromptet refuse s’il est vide. - L’outil vérifie deux conditions de protection dans l’ordre :
- Plafond de profondeur 1. Si l’exécution appelante était elle-même un SubAgent (
AgentRunOverrides.is_subagent == true), refuser avec"spawn_subagent: a subagent may not spawn its own subagents (depth-1 cap)". Les SubAgents ne peuvent pas être récursifs. - Contrôle des outils par profil de risque. Si
[risk_profiles.<alias>].deny_all_toolsdu parent vauttrue, ou siallowed_toolsest non vide et ne liste passpawn_subagent, ou siexcluded_toolsle liste, refuser avec un message mentionnant l’alias du parent. Unallowed_toolsomis ou vide laisse l’outil disponible (l’état non restreint hérité).
- Plafond de profondeur 1. Si l’exécution appelante était elle-même un SubAgent (
- L’outil appelle
SubAgentSpawn::for_agent+build. Les échecs (alias parent inconnu, surcharge d’élévation de privilèges) apparaissent sous la formeToolResult { success: false, error: "subagent spawn failed: ..." }. - L’outil construit
AgentRunOverrides { security, memory: None, is_subagent: true, suppress_memory_inject: true }(l’origineSubTurnde l’enfant ignore déjà l’injection de mémoire du moteur ; le flag rend l’opt-out explicite) et attendcrate::agent::run(crates/zeroclaw-runtime/src/agent/loop_.rs,pub async fn run) dans une portée de tracing clésubagent-<uuid>. L’exécutiontooldu parent bloque jusqu’au retour de l’enfant. - La boucle de l’agent enfant s’exécute jusqu’à son terme. Son registre d’outils est construit à neuf, avec
is_subagent_caller: truetransmis à son propreSpawnSubagentTool, de sorte que toute tentative de récursion est rejetée à la même barrière de profondeur 1. - L’enfant retourne
Result<String>. L’outilspawn_subagentdu parent l’encapsule :- Succès :
ToolResult { success: true, output: <child's final response>, error: None }. Une sortie vide est remplacée par la valeur littérale"subagent completed without output". - Échec :
ToolResult { success: false, error: Some("subagent run failed: ...") }.
- Succès :
- La boucle d’outils du parent se poursuit avec ce
ToolResultdans son contexte de conversation. Les tours intermédiaires et les appels d’outils de l’enfant ne sont PAS rejoués dans l’historique du parent ; seule la réponse finale apparaît.
Ce qui est renvoyé en amont
Une chose : le message final de l’assistant de l’enfant, sous forme de chaîne, encapsulé dans ToolResult.output.
- Les appels d’outils de l’enfant, les tours de raisonnement intermédiaires et toutes les écritures en mémoire effectuées par l’enfant sont observables dans les journaux structurés sous la portée de traçage de l’enfant, mais n’entrent pas dans l’historique de conversation du parent.
- La session de l’enfant réside sous le chemin
subagent-<uuid>(oucron-<uuid>pour les exécutions lancées par cron). Il s’agit de la clé d’historique de conversation, et non d’un emplacement dans le système de fichiers ; elle isole l’historique de l’enfant de celui du parent. - Les écritures en mémoire effectuées par l’enfant sont écrites dans l’identité du parent (même UUID d’agent pour les backends SQL/Postgres ; même répertoire de workspace pour Markdown). Les exécutions lancées par Cron désactivent
memory.auto_save, de sorte que les écritures volontaires fonctionnent toujours, mais la mémorisation de routine ne s’accumule pas.
Une délégation synchrone ne dispose d’aucun canal de streaming ni de remontée de progression partielle vers le parent : un SubAgent de longue durée bloque l’exécution des outils du parent pendant toute sa durée, et il n’existe aucun paramètre de délai d’expiration par appel. Une délégation en arrière-plan fournit à la place une progression en direct limitée via check_result et await_sessions (voir le champ progress dans la structure du résultat en arrière-plan ci-dessous).
Budget d’itérations de l’arbre d’exécution
Définissez max_execution_tree_iterations dans l’entrée [runtime_profiles.<alias>] de la cible pour limiter le nombre total d’itérations de boucle d’outils sur un même arbre d’exécution au premier plan :
[runtime_profiles.default]
max_execution_tree_iterations = 12
Le champ est facultatif. Son omission désactive le budget global, tandis qu’une valeur explicite 0 est invalide. Ce budget est distinct de max_tool_iterations, qui reste la limite locale par boucle pour chaque tour d’agent.
Un tour racine logique possède un seul descripteur de budget atomique. Le spawn_subagent au premier plan, les cibles delegate bornées, synchrones ou parallèles, et le travail enfant SOP explicite en direct consomment tous dans ce descripteur. Un enfant ne peut réserver un emplacement que tant qu’il en reste plus d’un. Lorsque la racine ne dispose plus que de son dernier emplacement, elle consomme cet emplacement pour le chemin de complétion existant sans outils. Un enfant qui épuise sa vue renvoie un ToolResult en échec, ce qui permet au tour racine parent de continuer, et les réservations concurrentes ne peuvent pas faire déborder le compteur.
Les cibles delegate indépendantes en premier plan utilisent le budget de leur propre profil d’exécution, et non le reliquat de l’appelant. Omettre la limite de la cible laisse ce tour indépendant sans plafond de budget d’arbre d’exécution, même lorsque l’appelant en possède un. Les tâches delegate détachées en arrière-plan et les tours pairs créent également une nouvelle racine à partir du profil d’exécution du destinataire, au lieu de partager le budget du tour de lancement.
Plusieurs appels en un seul tour
La boucle d’agent applique une protection contre les appels en double par tour : un outil appelé deux fois avec des arguments identiques dans le même tour voit normalement le second appel ignoré. spawn_subagent et delegate sont exemptés de cette protection. En lancer plusieurs avec le même prompt (redondance, échantillonnage, fan-out) est un schéma intentionnel, et non une répétition accidentelle, donc chaque appel identique s’exécute et chaque résultat est renvoyé. Sans cette exemption, seul le premier appel identique serait exécuté et seule sa sortie parviendrait au modèle.
Lorsque l’exécution parallèle des outils est activée (parallel_tools = true dans le profil d’exécution), plusieurs appels spawn_subagent dans un même tour s’exécutent simultanément et la réponse finale de chaque enfant est renvoyée au parent, associée à son propre appel d’outil. delegate dispose de son propre fan-out explicite via l’argument parallel: [...] (voir la section sur les chaînes de sortie) ; ce chemin lance chaque cible dans sa propre tâche et agrège tous les résultats.
ZeroCode : inspection de la transcription
Les appels spawn_subagent et delegate en premier plan disposent de cartes de transcription extensibles affichant la tâche, les noms des cibles de délégation lorsqu’ils sont fournis, et le texte du résultat conservé. La délégation parallèle reste une seule invocation avec les blocs de résultat existants par cible. Les arguments supplémentaires, tels que context, restent visibles dans le champ d’entrée. Les arguments mal formés ou trop volumineux reviennent à la carte d’outil générique. L’expansion conserve les limites d’affichage des outils de 8 KiB ou 100 lignes par champ ; Copier utilise l’entrée d’origine conservée, dont le résultat peut déjà avoir été tronqué à 16 KiB.
Les noms des cibles de délégation apparaissent dans l’en-tête de la carte, suivis de la tâche et d’une section Response. Pour une enveloppe de délégation synchrone correspondante, la réponse renvoyée apparaît en premier, et les métadonnées du fournisseur et du modèle restent dans Details déployé. Les autres formats de résultat restent intacts. Une réponse manquante affiche Result not recorded. La carte n’affirme pas qu’un enfant est actuellement en cours d’exécution ni qu’il a réussi, y compris dans l’historique restauré ou dans le texte de diagnostic renvoyé. Les lancements en arrière-plan et les actions de gestion telles que check_result et await_sessions conservent des cartes génériques, de sorte qu’un accusé de lancement n’est pas affiché comme la fin de l’exécution d’un enfant.
La supervision en direct des sous-processus nécessite encore des métadonnées d’exécution corrélées à l’identifiant de l’appel d’outil parent : identité de la tâche ou de la session enfant, cycle de vie et résultat faisant autorité, horodatages, et événements d’outils enfants ou lien authentifié vers la transcription. Les notifications d’outils parents actuelles ne transportent pas ces champs. ZeroCode n’en déduit pas à partir du texte des résultats, des journaux de traçage, de la correspondance des invites ni des alias de cible ; le plan de contrôle d’exécution reste le responsable du cycle de vie. Il s’agit d’une implémentation partielle de #8763, et non d’une supervision en direct des sous-processus.
Héritage des permissions
Un SubAgent hérite des permissions du parent à l’identique, sauf si le site de création fournit un SubAgentOverrides restrictif. Aujourd’hui, les deux sites de création in-tree transmettent SubAgentOverrides::default() (tout hériter). La surface de surcharge est livrée et validée ; un futur chemin de restriction fourni par l’appelant peut être intégré sans modification à l’exécution.
Héritage axe par axe :
SecurityPolicy: hérité par clonage deArc<SecurityPolicy>. Le chemin de surcharge (SubAgentOverrides::policy = Some(policy)) exécuteSecurityPolicy::ensure_no_escalation_beyond(crates/zeroclaw-config/src/policy.rs) et rejette tout champ qui ajoute un privilège que le parent ne possède pas. Les axes validés incluent le niveau d’autonomie, allowed_roots (rw + ro + écriture seule), allowed_commands, workspace_only, forbidden_paths dans le sens parent ⊆ enfant, shell_env_passthrough,max_actions_per_hour,max_cost_per_day_cents,shell_timeout_secs,block_high_risk_commandsetrequire_approval_for_medium_risk. Les rejets chaînent uneEscalationViolationprécise afin que les diagnostics nomment le champ fautif.- Budgets d’actions / de coûts :
PerSenderTrackerest partagé entre le parent et l’enfant par clone d’Arc. Chemin d’héritage tel quel : l’enfant détient le mêmeArc<SecurityPolicy>, donc les écritures dansrecord_action()/record_cost()touchent le même compartiment. Chemin de surcharge :SubAgentSpawn::buildcopie explicitement le champtrackerdu parent dans la politique restreinte de l’enfant. Un SubAgent ne peut pas contournermax_actions_per_houroumax_cost_per_day_centsen se dupliquant, la limite est partagée. - Registre d’outils : le registre de l’enfant est construit à neuf par
tools::all_tools_with_runtimeselon la politique héritée. Le registre passe ensuite parapply_policy_tool_filter(crates/zeroclaw-runtime/src/agent/loop_.rs), qui supprime tout outil dont le nom échoue à l’une ou l’autre des vérifications :- Les
allowed_tools/excluded_toolsde la stratégie (provenant durisk_profiledu parent). - L’argument
allowed_toolsfourni par l’appelant àagent::run.spawn_subagentfigure dans le registre, mais son indicateuris_subagent_callerest défini àtruepour l’enfant, de sorte que le refus de profondeur 1 se déclenche avant tout travail de spawn. Ce même indicateuris_subagent_callerretire entièrementmodel_switchdu registre de l’enfant : un SubAgent hérite du modèle du parent tel quel (voir l’axe 5) et ne doit pas pouvoir changer le modèle actif à l’insu du parent, donc l’outil ne lui est tout simplement pas proposé.
- Les
- Liste d’autorisation de la mémoire : les accès accordés aux frères et sœurs du parent via
workspace.read_memory_from, plus l’alias propre du parent. Les restrictions de catégorie restent exactes et ne sont jamais élargies par un SubAgent ; l’alias propre du parent est toujours réintégré afin qu’un SubAgent voie toujours les lignes du parent. La surcharge actuelleSubAgentOverrides::allowed_agent_aliasesest validée comme un sous-ensemble de noms, elle ne peut donc que restreindre les frères et sœurs accordés qui sont visibles. - Fournisseur de modèle : hérité de la résolution
[agents.<alias>] model_providerdu parent. La température provient de l’entrée du fournisseur du parent (config.model_provider_for_agent(parent_alias).and_then(|e| e.temperature)). Cet héritage est imposé, et non un simple comportement par défaut :model_switchest exclu du registre d’outils du SubAgent (voir axe 3), de sorte qu’un SubAgent ne peut pas changer son propre modèle. Pour exécuter une sous-tâche sur un modèle différent, utilisezdelegatevers un agent frère dont lemodel_providerdésigne ce modèle. - Identité au niveau de la couche de données : même UUID dans la table
agents(backends SQL), même répertoire d’espace de travail pour le Markdown, même magasin de secrets. La distinction parent-vs-enfant relève purement de l’observabilité : un span de traçage distinct et une clé de session d’historique de conversation distincte.
Comment un utilisateur en déclenche un
Vous n’appelez pas ces outils vous-même ; c’est le bot qui le fait, depuis l’intérieur de son tour. En tant qu’utilisateur, vous influencez le choix du bot par la façon dont vous formulez votre requête. Il n’y a pas de commande spéciale, pas de syntaxe à barre oblique, ni de JSON que l’utilisateur saisit. Le fait que le modèle choisisse spawn_subagent ou delegate dépend de son prompt système, du texte de description de l’outil (visible par le modèle) et de la formulation de l’utilisateur. La formulation influence ; elle ne force pas.
Ce qui peut être rendu déterministe, c’est la disponibilité : les outils absents du registre de l’agent parent ne peuvent pas être sélectionnés. Le filtre de profil de risque se trouve dans [risk_profiles.<alias>].allowed_tools, [risk_profiles.<alias>].deny_all_tools et [risk_profiles.<alias>].excluded_tools. Une liste allowed_tools non vide doit inclure spawn_subagent ou delegate pour que le modèle voie cet outil ; si allowed_tools est omis ou vide, la disponibilité des outils n’est pas restreinte, sauf si excluded_tools désigne l’outil. deny_all_tools = true est le filtre explicite de refus total et masque les deux outils (une liste allowed_tools vide n’est pas un refus total). Redémarrez le démon après avoir modifié la configuration.
Ce qui est vérifiable de bout en bout :
- Les chaînes de sortie et de refus des outils gérés par le protocole sont des contrats Rust littéraux. La transmission des échecs de complétion du terminal visibles par l’utilisateur relève d’un contrat de catalogue Fluent : la source anglaise est indiquée ci-dessous, et un catalogue dans une autre langue ou une surcharge sur disque qui définit la même clé peut l’afficher différemment.
- Les paramètres de configuration concrets qui modifient le comportement (
allowed_tools,max_delegation_depth, etc.). - La forme du span de traçage structuré qui délimite tout ce qui est émis pendant l’exécution enfant.
Ce qui n’est PAS vérifiable à partir de cette documentation :
- Si votre bot spécifique, sur votre modèle spécifique, avec votre prompt système spécifique, choisira l’outil lorsqu’on lui demande « Spawn a subagent to … ». La formulation a son importance ; les résultats varient. Si le bot ne choisit pas l’outil, le levier le plus fiable consiste à étendre le prompt système du bot avec des instructions explicites (« When asked for a focused subtask, use the
spawn_subagenttool »). - Le texte exact que le bot vous écrit dans sa réponse finale. Le bot lit la sortie de l’outil et génère sa propre réponse à partir de celle-ci. Le texte de sortie de l’outil peut être cité, paraphrasé ou résumé.
spawn_subagent : chaînes de refus que le modèle voit
Ceux-ci sont exacts, provenant de crates/zeroclaw-runtime/src/tools/spawn_subagent.rs. Le modèle les reçoit comme chaîne d’erreur de l’outil et y réagit. La réponse du bot visible par l’utilisateur est ce que le modèle écrit ensuite ; elle fait souvent référence au refus ou le reprend.
- Argument
promptvide/manquant :Missing or empty 'prompt' parameter - Caller is itself a SubAgent (depth-1 cap):
spawn_subagent: a subagent may not spawn its own subagents (depth-1 cap) - La barrière d’outils du profil de risque du parent exclut
spawn_subagent:spawn_subagent : refusé : l'agent '<parent_alias>' risk_profile ne liste pas spawn_subagent dans allowed_tools - Erreur de l’alias parent inconnu / de l’initialisation du spawn :
subagent spawn failed: <wrapped error> - Le sous-traitement enfant a renvoyé une erreur :
subagent run failed: <wrapped error>
En cas de succès, la sortie de l’outil EST le texte de la réponse finale de l’enfant. Si l’enfant a renvoyé une chaîne vide, la sortie est le texte indicateur littéral : subagent completed without output. Il n’y a pas de préfixe fixe à rechercher avec grep en cas de succès.
spawn_subagent : comment vérifier qu’il s’est réellement déclenché
Surveillez votre journal. Le processus enfant généré par l’outil s’exécute dans un scope! qui émet une étendue de traçage nommée zeroclaw_scope (avec la cible zeroclaw_log_internal_scope) portant agent_alias=<parent> et session_key=<uuid>. Chaque ligne de journal émise pendant l’exécution de l’enfant porte ces champs. Le tour du parent possède son propre session_key ; une NOUVELLE valeur de session_key apparaissant en milieu de tour pour le même agent_alias est le signal qu’un SubAgent s’est exécuté. Le chemin de session de l’historique de conversation de l’enfant est subagent-<uuid> (identifiant de type système de fichiers, distinct du champ de traçage).
Les tâches d’agent lancées par cron utilisent un nom de span différent et plus explicite : subagent (littéral) avec les champs category="cron", agent_alias=<owning agent>, cron_job_id=<id>, run_id=<uuid>, spawn_site="cron". Les chemins cron sont trivialement repérables avec grep : grep 'spawn_site="cron"' zeroclaw.log. Notez que les exécutions lancées par cron sont de premier niveau (is_subagent=false) ; elles peuvent elles-mêmes appeler spawn_subagent une seule fois.
Il s’agit d’un signal limité pour le chemin de spawn de la boucle d’agent. Un enregistrement dédié « subagent started / completed » acheminé via attribution_span!(tool) est suivi comme une tâche de suivi côté code ; une fois que la boucle d’agent enveloppera l’exécution des outils dans un span d’attribution, chaque record! à l’intérieur de l’outil portera automatiquement tool=spawn_subagent et la question se réduira à un simple grep.
Restriction de délégation
delegate applique deux contrôles dans crates/zeroclaw-runtime/src/tools/delegate.rs avant qu’un agent cible ne s’exécute, dans cet ordre :
-
delegation_policy.mode: le profil de risque de l’appelant doit autoriser la délégation.[risk_profiles.<alias>].delegation_policyvaut{ mode = "forbidden" }par défaut ; définissezmode = "allow"pour autoriser la délégation. Lorsqu’elle est interdite, le refus est :la délégation est interdite pour l’appelant “
<caller>” par la politique de délégation du profil de risque “<caller_profile>” ; définissez [risk_profiles.<caller_profile>].delegation_policy mode = “allow“Ceci est modifiable dans le tableau de bord de la passerelle et zerocode dans Config → Risk profiles →<profile>→delegation_policy.mode(un sélecteur forbidden/allow). -
Accessibilité : l’agent cible doit se trouver dans l’ensemble accessible de l’appelant, résolu par
Config::reachable_delegate_target_configs. L’ensemble accessible est l’union de deux sources par agent sur[agents.<caller>], à l’exclusion de l’appelant lui-même :-
pairs de même profil : tous les autres agents partageant le profil de risque de l’appelant, inclus tant que
delegate_same_risk_profile = true(la valeur par défaut). Définissez-le surfalsepour exclure l’appelant de l’autorisation automatique des pairs. -
liste explicite :
delegates, une liste éventuellement vide de cibles que l’appelant peut déléguer, même entre profils de risque. Les entrées chaîne sont pratiques pour l’édition manuelle et représentent des cibles bornées. Les entrées objet rendent le mode explicite :delegates = [ "reviewer", { agent = "sysadmin", mode = "independent" }, ]Lorsque la configuration est enregistrée, chaque entrée est écrite sous forme d’objet avec
mode = "bounded"oumode = "independent". Ne déployez pas cette forme de configuration avant que les binaires du daemon et de l’UI n’aient été mis à niveau vers une version qui prend en charge les modes de délégué. Les binaires plus anciens s’attendent à ce quedelegatesne contienne que des chaînes ; une entrée objet rend la sectionagentsinvalide pour ce binaire et le chargeur résilient supprime la section afin que les surfaces de réparation puissent toujours démarrer. Lorsque la cible est en dehors de cet ensemble, le refus nomme la cause. Par exemple :
cible de délégation “
<target>” non atteignable depuis “<caller>” : profil de risque différent (l’appelant utilise “<caller_profile>”, la cible utilise “<target_profile>”). delegate_same_risk_profile n’atteint que les agents ayant le même profil de risque ; ajoutez une entrée [agents.<caller>].delegates explicite avec le mode prévu, ou changez le risk_profile d’un des agents.la cible de délégation “<target>” n’est pas accessible depuis “<caller>” : delegate_same_risk_profile est désactivé et la cible n’est pas listée dans [agents.<caller>].delegatescible de délégation “<target>” n’est pas accessible depuis “<caller>” : l’agent cible est désactivéUne cible bornée hérite du suivi des actions/coûts de l’appelant, les plafonds effectifs étant resserrés aux valeurs de l’appelant à chaque saut. Ainsi, le budget d’actions de l’appelant contraint l’ensemble de son sous-arbre, quel que soit le profil propre de la cible. Les workers d’arrière-plan et parallèles générés restaurent le compartiment de limitation de débit de l’expéditeur d’origine avant leur première admission, afin que le budget reste par expéditeur à travers les frontières de génération, au lieu de retomber sur le compartiment non délimité. Le plafond de coût est combiné de la même manière à chaque saut et appliqué à l’intérieur de la sous-boucle déléguée, par rapport au budget quotidien de dépenses de l’agent cible (voir le paragraphe sur le suivi des coûts ci-dessous). Lorsque la cible bornée partage le profil de risque de l’appelant, elle hérite également de la frontière de l’espace de travail de session de l’appelant. Une cible bornée cross-profile est autorisée lorsqu’elle est accessible via la liste des délégués de l’appelant etdelegation_policy; elle s’exécute sous la politique résolue de la cible, tandis que la disponibilité des outils agentiques est plafonnée par le registre des outils de l’appelant.Un target indépendant est disponible uniquement lorsqu’il est explicitement listé avec
mode = "independent". Il nécessite toujoursdelegation_policy.mode = "allow"et une accessibilité viadelegates, mais une fois sélectionné, il applique la politique propre du target agent sans le plafond de non-escalade du caller, le remplacement de l’espace de travail de session, ou le suivi des actions/coûts.Le suivi des coûts est appliqué dans chaque sous-boucle déléguée, qu’elle soit bornée ou indépendante : la sous-boucle déléguée s’exécute sous un périmètre de suivi des coûts attribué à l’alias de l’agent cible, de sorte que sa consommation de tokens est enregistrée dans le registre quotidien partagé des coûts sous cet alias. Le plafond effectif par saut de la cible, défini par
[runtime_profiles.<target>].max_cost_per_day_cents(une valeur de0hérite de la limite globale), est vérifié par rapport aux dépenses quotidiennes propres de l’agent cible dans ce registre. Les limites process-globales[cost] daily_limit_usdet mensuelles continuent de s’appliquer au total partagé ; la limite partagée l’emporte dès qu’elle est la plus basse des deux. Le type de périmètre (par agent ou plafonné partagé, selon[cost] track_per_agent), la question de savoir si une exécution déléguée est soumise au suivi ([cost] enabled) et l’attribution par agent de ses lignes de registre sont tous décidés une seule fois à la délégation racine d’un arbre, puis hérités par chaque saut imbriqué : une délégation imbriquée dérive son suiveur de celui du parent délégant, de sorte que le mode dans lequel l’arbre a démarré s’applique à l’ensemble de l’arbre, et un rechargement en cours d’exécution de ces indicateurs ne peut ni retirer le suivi d’une exécution suivie, ni commencer à réattribuer en cours de route une exécution non attribuée. Les limites et la table de tarification restent actives à chaque saut : le suiveur dérivé partage le handle de configuration de la base, et la table de tarification est reconstruite à partir de la configuration résolue, de sorte qu’un opérateur qui recharge les limites globales[cost]les applique aux délégués en cours dès leur prochaine vérification de budget. Lorsque les dépenses propres de la cible dépassent déjà son plafond, la délégation échoue avant que le fournisseur de la cible ne soit appelé, et le refus remonte à l’appelant sous forme d’erreur de l’outil de délégation mentionnant le nom de l’agent. Le plafond d’un ancêtre couvre aussi tout ce qu’il a délégué : chaque périmètre de budget d’un saut borné transporte la chaîne des ancêtres, de sorte que la consommation d’un descendant borné compte pour le plafond par saut de chaque ancêtre, même si les lignes de registre du descendant restent attribuées à l’alias propre de ce dernier. Une cible indépendante s’exécute sous son propre plafond par saut et sous les limites globales partagées : elle hérite du mode d’application de l’arbre, mais ni du plafond de l’appelant ni de sa chaîne, de sorte que ni le plafond épuisé de l’appelant ni son accumulateur de descendants ne peuvent bloquer le saut indépendant. La comptabilité des descendants est valable pour la durée de vie du processus et pour chaque jour UTC : elle est remise à zéro avec les totaux quotidiens du registre à minuit UTC, et un redémarrage du démon fait oublier les dépenses des descendants, alors que les totaux propres de chaque agent dans le registre persistent. Avec[cost] track_per_agent = false, aucun total quotidien par agent n’existe, de sorte que le plafond par saut se réduit à une vérification par rapport au total quotidien partagé. Les cibles de délégation non agentiques (un appel unique au modèle, sans boucle d’outils) s’exécutent sous le même périmètre, mais leur chemin de dispatch ne remonte aucune consommation de tokens ; ces appels uniques ne sont donc actuellement ni tarifés ni soumis à une vérification de budget. -
Le roster annoncé est inclus dans la description du paramètre agent du schéma de l’outil. Il liste exactement cet ensemble atteignable, et uniquement lorsque delegation_policy.mode = "allow". Les agents désactivés (enabled = false) ne sont jamais atteignables, qu’ils soient des pairs du même profil ou des entrées delegates explicites.
Dans la délégation agentique bornée, les outils du sous-agent sont tirés du registre de l’appelant, déjà filtré par la politique, puis intersectés avec les allowed_tools propres à la cible. Un allowed_tools omis ou vide sur la cible signifie « hériter » : le sous-agent s’exécute avec le registre délégable complet de l’appelant au lieu d’être rejeté (une liste vide correspond à l’état historique sans restriction, et non à un refus total). deny_all_tools = true est le verrou explicite de refus total et produit un registre enfant vide. Une liste non vide est intersectée avec ce registre. Dans tous les cas, le registre de l’appelant constitue le plafond : une cible bornée inter-profils, dont le profil de risque nomme un outil que l’appelant n’a jamais reçu, ne le reçoit pas. La délégation bornée est donc limitée par les outils, et non soumise à une vérification complète de SecurityPolicy::ensure_no_escalation_beyond. Si cette intersection est vide, la cible reçoit tout de même un tour de modèle agentique normal, sans outils.
L’outil delegate lui-même suit la même forme bornée : il est retiré du registre de l’enfant, sauf si la politique de risque de la cible définit delegation_policy mode = "allow" (la valeur par défaut "forbidden" conserve le comportement actuel, sans sous-délégation). Les contrôles côté appelant s’appliquent toujours : les allowed_tools/excluded_tools de l’appelant doivent autoriser delegate, et la politique d’outils de la cible ne doit pas l’exclure. Lorsqu’il est accordé, l’instance delegate de l’enfant est liée à l’identité de la cible : ses appels résolvent delegation_policy, l’accessibilité et la liste des délégués annoncée à partir de l’alias et de la configuration delegates propres à la cible, jamais à partir de celles du parent délégant, et chaque saut accordé incrémente le compteur de profondeur (voir ci-dessous).
Deux règles d’approbation et de portée s’appliquent à l’instance accordée. Premièrement, elle est réservée à la délégation : les actions de gestion des tâches (check_result, list_results, cancel_task, await_sessions) sont refusées, car l’identité de l’instance enfant est transitoire. La ligne de tâche enregistre la chaîne de délégation des alias des appelants, de sorte que chaque ancêtre de l’enfant est admis par la vérification de propriétaire ; seul un ancêtre dont l’outil de délégation inclut la gestion des tâches (normalement l’agent racine qui délègue, puisque les enfants intermédiaires bornés sont eux aussi réservés à la délégation) peut agir sur cette admission. Ainsi, un enfant transmet l’identifiant de tâche vers le haut jusqu’à atteindre un tel ancêtre. Notez la limite précisément : cela restreint uniquement la surface d’API de délégation. L’artefact delegate_results/*.json sous-jacent reste un fichier ordinaire de l’espace de travail ne contenant que l’identifiant de tâche et la sortie, et tout ce que la vérification de propriétaire contrôle réside dans la ligne de tâche. Deuxièmement, les boucles de sous-agents n’ont aucune voie d’approbation par l’opérateur. Si le profil de risque de la cible demanderait une confirmation pour delegate (la valeur par défaut supervisée, ou l’outil listé dans always_ask), la porte d’approbation de l’enfant refuse l’appel avant que l’implémentation de delegate ne s’exécute, en signalant qu’aucune décision de l’opérateur n’était disponible. L’implémentation de delegate conserve sa propre vérification de politique de la cible pour les appels qui l’atteignent. La délégation imbriquée exige donc que le profil cible approuve explicitement delegate automatiquement ou fonctionne en autonomie complète ; un profil appelant qui demanderait une confirmation continue de la demander dans sa propre boucle, sans changement.
La même frontière « sans opérateur » s’applique aux appels d’outils dans un enfant agentique borné. La boucle enfant crée un gestionnaire d’approbation non interactif neuf à partir du profil de risque de la cible, au point de jonction de répartition (dispatch seam). Un appel nécessitant une confirmation est refusé avant que l’implémentation de l’outil ne s’exécute, tandis qu’un appel explicitement listé dans auto_approve (ou autorisé par l’autonomie complète) peut se poursuivre. Pour les outils de commande hérités, détenus par l’appelant (shell, schedule, cron_add, cron_update et cron_run), cette décision fondée sur le profil de la cible n’approuve que l’invocation de l’outil : elle ne fournit ni ne remplace l’approbation propre à la commande, consommée par la politique de commande de l’appelant. La politique de commande de l’appelant reste déterminante, de sorte que ces outils continuent d’appliquer ses exigences d’approbation. Le registre hérité, plafonné par l’appelant, est inchangé : l’approbation évalue les appels ayant survécu au filtrage des capacités ; elle n’accorde pas d’outils supplémentaires et ne transmet pas la session d’approbation de l’appelant.
Dans la délégation agentique indépendante, les outils du sous-agent sont construits à partir de la politique configurée et du registre d’exécution de l’agent cible, comme pour l’ouverture d’une nouvelle conversation avec cet agent. Le registre du parent n’est pas utilisé comme plafond. L’outil delegate est retiré du registre de ce sous-agent : la délégation indépendante ne se rappelle pas elle-même via un autre appel à delegate.
La profondeur est plafonnée selon le runtime_profile.max_delegation_depth de l’agent parent délégant, et chaque saut accordé porte ce plafond resserré (min) avec le plafond propre au profil de la cible suivante, de sorte que le plafond d’un parent contraint l’ensemble de son sous-arbre, et qu’une chaîne ne peut que se resserrer. Définissez-le à 1 pour permettre à l’agent de premier niveau un seul saut de délégation, sans sous-délégation ultérieure.
Politique des outils cibles agentiques
Si [runtime_profiles.<target>].agentic = true pour l’agent cible, delegate construit le registre d’outils de la sous-boucle cible à partir des outils disponibles du parent (mode = "bounded") ou du propre registre d’exécution de la cible (mode = "independent"). Le profil de risque de la cible filtre ensuite ce registre :
- Un champ
[risk_profiles.<target_profile>].allowed_toolsomis ou vide laisse le registre sélectionné sans restriction.deny_all_tools = trueest le verrou explicite de refus total et produit un registre enfant vide. - Une liste
allowed_toolsnon vide ne conserve que les noms d’outils correspondant exactement. [risk_profiles.<target_profile>].excluded_toolsest toujours soustrait du résultat.delegateest retiré du registre enfant sauf si la sous-délégation bornée est accordée : une cible bornée dont le profil de risque définitdelegation_policy mode = "allow"(et dont les contrôles côté appelant autorisent l’outil) reçoit une instance liée à la cible, limitée à delegate, qui refuse en outre la délégation lorsque le profil de la cible demanderait une confirmation ; les registres enfants indépendants l’abandonnent toujours.
Cette politique réside sur la cible, pas sur l’appelant. Les pairs de même profil utilisent le profil de risque partagé. Les délégués inter-profils explicites utilisent le profil de risque de la cible après les portes de joignabilité et de politique de délégation. Les délégués agentiques bornés reçoivent uniquement le registre d’outils plafonné par l’appelant intersecté avec la politique d’outils de la cible ; les délégués agentiques indépendants reçoivent le registre d’outils appartenant à la cible. Un profil de risque de cible manquant refuse avant le démarrage de la sous-boucle. Un profil configuré qui laisse zéro outil enfant exécutable autorise toujours un tour de modèle normal sans outils.
Lorsque la chaîne de fournisseurs Reliable configurée pour la cible mélange des candidats capables de gérer les outils natifs et des candidats limités au texte, strict_tool_parsing = false utilise un protocole d’outils texte/XML unique pour toute la passe agentique, afin que chaque solution de repli accessible puisse exécuter des outils. S’il reste des outils effectifs et que strict_tool_parsing = true, ZeroClaw rejette la chaîne mixte avant d’effectuer une requête auprès d’un fournisseur, car l’analyse stricte interdit ce protocole de repli texte/XML. Les chaînes uniformes restent inchangées : les chaînes entièrement natives utilisent le transport d’outils natifs, tandis que les chaînes volontairement entièrement textuelles suivent la politique d’outils textuels configurée.
Approbations déléguées indépendantes
Une cible agentique indépendante peut envoyer des demandes d’approbation d’outil à l’opérateur sélectionné par le profil de risque qui lui est propre, via son approval_route. Définissez à la fois approver_channel et un approver_recipient non vide, et veillez à ce que ce canal reste activé et enregistré dans le démon en cours d’exécution. L’appelant doit déjà autoriser cette cible nommée en mode indépendant ; une route d’approbation n’accorde ni portée de délégation ni outils supplémentaires.
Par exemple, une cible utilisant le profil de risque worker peut acheminer ses approbations vers un canal d’exploitation configuré :
[risk_profiles.worker.approval_route]
approver_channel = "telegram.ops"
approver_recipient = "operations-room"
on_no_approver = "deny"
timeout_secs = 120
Les délégués en premier plan, en arrière-plan et les délégués indépendants en parallèle utilisent la même porte d’approbation de la route cible. L’invite identifie la cible et l’appelant, conserve le nom réel de l’outil et le résumé des arguments, et utilise le destinataire configuré. Une approbation réelle autorise l’appel détenu par la cible, sous réserve de sa politique existante pour les outils et les commandes. Un refus, un canal manquant, une surface d’approbation non prise en charge, une déconnexion ou un délai dépassé laisse l’outil non exécuté. Le délai d’exécution de l’enfant borne également l’attente d’approbation et peut expirer avant le délai de la route.
Une route cible complète s’applique également lorsque le profil était déjà configuré pour les tours de canal propres à la cible. Pour un enfant indépendant en mode Supervised, les appels au shell exigent désormais une décision de l’opérateur, sauf si la cible approuve explicitement shell automatiquement. Cela inclut les commandes à faible risque qui passaient auparavant directement par la politique de commandes de l’outil shell. Dans un processus sans registre de canaux, comme une exécution CLI ponctuelle, les appels nécessitant une décision sont refusés. L’autonomie complète et les entrées explicites auto_approve conservent leur comportement existant, tandis que always_ask exige toujours une décision. Supprimez la route pour rétablir le chemin shell non interactif précédent, uniquement si cela correspond à la politique cible voulue.
Chaque exécution enfant dispose d’un gestionnaire d’approbation neuf. Une réponse « Toujours » ne s’applique qu’à cette exécution enfant : elle ne peut pas approuver les appels de l’appelant, les exécutions déléguées ultérieures ni les enfants parallèles. Les outils listés dans always_ask exigent toujours une décision pour chaque appel, y compris en mode « Autonomie totale ». Les outils explicitement approuvés automatiquement conservent par ailleurs le comportement de leur profil cible.
Les délégués n’héritent pas de la session d’approbation CLI, ACP, RPC ou du tableau de bord de l’appelant. Même avec on_no_approver = "inherit-originator", la demande est refusée lorsque l’approbateur configuré ne peut pas répondre, car un enfant indépendant n’a aucun canal d’approbation d’origine vers lequel se replier. L’approbation en attente est propre au processus et est abandonnée en cas d’annulation ou d’expiration du délai de l’enfant ; redémarrer un démon ne reprend pas cette demande et ne restaure pas les autorisations d’approbation accordées à l’enfant.
Sans itinéraire cible complet, les cibles indépendantes dont always_ask n’est pas vide sont refusées avant l’exécution, y compris lors de l’admission en arrière-plan et en parallèle. Les autres appels nécessitant une confirmation échouent toujours en mode fermé au moment de la distribution de l’outil. Les délégués agentiques bornés conservent leur comportement existant sans opérateur ainsi que la politique de commandes détenue par l’appelant.
delegate : chaînes de sortie que le modèle voit
Les chaînes d’échec visibles par l’utilisateur sont des messages Fluent localisés. Leur source de vérité en anglais est crates/zeroclaw-runtime/locales/en/cli.ftl ; les exemples ci-dessous présentent les valeurs actuelles du catalogue anglais, et non un contrat de chaînes au niveau du protocole. Les chaînes restantes sont des sorties de protocole/d’outils, sauf si cette section les désigne comme des clés Fluent.
-
Réussite synchrone : la sortie commence par
[Agent '<target>' (<provider_type>/<model>)]\n, suivie d’une réponse non vide de l’agent cible. Lorsque la cible récupère via une solution de secours de fournisseur configurée, son en-tête identifie à la place le fournisseur et le modèle demandés et utilisés, par exemple[Agent 'reviewer' (requested: anthropic.primary/claude; served: openai.terra/gpt-5.6-terra, agentic)]. Pour une cible agentique, cette attribution décrit la demande de modèle qui a produit la réponse finale, et non une demande antérieure qui a seulement produit un appel d’outil. Le résultat se termine également par la version localisée dedelegate-provider-fallback-warning. En anglais :Warning: The delegated agent recovered through a provider fallback. Provider failure details were logged and omitted from this result.Cette attribution et cet avertissement appartiennent au résultat délégué ; ils ne doivent pas être présentés comme une solution de secours de l’agent appelant. Ils omettent intentionnellement les détails des erreurs des fournisseurs rejetés, les points de terminaison et les identifiants d’authentification. Réessayer le même candidat configuré ne produit pas cet avertissement ; atteindre un candidat configuré ultérieur le produit, même lorsque les libellés de son fournisseur et de son modèle correspondent à ceux du premier candidat. -
Une réponse terminale vide est un échec synchrone : son champ d’erreur utilise
cli-delegate-error-invalid-semantic-completion, avecagent_namedéfini sur la cible. En anglais :Agent '<target>' failed: model provider returned an invalid semantic completion. -
Autres échecs synchrones : le champ d’erreur commence par
Agent '<target>' failed: <wrapped error>. Si tous les candidats de fournisseur configurés échouent,<wrapped error>est le résumé sûr et ordonné fourni par Reliable des événements d’échec, du nombre de tentatives, de la classe d’échec, de la phase et de l’indication corrective fixe. Les corps des réponses des fournisseurs, les points de terminaison, les alias, les modèles et les identifiants d’accès ne sont pas renvoyés à l’agent appelant ; lorsque davantage de détails sont nécessaires, examinez les journaux des tentatives auprès des fournisseurs conformément à la politique habituelle de journalisation des opérateurs de l’installation. Le résultat reste une erreur, et non un avertissement de récupération. -
Délai d’attente synchrone (lorsque le profil d’exécution de la cible définit
delegation_timeout_secs) : le champ d’erreur estAgent '<target>' timed out after <N>s. -
Démarrage en arrière-plan réussi : la sortie correspond au littéral de trois lignes
Background task started for agent '<target>'. task_id: <uuid> Use action='check_result' with task_id='<uuid>' to retrieve the result.L’état du cycle de vie est stocké dans
data/control_plane.db. L’artefact de workspace situé à<workspace>/delegate_results/<uuid>.jsoncontient uniquement l’identité de la tâche et sa sortie ; ce n’est pas un second enregistrement d’état. La vuecheck_result(et chaque entrée d’un résultat deawait_sessions) porte un objetprogressdès que la tâche a démarré, ounullpour les lignes qui n’en ont jamais enregistré. Ses champs sont :last_activity_at,iterations(requêtes de modèle émises),tools_completed,last_tool(name,started_at,finished_at,success: l’appel le plus récent encore en cours, avecfinished_atànull, sinon le dernier appel terminé),timeout_budget_secs(le délai d’expiration agentique, ou le délai d’un appel unique pour une cible sans boucle d’outils),recent_tools(les 5 derniers appels terminés),receipt_tail(les 5 derniers reçus collectés jusqu’ici, sous la forme<tool>: <token>, vide lorsque les reçus d’outils sont désactivés ; les reçus sont collectés au retour d’un lot d’outils, si bien qu’un appel peut être comptabilisé avant que son reçu n’apparaisse), etelapsed_secs, calculé au moment de la lecture. Une cible à appel unique compte sa requête unique dansiterations. Les noms d’outils sont tronqués à 64 caractères partout où ils apparaissent, y compris dansreceipt_tail; les jetons signés sont conservés intacts. Les arguments d’outils, les résultats d’outils, les textes d’erreur et le contenu des prompts ne sont jamais enregistrés. La progression réside dans la ligne de la tâche, elle est donc visible exactement par les appelants qui peuvent lire la sortie de la tâche. Pendant l’exécution de la tâche, le heartbeat de sa ligne est rafraîchi au moins une fois par minute. -
action="check_result"avec un identifiant de tâche inconnu : l’erreur estNo result found for task_id '<uuid>'. -
action="await_sessions"avectask_ids: [<uuid>, ...]attend plusieurs fichiers de résultats en arrière-plan à la fois. La sortie est un objet JSON avecstatus(completeoutimeout),completed,pending,missing,failedetresults.timeout_msvaut par défaut 30000 et est plafonné à 120000 ; en cas de timeout, l’outil renvoie des résultats partiels et une erreur indiquant qu’une ou plusieurs tâches sont encore en attente ou manquantes. Les IDs de tâches en double sont rejetés. -
Sortie de fan-out parallèle : commence par
[Parallel delegation: <N> agents]\n\n, suivie de blocs propres à chaque agent séparés par\n\n, chaque bloc commençant par--- <target> (success=<bool>) ---\n. Une cible récupérée conserve son attribution entre cible demandée et cible servie, ainsi que son avertissement de repli générique, dans son propre bloc. En cas d’échec d’un agent, le bloc interne est--- <target> (success=false) ---\nError: <wrapped error>. -
Agent cible inconnu : l’erreur est
Unknown agent '<target>'. Available agents: <comma-separated list>. -
Profondeur dépassée (contrôlée par le
runtime_profile.max_delegation_depthdu parent, valeur par défaut 3) : l’erreur estDelegation depth limit reached (<depth>/<max>). -
Action inconnue : l’erreur est
Unknown action '<value>'. Use delegate/check_result/list_results/cancel_task/await_sessions. -
Cible dont le profil de risque contient des entrées
always_ask: une cible indépendante sans route d’approbation explicite complète renvoiedelegate target "<target>" cannot run in independent mode from "<caller>": risk profile "<profile>" has always_ask entries (<list>). See ZeroClaw docs, "Delegation & SubAgents" > "What's not supported".Une cible agentique bornée renvoie la même erreur aveccannot run in bounded agentic mode. -
La cible agentique présente un profil de risque manquant : l’erreur est
Agent '<target>' is agentic but risk_profile '<target_profile>' is not configured. -
Cible agentic avec zéro outil enfant exécutable : aucune erreur n’est émise pour l’ensemble d’outils vide ; la cible reçoit un tour de modèle normal sans outils.
delegate : comment vérifier qu’il s’est réellement déclenché
delegate n’émet pas de span de traçage dédié pour le moment. Le signal est la boucle de l’agent cible qui apparaît dans le journal, laquelle hérite du périmètre dans lequel se trouvait l’envoi d’appel d’outil du parent. En mode arrière-plan, utilisez check_result ou await_sessions pour lire ensemble l’état de cycle de vie et la sortie qui font autorité. Ces actions, ainsi que le listage et l’annulation, se limitent aux lignes de délégation dont la chaîne enregistrée contient l’alias de l’appelant. L’artefact de l’espace de travail peut confirmer que la sortie a été persistée, mais il ne porte pas le statut. Lorsque les reçus d’outils sont activés, les délégations en arrière-plan s’exécutent avec un périmètre de reçus qui partage le générateur du parent et utilise son propre collecteur ; leurs résultats d’outils sont donc signés et vérifiables, mais ne sont pas ajoutés au bloc de reçus du tour de lancement.
(Les tâches d’agent lancées par cron constituent un site de génération distinct et utilisent le span subagent explicite décrit ci-dessus ; delegate et cron ne suivent pas le même chemin.)
delegate: ce qui annule l’arrêt du tour
Interrompre le tour qui a appelé delegate (par exemple POST /api/sessions/{id}/abort, ou une interruption de canal) atteint le travail que ce tour a lancé, alors même que les sous-tâches s’exécutent dans leurs propres tâches et survivent à la future d’outil abandonnée du parent :
- Les enfants agentiques (bornés ou indépendants) sont liés à l’annulation du tour de lancement et s’arrêtent avec lui.
- Les enfants parallèles (
parallel: [...]) qui exécutent leur propre boucle d’outils sont liés de la même manière, à chaque niveau de profondeur. Un enfant parallèle qui effectue un simple appel de modèle sans boucle d’outils n’est pas interrompu en cours d’appel : il se termine avec le délai d’expiration propre à cet appel. - Tâches en arrière-plan (
background: true) ne sont liées au tour à aucune profondeur, y compris lorsqu’elles sont lancées par un enfant lui-même démarré au premier plan. Elles survivent au tour par conception et ne s’arrêtent que viacancel_task.
spawn_subagent n’a aucune de ces fonctionnalités : il n’a ni mode arrière-plan ni mode parallèle, et son sous-agent est annulé avec le future parent.
Ce qui n’est pas dans cette page (intentionnellement)
- Exemples de transcriptions de conversations. Tout ce que j’écrirais ici pour décrire « ce que le bot dira » dépendrait du modèle. La réponse du bot découle de la sortie de l’outil, du modèle, du prompt système et de l’état actuel de la conversation, dont aucun n’est contrôlé par cette page. La couche vérifiable est ce que l’outil retourne (ci-dessus) et ce que le journal capture.
- Un marqueur de journal dédié « subagent fired » / « delegate fired ». Suivi comme une tâche de suivi côté code. Aujourd’hui, les opérateurs vérifient via la forme de portée décrite ci-dessus (qui constitue le signal structurel existant) et via le fichier de résultat en mode arrière-plan.
Choisir entre spawn_subagent et delegate
spawn_subagent | delegate | |
|---|---|---|
| Identité | Identique au parent (même UUID, même profil de risque) | Identité de l’agent cible (alias différent ; pair de même profil ou délégué inter-profil explicite) |
| Modèle d’autorisation | Politique du parent telle quelle (ou sous-ensemble restreint) | Les cibles bornées s’exécutent sous la politique de la cible, avec le registre d’outils agentiques de l’appelant comme plafond ; les cibles indépendantes s’exécutent sous la politique de la cible et le registre appartenant à la cible. |
| Fournisseur de modèle | Parent | Fournisseur configuré de l’agent cible |
| Profondeur de génération | Limite stricte à 1 | Jusqu’à runtime_profile.max_delegation_depth (3 par défaut) |
| Mode arrière-plan | Non pris en charge | background: true retourne un task_id |
| Distribution en parallèle | Aucun argument intégré ; plusieurs appels dans un même tour s’exécutent simultanément lorsque parallel_tools = true | parallel: [...] exécute plusieurs cibles simultanément |
| Contrôle d’accès | Un risk_profile.allowed_tools omis ou vide laisse spawn_subagent disponible ; deny_all_tools = true le refuse ; une liste non vide doit inclure spawn_subagent ; excluded_tools ne doit pas le lister | Une valeur allowed_tools omise ou vide laisse delegate disponible ; deny_all_tools = true le refuse ; une liste non vide doit inclure delegate ; excluded_tools ne doit pas le lister ; delegation_policy mode = "allow" doit être défini pour l’appelant ; et la cible doit faire partie de l’ensemble accessible à l’appelant (pair du même profil ou entrée explicite delegates). Une cible bornée ne peut recevoir l’outil delegate que si son propre profil de risque définit delegation_policy mode = "allow" ; l’instance accordée ne dispose que de delegate et refuse la délégation lorsque le profil de la cible demanderait une confirmation pour celle-ci. |
| À utiliser quand | Sous-tâche interne qui doit rester au sein de la même identité | Vouloir un spécialiste configuré différemment (modèle différent, alias différent) pour prendre en charge la tâche sous délégation bornée ou indépendante |
Ce qui n’est pas pris en charge
- Récursivité au-delà du niveau 1. Un SubAgent ne peut pas générer son propre SubAgent. Cette limite est un refus strict au niveau de l’outil, et non un budget. Les exécutions lancées par cron commencent au niveau 0 et peuvent générer un niveau supplémentaire ; les SubAgents lancés par boucle d’agent se trouvent au niveau 1 et refusent toute génération ultérieure.
- Une identité distincte pour l’enfant. Les SubAgents partagent l’UUID de l’agent parent. Pour s’exécuter sous une identité différente, utilisez
delegatepour déléguer à un agent frère configuré. - Budget de temps par lancement. Il n’existe pas d’argument
timeout_secs. Le parent reste bloqué pendant toute la durée de l’exécution de l’enfant ; l’annulation doit passer par le périmètre d’interruption plus large (pourdelegate, voir ce que l’arrêt du tour annule). - Streaming de la progression vers le parent. Le parent voit la réponse finale de l’enfant comme une chaîne unique après l’achèvement.
- Un bloc de configuration
[agents.<alias>].subagent_*. Le validateur et le type override sont disponibles aujourd’hui ; la surface de configuration côté opérateur qui achemine le narrowing défini par l’appelant n’est pas dans cette version. Les deux sites de spawn passentSubAgentOverrides::default()jusqu’à ce que cette surface arrive. - Transmission des approbations à la session d’origine et approbations bornées. Les délégués indépendants peuvent utiliser une voie d’approbation explicite détenue par la cible, mais ne peuvent pas transmettre l’approbation à la session CLI, ACP, RPC ou tableau de bord d’origine de l’appelant. Les cibles agentiques bornées dont
always_askn’est pas vide restent bloquées avant l’exécution, y compris la délégation en arrière-plan et en parallèle. La délégation bornée non agentique reste disponible, car elle ne peut pas exécuter d’outils enfants.