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 une arborescence. Ce n’est pas l’objet sur lequel les agents s’exécutent — 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 à bien 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 d’audit et de notification, ainsi qu’un déclencheur de pipeline. L’attribution à un agent ne démarre aucune exécution. Aucun répartiteur de tickets ne surveille assignedAgentId pour réveiller cet agent.

L’exception est une équipe : lorsque assignedTeamId est défini, TeamRoutingService lance le chef d’équipe avec le protocole d’exploitation et la composition de l’équipe.

Si vous voulez qu’un agent précis travaille sur un ticket, vous le lui demandez dans une conversation, ou une étape de pipeline le lance, ou un autre agent lui délègue la tâche. Attribuer un ticket à un agent et 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 distante y fait alors autorité, et une actualisation réécrit le miroir intégralement.

La surcouche — attribution, délégation, lien d’espace, parent, projet, PR liées — est propre à Control Center. L’outil de suivi n’en sait rien et une actualisation n’y touche jamais.

Cette séparation est précisément ce qui rend la synchronisation sûre : une récupération chez le fournisseur peut écraser le miroir sans jamais perdre les métadonnées d’orchestration superposées.

Le graphe de cycle de vie s’impose aux agents, pas à vous

Section intitulée « Le graphe de cycle de vie s’impose aux agents, pas à vous »

Les statuts s’enchaînent 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.

Toute modification déclenchée par un utilisateur dans l’interface passe force: true et contourne entièrement le graphe. Un humain peut déplacer un ticket vers n’importe quel statut proposé par le sélecteur — y compris rouvrir un ticket terminal. Le sélecteur ne propose pas failed ; les agents le définissent via fail_ticket. Le graphe est un garde-fou pour l’automatisation, pas une règle sur la façon dont le travail a le droit de progresser.

TicketWorkflowService détient chaque mutation via un point de passage unique qui charge la ligne, vérifie qu’elle appartient à l’espace de travail de l’appelant et écrit avec une version attendue, de sorte que deux modifications concurrentes ne peuvent 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 compte cinq valeurs — manual, pipelineStep, agentDelegation, externalSync, recovery — mais seules deux sont écrites aujourd’hui : manual (la valeur par défaut de createTicket, y compris pour les tickets 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’atterrissent 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’endroit où un privilège pourrait fuir ; les gardes vivent donc côté serveur, à un point de passage unique, plutôt que dans des instructions de 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 contrôles 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égué dans l’espace ne doit pas dépasser celui du délégant) et une enveloppe budgétaire (la délégation est facturée au budget d’arrêt strict 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 lorsque 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 tel quel à l’agent délégant — un refus explicite, jamais un no-op silencieux.

delegate_ticket est le frère aîné, non gardé : il appelle createTicket directement et saute tous les gardes. Préférez delegate_task.

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

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

ticket_links contient les arêtes orientées 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 de l’espace de travail, autour d’un objectif commun. Les projets sont propres à 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 assignee, collaborator ou reviewer. Humains et agents sont des lignes égales en droit — un principalId et 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 fait foi. Supprimer un ticket est local uniquement : 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 suivent en cascade.

Les identifiants d’adaptateur proviennent des magasins d’identifiants du serveur (une clé Linear ou un jeton GitHub collé dans Paramètres est pris en compte dès 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 ticket — la palette ne contient aucune étape de ce type 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’existe pas de colonne pipelineRunId sur le ticket ; le couplage avec les pipelines vit dans le journal d’exécution de l’agent.