Étapes de pipeline
Une étape de pipeline comporte deux axes indépendants :
- Type (
StepKind) — la façon dont le moteur planifie l’étape par rapport à ses déclencheurs. - Clé de corps (
BuiltInBodyKeys) — la fermeture de corps enregistrée qui exécute le travail de l’étape.
Types d’étapes
Section intitulée “Types d’étapes”Définis dans packages/cc_domain/lib/features/pipelines/domain/entities/step_kind.dart.
| Type | Sémantique d’ordonnancement |
|---|---|
trigger |
Point d’entrée obligatoire — exactement un par modèle, toujours le premier nœud. Ne fait aucun travail en soi ; ce qui démarre l’exécution (manuel / événement / planification) est suivi séparément dans des lignes PipelineTrigger. Son corps est le corps sans effet pipeline.trigger. |
listen |
Se déclenche lorsque toutes les étapes sources (StepTrigger.sourceStepIds) sont terminées. |
join |
Se déclenche lorsque toutes les étapes de waitForStepIds atteignent un état terminal. |
router |
Embranchement conditionnel — le corps retourne StepResult.route(key), ce qui ne déclenche que l’arête en aval dont la clé routeKey correspond ; les branches non sélectionnées sont marquées comme ignorées. |
forEach |
Planifiée comme listen (se déclenche lorsque toutes les étapes de sourceStepIds sont terminées). C’est le corps enregistré flow.forEach qui itère ; le moteur ne crée pas une étape enfant par élément. |
terminal |
Sentinelle de l’ordonnanceur sans corps. L’exécution du pipeline se termine lorsqu’une étape terminale se termine. |
Corps d’étape
Section intitulée “Corps d’étape”Fermetures de corps enregistrées, indexées par les constantes de BuiltInBodyKeys (packages/cc_domain/lib/features/pipelines/domain/templates/builtin_template_seeds.dart). Il y en a 25, tous répertoriés ci-dessous. La configuration est lue dans le PipelineNodeConfig de l’étape (voir ci-dessous) sauf indication contraire ; les « clés d’état » sont lues dans l’état du pipeline ou la charge utile du déclencheur au moment de l’exécution.
Une étape dont le bodyKey n’est pas enregistré produit un avertissement au chargement du modèle, puis ne s’exécute jamais — elle ne fait pas échouer l’exécution. Les étapes trigger et terminal sont exemptées de cet avertissement : le déclencheur exécute quand même le corps sans effet pipeline.trigger ; une étape terminal n’a pas de corps.
pipeline.trigger
Section intitulée “pipeline.trigger”Corps sans effet du nœud trigger obligatoire — se termine immédiatement pour que le moteur se déploie en éventail vers les écouteurs en aval. Aucune configuration.
pipeline.bashScript
Section intitulée “pipeline.bashScript”Étape shell sans agent (bash -c). Les espaces réservés {{key}} du script sont remplacés à partir de l’état et de la charge utile du déclencheur par des valeurs échappées pour le shell.
| Champ de configuration | Description |
|---|---|
script |
Commande shell à exécuter (obligatoire) |
outputKey |
Clé d’état qui reçoit la sortie standard débarrassée des espaces de début et de fin en cas de sortie 0 |
S’exécute avec le répertoire de travail <cc_root>/pipelines/<pipelineRunId>/ (partagé par toutes les étapes bash d’une exécution) et GITHUB_TOKEN dans l’environnement. Écrit aussi <stepId>_runDir dans l’état. Un code de sortie non nul fait échouer l’étape avec la fin de la sortie d’erreur (stderr).
conversation.promptAgent
Section intitulée “conversation.promptAgent”Nœud générique de prompt et de lancement : rend le prompt, lance l’agent dans une conversation et suspend jusqu’à ce que l’exécution se termine. La charge utile submit_output de l’agent est recueillie dans l’état sous outputKey.
| Champ de configuration | Description |
|---|---|
agentId |
UUID d’agent propre à l’espace de travail à lancer (obligatoire) |
prompt |
Texte d’instruction ; prend en charge la substitution {{key}} (obligatoire) |
outputKey |
Clé d’état de la sortie recueillie |
outputSchema |
Schéma JSON auquel la sortie doit satisfaire |
label |
Libellé lisible affiché sur le canevas |
extras.spaceId |
Conversation existante dans laquelle travailler (par défaut : une conversation masquée) |
extras.conversationTitle |
Fil nommé à l’intérieur de cet espace (facultatif) |
extras.mode |
Lu sur le nœud qui ouvre le salon (messaging.createSpace), pas ici. Valeur par défaut : review. |
pipeline.condition
Section intitulée “pipeline.condition”Corps de routeur — lit config.extras, évalue une condition et retourne StepResult.route(key). Les conditions ne sont pas des expressions JavaScript ; trois formes de rédaction sont prises en charge, par ordre de priorité :
- Arbre de prédicats (
extras.predicate) — un arbre booléen qui achemine vers"true"ou"false". Chaque nœud est une map avec untype:{"type": "fileExists", "paths": [...], "baseKey": "repo_local_path", "negate": false, "recursive": false}— vrai lorsqu’un des chemins listés existe sur le disque ;negatel’inverse ;recursivefouille aussi les sous-répertoires (en ignorant.git,node_modules,build,.dart_tool). Les chemins relatifs sont résolus par rapport àstate[baseKey](par défautrepo_local_path), sinon par rapport au répertoire d’espace de travail propre à l’exécution.{"type": "comparison", "left": "{{score}}", "op": "gt", "right": 80}— opérateursequals,notEquals,contains,exists,notExists,gt,lt; lit l’état du pipeline, pas le système de fichiers.{"type": "and" | "or", "of": [<predicate>, ...]}— groupes booléens.{"type": "not", "of": <predicate>}— négation.
- Switch (
extras.switchKey) —{"switchKey": "pr_class", "cases": [...], "default": "standard"}; achemine vers le premier cas que la valeur d’état contient sans tenir compte de la casse, sinon versdefault. - Comparaison (champs
extras.left/extras.op/extras.righthérités du premier niveau) — équivalent à un prédicatcomparison, conservé pour les modèles rédigés avant l’arbre.
Les arêtes qui sortent du nœud portent une clé routeKey qui doit correspondre à la clé retournée.
prReview.comment
Section intitulée “prReview.comment”Publie les constats consolidés sous forme de commentaire de révision de PR par l’entremise du client de PR GitHub.
| Clé d’état | Description |
|---|---|
repo_full_name |
owner/repo (obligatoire) |
pr_number |
Numéro de PR — entier ou chaîne numérique (obligatoire) |
consolidated_findings |
Corps du commentaire (obligatoire) |
Retourne StepResult.terminal avec comment_review_id et commented_at dans l’état.
messaging.postSpace
Section intitulée “messaging.postSpace”Publie un message dans un espace de messagerie par l’entremise de MessagingPort.
| Clé d’état | Description |
|---|---|
space_id |
Espace cible (obligatoire) |
content |
Corps du message, lu tel quel dans l’état (obligatoire) |
Écrit posted_space_id et posted_at dans l’état.
team.dispatch
Section intitulée “team.dispatch”Lance une équipe entière plutôt qu’un seul agent, puis suspend jusqu’à ce que les exécutions des membres se terminent.
| Champ de configuration | Description |
|---|---|
teamId |
Équipe propre à l’espace de travail à lancer (obligatoire ; mutuellement exclusive avec agentId) |
prompt |
Texte de l’objectif ; prend en charge la substitution {{key}} (obligatoire) |
dispatchMode |
allParallel (par défaut — une tâche par membre) ou manager (ne lancer que le chef pour coordonner) |
outputKey |
Clé d’état des sorties recueillies des membres |
outputSchema, label, extras.spaceId |
Comme pour conversation.promptAgent |
human.gate
Section intitulée “human.gate”Porte d’approbation. Lance l’agent approbateur dans une conversation (mode review) avec le prompt de la porte et un contrat de sortie {decision, reason}, puis suspend jusqu’à ce que l’approbateur soumette par submit_output.
| Champ de configuration | Description |
|---|---|
agentId |
L’agent approbateur (obligatoire) |
prompt |
Prompt de la porte (facultatif ; une valeur par défaut est utilisée en son absence) |
outputKey |
Clé d’état de la charge utile {decision, reason} recueillie |
outputSchema |
Utilise par défaut un schéma d’approbation (decision : approved / rejected, reason) |
label, extras.spaceId |
Comme pour conversation.promptAgent |
La porte ne se résout que par submit_output. BuiltInBodyKeys.humanGate, la bibliothèque de types de nœuds et le texte de prompt d’un modèle intégré nomment tous les outils MCP approve_step et reject_step ; aucun de ces deux outils n’existe dans un paquet ou un registre.
repos.cleanup
Section intitulée “repos.cleanup”Supprime les arbres de travail isolés périmés ; choisit son mode à partir de la charge utile du déclencheur. Respecte l’indicateur dryRun de l’exécution.
| Clé d’état | Description |
|---|---|
ticket_id |
Libère les arbres de travail de ce ticket |
repo_full_name + pr_number |
Libère l’arbre de travail éphémère de l’éditeur de PR |
space_id |
Détruit les arbres de travail et le dossier de cette conversation |
| (aucune des clés ci-dessus) | Balaye l’espace de travail : répertoires disparus, espaces morts, dossiers de conversation orphelins |
outputKey (facultatif) reçoit une chaîne de résumé.
flow.forEach
Section intitulée “flow.forEach”Application / déploiement en éventail sur une collection d’état : rend le prompt une fois par élément, puis lance l’agent une seule fois avec ces prompts concaténés (séparateurs ---) et suspend jusqu’à ce que cette exécution se termine. Une collection vide se termine immédiatement avec une liste vide. Ce n’est pas un lancement parallèle par élément.
| Champ de configuration | Description |
|---|---|
agentId |
Agent à lancer pour chaque élément (obligatoire) |
prompt |
Prompt par élément ; prend en charge la substitution {{key}} (obligatoire) |
extras.iterableKey |
Clé d’état contenant la collection (obligatoire) |
extras.itemKey |
Clé à laquelle l’élément courant est lié dans le prompt (par défaut item) |
outputKey |
Clé d’état de la sortie recueillie (un seul lancement, pas une liste) |
flow.callPipeline
Section intitulée “flow.callPipeline”Exécute un autre modèle de pipeline comme sous-étape imbriquée (le sous-pipeline s’exécute comme exécution enfant avec parentPipelineRunId défini). L’étape suspend jusqu’à ce que l’enfant atteigne un état terminal, puis l’état final de l’enfant est fusionné sous outputKey.
| Champ de configuration | Description |
|---|---|
extras.templateId |
Modèle enfant à exécuter (obligatoire ; un pipeline ne peut pas s’appeler lui-même) |
inputKeys |
Clés d’état copiées dans la charge utile du déclencheur de l’enfant |
outputKey |
Clé d’état de l’état final fusionné de l’enfant |
code.index
Section intitulée “code.index”Indexation tree-sitter en arrière-plan : parcourt un dépôt, extrait les symboles et les arêtes dans des isolats de workers et les intègre au graphe de code de l’espace de travail. Diffuse la progression dans la ligne d’exécution de l’étape ; prend en charge l’exécution à blanc. Tree-sitter et ses grammaires sont des bibliothèques natives obligatoires — une dylib manquante signifie que le serveur n’a jamais démarré, et une grammaire qui ne se résout pas en cours d’exécution fait échouer l’étape plutôt que de l’ignorer.
| Clé d’état | Description |
|---|---|
repo_id |
Dépôt à indexer (obligatoire) |
repo_local_path |
Chemin local de la copie de dépôt (obligatoire) |
meeting.diarize
Section intitulée “meeting.diarize”Diarisation hors ligne des locuteurs. Lit l’audio conservé de la réunion, le regroupe par locuteur (Person 1, Person 2, …), réétiquette les segments de transcription et réécrit l’état transcript ; émet aussi diarization_spans pour l’étape parallèle meeting.updateTranscript. Laisse la transcription intacte lorsqu’aucun audio n’a été conservé ou que les modèles ne sont pas installés.
| Clé d’état | Description |
|---|---|
meeting_id |
Réunion à diariser (obligatoire) |
meeting.saveNotes, meeting.addActionItems, meeting.addDecisions
Section intitulée “meeting.saveNotes, meeting.addActionItems, meeting.addDecisions”Corps déterministes de persistance du résumé de réunion. Chacun lit meeting_id et la charge utile structurée meeting_outcome de l’étape d’agent ({summary, enhancedNotes, actionItems[], decisions[]}) dans l’état et écrit sa partie dans sa propre table — notes, tâches ou décisions. Les lignes déjà enregistrées ne sont jamais effacées lors d’une exécution dégradée.
Autres corps intégrés
Section intitulée “Autres corps intégrés”| Clé de corps | Description |
|---|---|
meeting.identifySpeakers |
Reconnaissance des locuteurs entre réunions : compare les locuteurs diarisés aux profils vocaux enregistrés et applique automatiquement les correspondances sûres. Sans effet en l’absence de profils ou d’embeddings. |
meeting.updateTranscript |
Applique les étiquettes Person N issues de diarization_spans et fusionne les fragments par fenêtre en tours par locuteur, puis conserve la transcription nettoyée. Sans effet lorsque la diarisation n’a rien produit. |
meeting.assemblePlayback |
Assemble la piste de lecture mixée de la réunion à partir des fichiers WAV par canal conservés. Sans effet lorsqu’aucun audio n’a été conservé. |
orchestration.markPhase |
À la fin du DAG de travail, écrit des sentinelles d’échec pour les sous-tickets sans sortie et fait passer l’orchestration à synthesizing. |
orchestration.persistDeliverable |
Écrit la sortie de synthèse dans le ticket parent, le termine, publie le livrable et marque l’orchestration comme completed. |
orchestration.awaitApproval |
Porte d’approbation partielle : se termine immédiatement lorsque la clé de nœud de l’étape figure dans l’ensemble approuvé de l’orchestration, sinon suspend jusqu’à ce qu’une approbation ultérieure la reprenne. C’est le seul corps qui définit extras.awaitApproval, ce qui l’exempte du délai d’expiration de 24 heures des étapes suspendues. |
messaging.createSpace |
Ouvre la conversation dans laquelle travaille le reste du modèle, publiée sous l’outputKey de l’étape (les modèles intégrés utilisent pipeline_space_id). Une liste repoIds vide n’extrait aucun dépôt ; extras['allRepos'] extrait tous les dépôts de l’espace de travail. extras['createConversation'] (désactivé par défaut) ouvre en plus un fil nommé. Une étape d’agent REJOINT un salon et n’en ouvre jamais un, de sorte qu’un éventail sans ce nœud échoue au lancement — et le câbler une fois fait en sorte que tout l’éventail partage une seule copie de dépôt au lieu d’en provisionner une par agent. |
prReview.finalize |
La dernière étape du modèle de révision de PR : classe les nœuds de révision accumulés dans des catégories de consensus et calcule le verdict par PR. |
skills.analyze |
Exécute l’analyse de la chaîne d’approvisionnement des skills sur les skills installés d’un espace de travail et consigne chaque verdict. |
Résultats d’étape
Section intitulée “Résultats d’étape”Un corps d’étape retourne un StepResult (packages/cc_domain/lib/features/pipelines/domain/entities/step_result.dart). Toutes les fabriques acceptent une map facultative mutatedState fusionnée dans l’état du pipeline, sauf failed.
| Fabrique | Signature | Description |
|---|---|---|
ok |
StepResult.ok({mutatedState}) |
Achèvement normal ; les écouteurs en aval sont évalués |
route |
StepResult.route(nextRouterKey, {mutatedState}) |
Achèvement du routeur ; sélectionne la branche en aval dont la clé routeKey correspond |
suspendUntilEvent |
StepResult.suspendUntilEvent(eventType, {mutatedState}) |
Met en pause jusqu’à ce qu’un type d’événement de domaine précis se produise |
suspendUntilTasksComplete |
StepResult.suspendUntilTasksComplete(taskIds, {mutatedState}) |
Met en pause jusqu’à ce que toutes les tâches listées atteignent un état terminal |
terminal |
StepResult.terminal({mutatedState}) |
L’exécution du pipeline est terminée — plus aucune étape |
failed |
StepResult.failed(errorMessage) |
L’étape a échoué avec un message d’erreur |
StepRetryPolicy
Section intitulée “StepRetryPolicy”Comportement de nouvelle tentative pour un corps de nœud en échec (PipelineNodeConfig.retryPolicy ; une valeur nulle désactive les nouvelles tentatives).
| Champ | Type | Description |
|---|---|---|
maxAttempts |
int |
Nombre total de tentatives, la première comprise (3 = 1 essai + 2 nouvelles tentatives) ; doit être ≥ 1 |
backoff |
String |
linear ou exponential (par défaut) |
initialDelayMs |
int |
Délai avant la première nouvelle tentative, en millisecondes ; les délais suivants augmentent selon backoff |
PipelineNodeConfig
Section intitulée “PipelineNodeConfig”Configuration propre à chaque nœud, portée à l’intérieur de la définition de l’étape (packages/cc_domain/lib/features/pipelines/domain/entities/pipeline_node_config.dart). Les nœuds intégrés et personnalisés partagent cette forme ; chaque corps ne lit que les champs qui le concernent.
| Champ | Type | Description |
|---|---|---|
prompt |
String? |
Modèle de prompt avec substitution {{key}} |
script |
String? |
Corps de script bash avec substitution {{key}} |
agentId |
String? |
UUID d’agent propre à l’espace de travail à lancer |
inputKeys |
List<String> |
Clés d’état que ce nœud consomme en entrée |
repoIds |
List<String> |
Dépôts que le nœud messaging.createSpace extrait (<repoId> ou <repoId>@<branch>). Vide = aucun ; extras['allRepos'] = tous les dépôts de l’espace de travail. Ignoré sur les étapes d’agent. |
outputKey |
String? |
Clé d’état sous laquelle la sortie de ce nœud est écrite |
label |
String? |
Libellé lisible affiché sur le canevas (par défaut, l’ID de l’étape) |
outputSchema |
Map<String, dynamic>? |
Schéma JSON (sous-ensemble) auquel la valeur de sortie doit satisfaire |
reducer |
String? |
Stratégie de fusion lorsque des branches parallèles écrivent la même outputKey : append, mergeLists, mergeMaps, sum ou override (par défaut) |
retryPolicy |
StepRetryPolicy? |
Politique de nouvelle tentative en cas d’échec du corps ; nulle = une seule tentative |
continueOnFail |
bool |
Lorsque vrai, un échec terminal ne fait pas échouer l’exécution ; l’erreur est rangée sous state['_stepErrors'][stepId] |
timeoutMs |
int? |
Délai d’expiration en temps réel du corps du nœud, en millisecondes ; nul = aucun délai d’expiration |
teamId |
String? |
Équipe à lancer (nœuds team.dispatch) ; mutuellement exclusive avec agentId |
dispatchMode |
String? |
Mode d’exécution de l’équipe : allParallel ou manager |
extras |
Map<String, dynamic> |
Configuration libre propre au corps (p. ex. iterableKey, templateId, arbres de conditions) |
Accesseurs dérivés : spaceId (extras['spaceId'] — conversation existante dans laquelle travailler), createsConversation (extras['createConversation']), conversationTitle (extras['conversationTitle']), modeName (extras['mode'] — chat / review / plan, lu sur le nœud d’ouverture d’espace) et runWhen (extras['runWhen'] — ignore le nœud lorsque la condition n’est pas remplie).