Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

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éfinir delegation_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ée delegates explicite. Les entrées explicites choisissent mode = "bounded" ou mode = "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) :

  1. Depuis une boucle d’agent : le modèle appelle l’outil spawn_subagent avec une chaîne prompt. L’outil est enregistré comme n’importe quel autre dans le registre (crates/zeroclaw-runtime/src/tools/mod.rs, SpawnSubagentTool::new).
  2. Depuis cron : les jobs JobType::Agent s’exécutent via run_agent_job (crates/zeroclaw-runtime/src/cron/scheduler.rs), qui construit le même SubAgentContext mais 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.

  1. La boucle d’outils du parent distribue spawn_subagent. L’outil lit son argument prompt et refuse s’il est vide.
  2. 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.
    • Filtre d’outils du profil de risque. Si le [risk_profiles.<alias>].allowed_tools du parent n’est pas vide et ne liste pas spawn_subagent, ou si excluded_tools le liste, refusez avec un message indiquant l’alias du parent.
  3. L’outil appelle SubAgentSpawn::for_agent + build. Les échecs (alias parent inconnu, surcharge d’élévation de privilèges) apparaissent sous la forme ToolResult { success: false, error: "subagent spawn failed: ..." }.
  4. L’outil construit AgentRunOverrides { security, memory: None, is_subagent: true, suppress_memory_inject: true } (l’origine SubTurn de l’enfant ignore déjà l’injection de mémoire du moteur ; le flag rend l’opt-out explicite) et attend crate::agent::run (crates/zeroclaw-runtime/src/agent/loop_.rs, pub async fn run) dans une portée de tracing clé subagent-<uuid>. L’exécution tool du parent bloque jusqu’au retour de l’enfant.
  5. La boucle de l’agent enfant s’exécute jusqu’à son terme. Son registre d’outils est construit à neuf, avec is_subagent_caller: true transmis à son propre SpawnSubagentTool, de sorte que toute tentative de récursion est rejetée à la même barrière de profondeur 1.
  6. L’enfant retourne Result<String>. L’outil spawn_subagent du 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: ...") }.
  7. La boucle d’outils du parent se poursuit avec ce ToolResult dans 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> (ou cron-<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.

Il n’existe aucun canal de streaming ou de progression partielle vers le parent. Les SubAgents de longue durée bloquent l’exécution des outils du parent pendant toute leur durée ; il n’y a aucun réglage de délai d’expiration par appel.

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.

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 :

  1. SecurityPolicy : hérité par clonage de Arc<SecurityPolicy>. Le chemin de surcharge (SubAgentOverrides::policy = Some(policy)) exécute SecurityPolicy::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_commands et require_approval_for_medium_risk. Les rejets chaînent une EscalationViolation précise afin que les diagnostics nomment le champ fautif.
  2. Budgets d’actions / de coûts : PerSenderTracker est partagé entre le parent et l’enfant par clone d’Arc. Chemin d’héritage tel quel : l’enfant détient le même Arc<SecurityPolicy>, donc les écritures dans record_action() / record_cost() touchent le même compartiment. Chemin de surcharge : SubAgentSpawn::build copie explicitement le champ tracker du parent dans la politique restreinte de l’enfant. Un SubAgent ne peut pas contourner max_actions_per_hour ou max_cost_per_day_cents en se dupliquant, la limite est partagée.
  3. Registre d’outils : le registre de l’enfant est construit à neuf par tools::all_tools_with_runtime selon la politique héritée. Le registre passe ensuite par apply_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_tools de la stratégie (provenant du risk_profile du parent).
    • L’argument allowed_tools fourni par l’appelant à agent::run. spawn_subagent figure dans le registre, mais son indicateur is_subagent_caller est défini à true pour l’enfant, de sorte que le refus de profondeur 1 se déclenche avant tout travail de spawn. Ce même indicateur is_subagent_caller retire entièrement model_switch du 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é.
  4. Liste d’autorisation mémoire : un HashSet<String> d’alias d’agents frères (les clés de configuration [agents.<alias>]). Héritée du workspace.read_memory_from du parent plus l’alias propre du parent. Le chemin de surcharge (SubAgentOverrides::allowed_agent_aliases) est validé comme un sous-ensemble ; tout alias absent de la liste du parent est rejeté par nom. L’alias propre du parent est toujours rajouté afin qu’un SubAgent voie toujours les lignes de son parent.
  5. Fournisseur de modèle : hérité de la résolution [agents.<alias>] model_provider du 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_switch est 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, utilisez delegate vers un agent frère dont le model_provider désigne ce modèle.
  6. 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 qui ne figurent pas dans le registre de l’agent parent ne peuvent pas être sélectionnés. Le contrôle du profil de risque se trouve dans [risk_profiles.<alias>].allowed_tools et [risk_profiles.<alias>].excluded_tools. Une liste allowed_tools non vide doit inclure spawn_subagent ou delegate pour que le modèle puisse voir cet outil ; une liste allowed_tools vide laisse la disponibilité des outils sans restriction, sauf si excluded_tools nomme l’outil. Redémarrez le démon après avoir modifié la configuration.

Ce qui est vérifiable de bout en bout :

  1. 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.
  2. Les paramètres de configuration concrets qui modifient le comportement (allowed_tools, max_delegation_depth, etc.).
  3. 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 :

  1. 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_subagent tool »).
  2. 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.

  1. Argument prompt vide/manquant : Missing or empty 'prompt' parameter
  2. Caller is itself a SubAgent (depth-1 cap): spawn_subagent: a subagent may not spawn its own subagents (depth-1 cap)
  3. Le contrôle d’outils du profil de risque du parent exclut spawn_subagent : spawn_subagent: refused — agent '<parent_alias>' risk_profile does not list spawn_subagent in allowed_tools
  4. Erreur de l’alias parent inconnu / de l’initialisation du spawn : subagent spawn failed: <wrapped error>
  5. 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 :

  1. delegation_policy.mode : le profil de risque de l’appelant doit autoriser la délégation. [risk_profiles.<alias>].delegation_policy vaut { mode = "forbidden" } par défaut ; définissez mode = "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).

  2. 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 sur false pour 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" ou mode = "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 que delegates ne contienne que des chaînes ; une entrée objet rend la section agents invalide 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. Lorsque la cible bornée partage le profil de risque de l’appelant, elle hérite également de la limite de l’espace de travail de session de l’appelant. Une cible inter-profil bornée est autorisée lorsqu’elle est accessible via la liste des délégués de l’appelant et delegation_policy ; elle s’exécute sous la politique résolue de la cible, tandis que la disponibilité des outils agentiques est limitée par le registre d’outils de l’appelant.

    Un target indépendant est disponible uniquement lorsqu’il est explicitement listé avec mode = "independent". Il nécessite toujours delegation_policy.mode = "allow" et une accessibilité via delegates, 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 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 d’agent bornée, les outils du sous-agent sont extraits du registre de l’appelant déjà filtré par la politique, puis croisés avec les allowed_tools propres à la cible. Une liste allowed_tools vide sur la cible signifie “hériter” : le sous-agent s’exécute avec l’intégralité du registre délégable de l’appelant au lieu d’être rejeté. Une liste non vide est croisée avec ce registre. Dans les deux cas, le registre de l’appelant constitue le plafond : une cible inter-profils dont le profil de risque référence un outil que l’appelant n’a jamais reçu ne le voit pas attribuer. La délégation bornée est donc limitée par les outils, et ne constitue pas une vérification complète SecurityPolicy::ensure_no_escalation_beyond. Si cette intersection est vide, la cible reçoit tout de même un tour normal du modèle agentique, mais sans aucun outil.

Dans une délégation agentique indépendante, les outils du sous-agent sont construits à partir de la politique et du registre d’exécution configurés de l’agent cible, comme l’ouverture d’un nouveau chat avec cette cible. Le registre parent n’est pas utilisé comme plafond. L’outil delegate est toujours retiré du registre enfant afin que la délégation agentique ne puisse pas entrer en récursivité via un autre appel à delegate.

La profondeur est plafonnée par le runtime_profile.max_delegation_depth du parent. Définissez-la à 1 pour autoriser l’agent principal à effectuer un seul saut de délégation, sans sous-délégation supplémentaire.

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 :

  1. Une liste configurée vide [risk_profiles.<target_profile>].allowed_tools laisse le registre sélectionné sans restriction.
  2. Une liste allowed_tools non vide ne conserve que les noms d’outils correspondant exactement.
  3. [risk_profiles.<target_profile>].excluded_tools est toujours soustrait du résultat.
  4. delegate est toujours supprimé du registre enfant afin que la délégation agentique ne puisse pas se récursiver via un autre appel à delegate.

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.

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.

  1. 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 de delegate-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.

  2. Une réponse terminale vide est un échec synchrone : son champ d’erreur utilise cli-delegate-error-invalid-semantic-completion, avec agent_name défini sur la cible. En anglais : Agent '<target>' failed: model provider returned an invalid semantic completion.

  3. 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.

  4. Délai d’attente synchrone (lorsque le profil d’exécution de la cible définit delegation_timeout_secs) : le champ d’erreur est Agent '<target>' timed out after <N>s.

  5. 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.
    

    Le fichier de résultats se trouve à l’emplacement <workspace>/delegate_results/<uuid>.json. Pendant son exécution, le champ status du fichier vaut running ; les états terminaux sont completed, failed ou cancelled. Une tâche terminée qui a récupéré grâce à un mécanisme de secours de fournisseur configuré stocke dans son output la même attribution entre ce qui a été demandé et ce qui a été fourni, ainsi que le même avertissement générique de récupération ; récupérez-la avec check_result ou await_sessions. Une tâche en échec stocke le même résumé terminal sûr que la délégation synchrone, et non les détails de la réponse du fournisseur.

  6. action="check_result" avec un identifiant de tâche inconnu : l’erreur est No result found for task_id '<uuid>'.

  7. action="await_sessions" avec task_ids: [<uuid>, ...] attend plusieurs fichiers de résultats en arrière-plan à la fois. La sortie est un objet JSON avec status (complete ou timeout), completed, pending, missing, failed et results. timeout_ms vaut 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.

  8. 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>.

  9. Agent cible inconnu : l’erreur est Unknown agent '<target>'. Available agents: <comma-separated list>.

  10. Profondeur dépassée (contrôlée par le runtime_profile.max_delegation_depth du parent, valeur par défaut 3) : l’erreur est Delegation depth limit reached (<depth>/<max>).

  11. Action inconnue : l’erreur est Unknown action '<value>'. Use delegate/check_result/list_results/cancel_task/await_sessions.

  12. Cible indépendante dont le profil de risque contient des entrées always_ask : l’erreur est delegate target "<target>" cannot run in independent mode from "<caller>": risk profile "<profile>" has always_ask entries (<list>). Voir la documentation ZeroClaw, « Delegation & SubAgents » > « What's not supported ».

  13. 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.

  14. 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 aujourd’hui de span de traçage dédié. Le signal est l’apparition dans le journal de la boucle de l’agent cible, qui hérite de la portée dans laquelle se trouvait le dispatch d’appel d’outil du parent. Les spawns en mode arrière-plan sont plus faciles à vérifier hors bande : le fichier de résultat <workspace>/delegate_results/<uuid>.json existe sur le disque et contient les champs status + output de l’agent cible ; cat ou jq fonctionne sans toucher au journal du tout.

(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.)

Ce qui n’est pas dans cette page (intentionnellement)

  1. 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.
  2. 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_subagentdelegate
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’autorisationPolitique 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èleParentFournisseur configuré de l’agent cible
Profondeur de générationLimite stricte à 1Jusqu’à runtime_profile.max_delegation_depth (3 par défaut)
Mode arrière-planNon pris en chargebackground: true retourne un task_id
Distribution en parallèleAucun argument intégré ; plusieurs appels dans un même tour s’exécutent simultanément lorsque parallel_tools = trueparallel: [...] exécute plusieurs cibles simultanément
Contrôle d’accèsrisk_profile.allowed_tools non vide doit lister spawn_subagent ; excluded_tools ne doit pas le listerL’attribut risk_profile.allowed_tools non vide de l’appelant doit lister delegate ; excluded_tools ne doit pas le lister ; le delegation_policy mode = "allow" de l’appelant ; et la cible se trouve dans l’ensemble accessible de l’appelant (pair du même profil ou entrée delegates explicite)
À utiliser quandSous-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

  1. 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.
  2. 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 delegate pour déléguer à un agent frère configuré.
  3. Budget de temps par spawn. Il n’y a pas d’argument timeout_secs. Le parent est bloqué pendant toute la durée de l’exécution de l’enfant ; l’annulation doit passer par la portée d’interruption plus large.
  4. 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.
  5. 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 passent SubAgentOverrides::default() jusqu’à ce que cette surface arrive.
  6. Cibles delegate indépendantes avec always_ask. La délégation indépendante est bloquée lorsque le profil de risque de l’agent cible contient des entrées always_ask non vides. Le runtime refuse avant de démarrer la cible, y compris pour la délégation en arrière-plan et parallèle. Ce blocage demeure jusqu’à ce que la transmission des approbations pour les agents enfants indépendants soit prise en charge par une future version de ZeroClaw.