Étapes de pipeline
Une étape de pipeline possède deux axes indépendants :
- Type (
StepKind) — comment le moteur ordonnance l’étape par rapport à ses déclencheurs. - Clé de corps (
BuiltInBodyKeys) — quelle closure de corps enregistrée exécute le travail de l’étape.
Types d’étape
Section intitulée « Types d’étape »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 lui-même aucun travail ; ce qui démarre l’exécution (manuel / événement / planification) est suivi séparément par les lignes PipelineTrigger. Son corps est le pipeline.trigger sans effet. |
listen |
Se déclenche lorsque toutes les étapes sources (dans StepTrigger.sourceStepIds) sont terminées. |
join |
Se déclenche lorsque toutes les étapes de waitForStepIds atteignent un état terminal. |
router |
Branchement conditionnel — le corps renvoie StepResult.route(key), ce qui ne déclenche que l’arête en aval dont le routeKey correspond ; les branches non sélectionnées sont marquées comme ignorées. |
forEach |
Ordonnancé comme listen (se déclenche lorsque tous les sourceStepIds sont terminés). C’est le corps flow.forEach enregistré 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 »Closures 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, toutes listées ci-dessous. Sauf mention contraire, la configuration est lue dans le PipelineNodeConfig de l’étape (voir ci-dessous) ; 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 pipeline.trigger sans effet ; 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 déclenche 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 par des valeurs échappées pour le shell, issues de l’état et de la charge utile du déclencheur.
| Champ de configuration | Description |
|---|---|
script |
Commande shell à exécuter (obligatoire) |
outputKey |
Clé d’état qui reçoit la sortie standard élaguée lorsque le code de sortie est 0 |
S’exécute avec pour répertoire courant <cc_root>/pipelines/<pipelineRunId>/ (partagé par toutes les étapes bash d’une même exécution) et avec 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 stderr comme message d’erreur.
conversation.promptAgent
Section intitulée « conversation.promptAgent »Nœud générique de prompt et de lancement : il rend le prompt, lance l’agent dans une conversation et suspend l’étape jusqu’à la fin de l’exécution. La charge utile submit_output de l’agent est récupérée dans l’état sous outputKey.
| Champ de configuration | Description |
|---|---|
agentId |
UUID de l’agent à lancer, limité à l’espace de travail (obligatoire) |
prompt |
Texte d’instruction ; prend en charge la substitution {{key}} (obligatoire) |
outputKey |
Clé d’état de la sortie récupérée |
outputSchema |
Schéma JSON auquel la sortie doit satisfaire |
label |
Libellé destiné aux humains, affiché sur le canevas |
extras.spaceId |
Conversation existante dans laquelle travailler (par défaut : une conversation masquée) |
extras.conversationTitle |
Flux nommé à l’intérieur de cet espace (facultatif) |
extras.mode |
Lu sur le nœud qui ouvre la salle (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 renvoie StepResult.route(key). Les conditions ne sont pas des expressions JavaScript ; trois formes d’écriture sont prises en charge, par ordre de priorité :
- Arbre de prédicats (
extras.predicate) — un arbre booléen qui aiguille vers"true"/"false". Chaque nœud est une map dotée d’untype:{"type": "fileExists", "paths": [...], "baseKey": "repo_local_path", "negate": false, "recursive": false}— vrai lorsqu’au moins un des chemins listés existe sur le disque ;negateinverse le résultat ;recursiveexplore 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.
- Aiguillage (
extras.switchKey) —{"switchKey": "pr_class", "cases": [...], "default": "standard"}; aiguille vers le premier cas que la valeur d’état contient sans tenir compte de la casse, sinon versdefault. - Comparaison (anciennes clés de premier niveau
extras.left/extras.op/extras.right) — équivalent à un prédicatcomparison, conservé pour les modèles écrits avant l’arbre.
Les arêtes sortantes du nœud portent un routeKey qui doit correspondre à la clé renvoyée.
prReview.comment
Section intitulée « prReview.comment »Publie les remarques consolidées sous forme de commentaire de revue de PR via le client 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) |
Renvoie 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 via 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 l’étape jusqu’à la fin des exécutions des membres.
| Champ de configuration | Description |
|---|---|
teamId |
Équipe à lancer, limitée à l’espace de travail (obligatoire ; exclusive de 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 de file pour coordonner) |
outputKey |
Clé d’état des sorties récupérées des membres |
outputSchema, label, extras.spaceId |
Comme pour conversation.promptAgent |
human.gate
Section intitulée « human.gate »Portail d’approbation. Lance l’agent approbateur dans une conversation (mode review) avec le prompt du portail et un contrat de sortie {decision, reason}, puis suspend l’étape jusqu’à ce que l’approbateur soumette sa décision via submit_output.
| Champ de configuration | Description |
|---|---|
agentId |
L’agent approbateur (obligatoire) |
prompt |
Prompt du portail (facultatif ; une valeur par défaut est utilisée en son absence) |
outputKey |
Clé d’état de la charge utile {decision, reason} récupérée |
outputSchema |
Par défaut, un schéma d’approbation (decision : approved / rejected, reason) |
label, extras.spaceId |
Comme pour conversation.promptAgent |
Le portail ne se dénoue 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é citent tous les outils MCP approve_step et reject_step ; aucun de ces deux outils n’existe dans le moindre paquet ni registre.
repos.cleanup
Section intitulée « repos.cleanup »Supprime les arbres de travail isolés obsolètes ; 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émonte 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 conversations orphelines |
outputKey (facultatif) reçoit une chaîne de résumé.
flow.forEach
Section intitulée « flow.forEach »Map / éclatement 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 (--- comme séparateurs) et suspend l’étape jusqu’à la fin de cette exécution. Une collection vide se termine immédiatement avec une liste vide. Il ne s’agit pas d’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 récupérée (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 une exécution enfant, avec parentPipelineRunId défini). L’étape est suspendue 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 isolates de worker et les ingère dans le 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 natifs requis — 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 au lieu d’être ignorée.
| Clé d’état | Description |
|---|---|
repo_id |
Dépôt à indexer (obligatoire) |
repo_local_path |
Chemin local du checkout (obligatoire) |
meeting.diarize
Section intitulée « meeting.diarize »Diarisation hors ligne des locuteurs. Lit l’audio conservé de la réunion, le regroupe en locuteurs (Person 1, Person 2, …), réétiquette les segments de la transcription et réécrit l’état transcript ; émet aussi diarization_spans pour l’étape parallèle meeting.updateTranscript. Laisse la transcription inchangée 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 compte rendu 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, puis écrit sa partie dans sa propre table — notes, actions à mener 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 : met en correspondance les locuteurs diarisés avec les profils vocaux enregistrés et applique automatiquement les correspondances fiables. Sans effet en l’absence de profils ou d’empreintes vocales. |
meeting.updateTranscript |
Applique les libellés Person N issus de diarization_spans et fusionne les fragments par fenêtre en tours par locuteur, puis enregistre 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 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 |
Portail 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 reste suspendu jusqu’à ce qu’une approbation ultérieure le 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 la clé outputKey de l’étape (les corps intégrés utilisent pipeline_space_id). Une liste repoIds vide ne récupère aucun dépôt ; extras['allRepos'] récupère tous les dépôts de l’espace de travail. extras['createConversation'] (désactivé par défaut) ouvre aussi un flux nommé. Une étape d’agent REJOINT une salle et n’en ouvre jamais, donc un éclatement en éventail sans ce nœud échoue au lancement — et le câbler une fois signifie que tout l’éclatement partage un seul checkout au lieu d’en provisionner un pour chacun. |
prReview.finalize |
Dernière étape du modèle de revue de PR : trie les nœuds de revue accumulés en catégories de consensus et calcule le verdict de chaque PR. |
skills.analyze |
Exécute l’analyse de chaîne d’approvisionnement des skills sur les skills installés d’un espace de travail et enregistre chaque verdict. |
Résultats d’étape
Section intitulée « Résultats d’étape »Un corps d’étape renvoie un StepResult (packages/cc_domain/lib/features/pipelines/domain/entities/step_result.dart). Toutes les fabriques acceptent une map mutatedState facultative fusionnée dans l’état du pipeline, sauf failed.
| Fabrique | Signature | Description |
|---|---|---|
ok |
StepResult.ok({mutatedState}) |
Terminaison normale ; les écouteurs en aval sont évalués |
route |
StepResult.route(nextRouterKey, {mutatedState}) |
Terminaison du routeur ; sélectionne la branche en aval dont le 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 ; null 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 d’étape (packages/cc_domain/lib/features/pipelines/domain/entities/pipeline_node_config.dart). Les nœuds intégrés comme les nœuds 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 de l’agent à lancer, limité à l’espace de travail |
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 récupère (<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é destiné aux humains, affiché sur le canevas (par défaut, l’identifiant 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 ; null = 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 ; null = aucun délai |
teamId |
String? |
Équipe à lancer (nœuds team.dispatch) ; exclusive de agentId |
dispatchMode |
String? |
Mode d’exécution de l’équipe : allParallel ou manager |
extras |
Map<String, dynamic> |
Configuration propre au corps, de forme libre (par 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 qui ouvre l’espace) et runWhen (extras['runWhen'] — ignore le nœud lorsque la condition n’est pas remplie).