- Accueil
- Documentation
- Sessions
- Pipeline de génération `/handoff`
Pipeline de génération `/handoff`
Ce document décrit comment l’agent de codage implémente /handoff aujourd’hui : chemin de déclenchement, invite de génération, capture de la complétion, changement de session et réinjection du contexte.
Périmètre
Section intitulée « Périmètre »Couvre :
- La distribution de la commande interactive
/handoff - Le cycle de vie et les transitions d’état de
AgentSession.handoff() - La façon dont la sortie du transfert est capturée depuis la sortie de l’assistant
- La façon dont les anciennes et nouvelles sessions persistent les données de transfert différemment
- Le comportement de l’interface utilisateur en cas de succès, d’annulation et d’échec
Ne couvre pas :
- La navigation générique dans l’arbre / les mécanismes internes des branches
- Les commandes de session autres que le transfert (
/new,/fork,/resume)
Fichiers d’implémentation
Section intitulée « Fichiers d’implémentation »../src/modes/controllers/input-controller.ts../src/modes/controllers/command-controller.ts../src/session/agent-session.ts../src/session/session-manager.ts../src/extensibility/slash-commands.ts
Chemin de déclenchement
Section intitulée « Chemin de déclenchement »/handoffest déclaré dans les métadonnées des commandes slash intégrées (slash-commands.ts) avec une indication en ligne optionnelle :[focus instructions].- Dans la gestion des entrées interactives (
InputController), le texte de soumission correspondant à/handoffou/handoff ...est intercepté avant la soumission normale de l’invite. - L’éditeur est effacé et
handleHandoffCommand(customInstructions?)est appelé. CommandController.handleHandoffCommandeffectue une vérification préalable en utilisant les entrées courantes :- Compte les entrées
type === "message". - Si
< 2, un avertissement est émis :Nothing to hand off (no messages yet)et la fonction retourne.
- Compte les entrées
La même vérification de contenu minimal existe à nouveau dans AgentSession.handoff() et lève une exception si la condition n’est pas satisfaite. Cette mesure de sécurité est dupliquée à la fois au niveau de l’interface utilisateur et au niveau de la session.
Cycle de vie de bout en bout
Section intitulée « Cycle de vie de bout en bout »1) Démarrage de la génération du transfert
Section intitulée « 1) Démarrage de la génération du transfert »AgentSession.handoff(customInstructions?) :
- Lit les entrées de la branche courante (
sessionManager.getBranch()) - Valide le nombre minimal de messages (
>= 2) - Crée
#handoffAbortController - Construit une invite fixe et intégrée demandant un document de transfert structuré (
Goal,Constraints & Preferences,Progress,Key Decisions,Critical Context,Next Steps) - Ajoute
Additional focus: ...si des instructions personnalisées sont fournies
L’invite est envoyée via :
await this.prompt(handoffPrompt, { expandPromptTemplates: false });expandPromptTemplates: false empêche l’expansion des modèles de slash/invite de cette charge utile d’instruction interne.
2) Capture de la complétion
Section intitulée « 2) Capture de la complétion »Avant l’envoi de l’invite, handoff() s’abonne aux événements de session et attend agent_end.
À la réception de agent_end, il extrait le texte du transfert depuis l’état de l’agent en parcourant à rebours pour trouver le message assistant le plus récent, puis en concaténant tous les blocs content où type === "text" avec \n.
Hypothèses importantes concernant l’extraction :
- Seuls les blocs de texte sont utilisés ; le contenu non textuel est ignoré.
- On suppose que le dernier message de l’assistant correspond à la génération du transfert.
- Il n’analyse pas les sections markdown ni ne valide la conformité au format.
- Si la sortie de l’assistant ne contient pas de blocs de texte, le transfert est considéré comme manquant.
3) Vérifications d’annulation
Section intitulée « 3) Vérifications d’annulation »handoff() retourne undefined lorsque l’une ou l’autre des conditions suivantes est vérifiée :
- aucun texte de transfert capturé, ou
#handoffAbortController.signal.abortedest vrai
Il efface toujours #handoffAbortController dans finally.
4) Création d’une nouvelle session
Section intitulée « 4) Création d’une nouvelle session »Si du texte a été capturé et que l’opération n’a pas été abandonnée :
- Vider l’enregistreur de la session courante (
sessionManager.flush()) - Démarrer une toute nouvelle session (
sessionManager.newSession()) - Réinitialiser l’état de l’agent en mémoire (
agent.reset()) - Relier
agent.sessionIdau nouvel identifiant de session - Vider les tableaux de contexte en attente (
#steeringMessages,#followUpMessages,#pendingNextTurnMessages) - Réinitialiser le compteur de rappel des tâches
newSession() crée un nouvel en-tête et une liste d’entrées vide (la feuille est réinitialisée à null). Dans le chemin de transfert, aucun parentSession n’est passé.
5) Injection du contexte de transfert
Section intitulée « 5) Injection du contexte de transfert »Le document de transfert généré est encapsulé et ajouté à la nouvelle session en tant qu’entrée custom_message :
<handoff-context>...handoff text...</handoff-context>
The above is a handoff document from a previous session. Use this context to continue the work seamlessly.Appel d’insertion :
this.sessionManager.appendCustomMessageEntry("handoff", handoffContent, true);Sémantique :
customType:"handoff"display:true(visible lors de la reconstruction TUI)- Type d’entrée :
custom_message(participe au contexte LLM)
6) Reconstruction du contexte actif de l’agent
Section intitulée « 6) Reconstruction du contexte actif de l’agent »Après l’injection :
sessionManager.buildSessionContext()résout la liste des messages pour la feuille couranteagent.replaceMessages(sessionContext.messages)rend le message de transfert injecté actif dans le contexte- La méthode retourne
{ document: handoffText }
À ce stade, le contexte LLM actif dans la nouvelle session contient le message de transfert injecté, et non l’ancienne transcription.
Modèle de persistance : ancienne session vs nouvelle session
Section intitulée « Modèle de persistance : ancienne session vs nouvelle session »Ancienne session
Section intitulée « Ancienne session »Durant la génération, la persistance normale des messages reste active. La réponse de transfert de l’assistant est persistée en tant qu’entrée message ordinaire lors de message_end.
Résultat : la session d’origine contient le transfert généré visible dans la transcription historique.
Nouvelle session
Section intitulée « Nouvelle session »Après la réinitialisation de la session, le transfert est persisté en tant que custom_message avec customType: "handoff".
buildSessionContext() convertit cette entrée en un message de contexte personnalisé/utilisateur à l’exécution via createCustomMessage(...), afin qu’il soit inclus dans les futures invites de la nouvelle session.
Comportement du contrôleur / interface utilisateur
Section intitulée « Comportement du contrôleur / interface utilisateur »Comportement de CommandController.handleHandoffCommand :
- Appelle
await session.handoff(customInstructions) - Si le résultat est
undefined:showError("Handoff cancelled") - En cas de succès :
rebuildChatFromMessages()(charge le nouveau contexte de session, incluant le transfert injecté)- invalide la barre de statut et la bordure supérieure de l’éditeur
- recharge les tâches
- ajoute une ligne de chat de succès :
New session started with handoff context
- En cas d’exception :
- si le message est
"Handoff cancelled"ou si le nom de l’erreur estAbortError:showError("Handoff cancelled") - sinon :
showError("Handoff failed: <message>")
- si le message est
- Demande un rendu à la fin
Sémantique d’annulation (comportement actuel)
Section intitulée « Sémantique d’annulation (comportement actuel) »Primitive d’annulation au niveau de la session
Section intitulée « Primitive d’annulation au niveau de la session »AgentSession expose :
abortHandoff()→ abandonne#handoffAbortControllerisGeneratingHandoff→ vrai tant que le contrôleur existe
Lorsque ce chemin d’abandon est utilisé, l’abonné au transfert rejette avec Error("Handoff cancelled"), et le contrôleur de commande le mappe vers l’interface utilisateur d’annulation.
Limitation du chemin interactif /handoff
Section intitulée « Limitation du chemin interactif /handoff »Dans le câblage actuel du contrôleur interactif, /handoff n’installe pas de gestionnaire Escape dédié qui appelle abortHandoff() (contrairement aux chemins de compactage/résumé de branche qui remplacent temporairement editor.onEscape).
Impact pratique :
- Il existe une prise en charge de l’annulation au niveau de la session, mais aucun raccourci clavier spécifique au transfert dans le chemin de la commande
/handoff. - L’interruption par l’utilisateur peut toujours se produire via des chemins d’abandon d’agent plus larges, mais ce n’est pas le même canal d’annulation explicite utilisé par
abortHandoff().
Transfert abandonné vs transfert échoué
Section intitulée « Transfert abandonné vs transfert échoué »Classification actuelle dans l’interface utilisateur :
-
Abandonné/annulé
- Le chemin
abortHandoff()déclenche"Handoff cancelled", ou - une
AbortErrorest levée - L’interface utilisateur affiche
Handoff cancelled
- Le chemin
-
Échoué
- toute autre erreur levée par
handoff()/ le pipeline d’invite (erreurs de validation de modèle/API, exceptions à l’exécution, etc.) - L’interface utilisateur affiche
Handoff failed: ...
- toute autre erreur levée par
Nuance supplémentaire : si la génération se termine mais qu’aucun texte n’est extrait, handoff() retourne undefined et le contrôleur signale actuellement annulé, et non échoué.
Protections pour les sessions courtes et le contenu minimal
Section intitulée « Protections pour les sessions courtes et le contenu minimal »Deux protections empêchent les transferts à faible signal :
- Couche interface utilisateur (
handleHandoffCommand) : avertit et retourne prématurément si< 2entrées de message - Couche session (
handoff()) : lève la même condition en tant qu’erreur
Cela évite de créer une nouvelle session avec un contexte de transfert vide ou quasi-vide.
Résumé des transitions d’état
Section intitulée « Résumé des transitions d’état »Flux d’état de haut niveau :
- Commande slash interactive interceptée
- Vérification préalable du nombre de messages
#handoffAbortControllercréé (isGeneratingHandoff = true)- Invite de transfert interne soumise (visible dans le chat comme une génération normale de l’assistant)
- À la réception de
agent_end, le dernier texte de l’assistant est extrait - Si manquant/abandonné → retourner
undefinedou chemin d’erreur d’annulation - Si présent :
- vider l’ancienne session
- créer une nouvelle session vide
- réinitialiser les files d’attente/compteurs à l’exécution
- ajouter
custom_message(handoff) - reconstruire et remplacer les messages actifs de l’agent
- Le contrôleur reconstruit l’interface de chat et annonce le succès
#handoffAbortControllereffacé (isGeneratingHandoff = false)
Hypothèses et limitations connues
Section intitulée « Hypothèses et limitations connues »- L’extraction du transfert est heuristique : « derniers blocs de texte de l’assistant » ; aucune validation structurelle.
- Aucune vérification stricte que le markdown généré suit le format de section demandé.
- Le texte extrait manquant est signalé comme une annulation dans l’expérience utilisateur du contrôleur.
- Le flux interactif
/handoffmanque actuellement d’une liaison Escape→abortHandoff()dédiée. - Les métadonnées de lignée de la nouvelle session (
parentSession) ne sont pas définies par ce chemin.