Aller au contenu

Tickets et délégation

Un ticket est l’unité de travail que Control Center suit. C’est délibérément un artefact bête : un titre, un corps, un statut, un responsable et une place dans un arbre. Ce n’est pas l’objet contre lequel les agents travaillent — le travail des agents vit dans les conversations, et la sortie structurée d’une exécution vit dans le journal d’exécution.

L’attribution consigne la propriété ; elle ne lance rien

Section intitulée “L’attribution consigne la propriété ; elle ne lance rien”

C’est la chose la plus importante à comprendre au sujet des tickets.

Définir assignedAgentId écrit un champ et publie un TicketAssigned événement de domaine. Cet événement est un signal de vérification et de notification, ainsi qu’un déclencheur de pipeline. L’attribution à un agent ne démarre aucune exécution. Il n’existe aucun répartiteur de tickets qui surveille assignedAgentId et réveille cet agent.

L’exception est une équipe : quand assignedTeamId est défini, TeamRoutingService lance le responsable de l’équipe avec le protocole d’exploitation et l’effectif.

Si vous voulez qu’un agent précis travaille sur un ticket, demandez-le-lui dans une conversation, faites-le lancer par une étape de pipeline, ou laissez un autre agent lui déléguer le travail. Attribuer un ticket à un agent puis attendre est la seule chose qui ne produira jamais de travail.

Un ticket peut être purement local ou adossé à un outil de suivi externe, et il porte les deux sur une même ligne.

Le miroir — fournisseur, clé externe, URL, titre, description, priorité, étiquettes, statut brut et normalisé, horodatages — est un cache lorsque le ticket est distant. La source de vérité est alors le système distant, et une actualisation réécrit le miroir au complet.

La surcouche — attribution, délégation, lien d’espace, parent, projet, pull requests liées — n’existe que dans Control Center. L’outil de suivi n’en sait rien et une actualisation n’y touche jamais.

C’est cette séparation qui rend la synchronisation sûre : une récupération chez le fournisseur peut être destructrice pour le miroir sans jamais perdre les métadonnées d’orchestration superposées.

Le graphe de cycle de vie lie les agents, pas vous

Section intitulée “Le graphe de cycle de vie lie les agents, pas vous”

Les statuts suivent backlog → open → inProgress → inReview → done, avec blocked comme boucle latérale et failed / cancelled comme autres états terminaux.

Le graphe de transitions n’est appliqué que sur le chemin des agents et de l’automatisation — les outils MCP et les réconciliateurs. Une transition illégale y est journalisée et ignorée, de sorte qu’un agent confus ne peut pas faire reculer un ticket hors d’un état terminal.

Tout changement déclenché par l’utilisateur dans l’interface passe force: true et contourne entièrement le graphe. Une personne peut déplacer un ticket vers n’importe quel statut figurant dans le sélecteur — y compris rouvrir un état terminal. Le sélecteur n’offre pas failed ; les agents le définissent avec fail_ticket. Le graphe est un garde-fou pour l’automatisation, et non une règle sur la façon dont le travail peut se dérouler.

TicketWorkflowService détient chaque mutation par un point de passage unique qui charge la ligne, vérifie qu’elle appartient à l’espace de travail de l’appelant et écrit avec un version attendu, pour que deux modifications concurrentes ne puissent pas s’écraser silencieusement.

Pour le tableau complet des statuts, les transitions, la liste des champs et les types de relations, consultez Cycle de vie des tickets.

Chaque ticket possède un originKind. L’énumération comporte cinq valeurs — manual, pipelineStep, agentDelegation, externalSync, recovery — mais seulement deux sont écrites aujourd’hui : manual (la valeur par défaut de createTicket, y compris pour les enfants délégués) et externalSync (le moteur de synchronisation, lors d’une récupération chez le fournisseur). TicketWorkflowService.createTicket ne prend pas d’argument d’origine, donc pipelineStep, agentDelegation et recovery n’aboutissent jamais sur une ligne.

La délégation est le moment où un agent crée du travail pour un autre agent, et c’est là que des privilèges pourraient fuir ; les gardes vivent donc côté serveur, à un point de passage, plutôt que dans les instructions du prompt.

delegate_task passe par TicketWorkflowService.delegateGuarded, qui calcule la profondeur et la racine de l’enfant à partir de la chaîne des parents, puis exécute les quatre vérifications de DelegationGuards : un saut qui dépasserait un plafond de profondeur de 3, un saut vers un agent déjà présent dans la chaîne (détection de cycle), un plafond d’autonomie (le niveau effectif du délégataire dans l’espace ne doit pas dépasser celui du délégant) et une enveloppe budgétaire (la délégation est imputée au budget d’arrêt ferme restant du délégant et ne peut pas en créer davantage). Le câblage de production fournit les deux ports de résolution. L’autonomie et le budget sont ignorés quand from_agent_id est omis (un budget illimité ou sans politique passe aussi). Un refus est levé sous forme de DelegationRefusedException, dont le message parvient mot pour mot à l’agent délégant — un refus explicite, jamais une non-opération silencieuse.

delegate_ticket est le frère plus ancien et non gardé : il appelle createTicket directement et saute toutes les gardes. Préférez delegate_task.

Ne confondez pas le plafond de profondeur de délégation (3, pour les chaînes de delegate_task) avec le plafond d’imbrication de sous-agents du harnais intégré (2). Ce sont des limites différentes sur des mécanismes différents.

Les relations parent et sous-tâche vivent dans tickets.parent_ticket_id, gérées par setParent / clearParent. Ce ne sont pas des lignes de ticket_links, donc tout ce qui lit les relations d’un ticket doit fusionner les deux sources.

ticket_links contient les arêtes directionnelles entre des tickets par ailleurs sans lien :

Type de lien Signification
blocks La source bloque la cible
relatesTo Les deux tickets sont liés (symétrique)
duplicateOf La source est un doublon de la cible

Un projet est un regroupement de tickets à l’échelle d’un espace de travail, autour d’un objectif commun. Les projets n’existent que dans Control Center et ne se synchronisent jamais avec un fournisseur externe.

Les lignes TicketCollaborator permettent de rattacher plus d’un principal à un ticket, avec un rôle de assignee, collaborator ou reviewer. Les personnes et les agents sont des lignes de même rang — un principalId plus un collaboratorType — sans valeur sentinelle pour « l’utilisateur » et sans clé étrangère vers l’une ou l’autre table.

Le ticket de Control Center est la référence. La suppression d’un ticket est locale seulement : un ticket adossé à un fournisseur que vous supprimez n’est pas supprimé dans l’outil de suivi et peut réapparaître à la prochaine synchronisation. Les collaborateurs et les tickets enfants sont supprimés en cascade.

Les identifiants des adaptateurs proviennent des magasins d’identifiants du serveur (une clé Linear ou un jeton GitHub collé dans Paramètres est pris en compte à la requête suivante ; l’environnement ne fait qu’amorcer ces magasins).

Aucun nœud de pipeline ne crée, ne termine ni n’annule de tickets — la palette n’offre aucune telle étape et le moteur n’appelle pas TicketWorkflowService. Un déclencheur de pipeline peut néanmoins se déclencher sur TicketAssigned, TicketCompleted, TicketFailed ou TicketCancelled (ces quatre sont les événements de ticket que EventPayloadMapper mappe réellement). Il n’y a pas de colonne pipelineRunId sur le ticket ; le couplage avec les pipelines vit dans le journal des exécutions d’agents.