Ir al contenido

Tipos de pasos de pipeline

Un paso de pipeline tiene dos ejes independientes:

  • Tipo (StepKind) — cómo el motor planifica el paso con respecto a sus disparadores.
  • Clave de cuerpo (BuiltInBodyKeys) — qué clausura de cuerpo registrada ejecuta el trabajo del paso.

Se definen en packages/cc_domain/lib/features/pipelines/domain/entities/step_kind.dart.

Tipo Semántica de planificación
trigger Punto de entrada obligatorio — exactamente uno por plantilla, siempre el primer nodo. No hace trabajo por sí mismo; lo que inicia la ejecución (manual / evento / programación) se registra aparte en filas PipelineTrigger. Su cuerpo es pipeline.trigger, que no hace nada.
listen Se activa cuando se completan todos los pasos de origen (en StepTrigger.sourceStepIds).
join Se activa cuando todos los pasos de waitForStepIds alcanzan un estado terminal.
router Ramificación condicional — el cuerpo devuelve StepResult.route(key), que activa únicamente la arista posterior cuyo routeKey coincide; las ramas no seleccionadas se marcan como omitidas.
forEach Se planifica como listen (se activa cuando se completan todos los sourceStepIds). El cuerpo registrado flow.forEach es el que itera; el motor no crea un paso hijo por cada elemento.
terminal Centinela del planificador sin cuerpo. La ejecución del pipeline termina cuando finaliza un paso terminal.

Clausuras de cuerpo registradas e indexadas por las constantes de BuiltInBodyKeys (packages/cc_domain/lib/features/pipelines/domain/templates/builtin_template_seeds.dart). Hay 25, todas enumeradas a continuación. La configuración se lee del PipelineNodeConfig del paso (véase más abajo) salvo que se indique lo contrario; las «claves de estado» se leen del estado del pipeline o del payload del disparador en el momento de la ejecución.

Un paso cuyo bodyKey no está registrado genera un aviso al cargar la plantilla y después nunca se ejecuta: no hace fallar la ejecución. Los pasos trigger y terminal están exentos de ese aviso: el disparador sigue ejecutando el cuerpo pipeline.trigger, que no hace nada, y un paso terminal no tiene cuerpo.

Cuerpo nulo del nodo trigger obligatorio — se completa de inmediato para que el motor despliegue en abanico hacia los escuchadores posteriores. Sin configuración.

Paso de shell sin agente (bash -c). Los marcadores {{key}} del script se sustituyen a partir del estado y del payload del disparador con valores escapados para el shell.

Campo de configuración Descripción
script Comando de shell que se ejecuta (obligatorio)
outputKey Clave de estado que recibe la salida estándar recortada si el código de salida es 0

Se ejecuta con el directorio de trabajo <cc_root>/pipelines/<pipelineRunId>/ (compartido por todos los pasos bash de una ejecución) y GITHUB_TOKEN en el entorno. También escribe <stepId>_runDir en el estado. Un código de salida distinto de cero hace fallar el paso con la cola de stderr.

Nodo genérico de prompt y despacho: renderiza el prompt, despacha al agente a una conversación y se suspende hasta que termina la ejecución. El payload submit_output del agente se recoge en el estado bajo outputKey.

Campo de configuración Descripción
agentId UUID del agente con ámbito de espacio de trabajo que se despacha (obligatorio)
prompt Texto de instrucción; admite sustitución de {{key}} (obligatorio)
outputKey Clave de estado para la salida recogida
outputSchema JSON Schema que debe cumplir la salida
label Etiqueta legible que se muestra en el lienzo
extras.spaceId Conversación existente en la que trabajar (valor predeterminado: una oculta)
extras.conversationTitle Flujo con nombre dentro de ese espacio (opcional)
extras.mode Se lee en el nodo que abre la sala (messaging.createSpace), no aquí. Valor predeterminado, review.

Cuerpo del enrutador — lee config.extras, evalúa una condición y devuelve StepResult.route(key). Las condiciones no son expresiones de JavaScript; se admiten tres formas de escribirlas, por orden de prioridad:

  • Árbol de predicados (extras.predicate) — un árbol booleano que enruta a "true" / "false". Cada nodo es un mapa con un type:
    • {"type": "fileExists", "paths": [...], "baseKey": "repo_local_path", "negate": false, "recursive": false} — es verdadero cuando alguna de las rutas indicadas existe en el disco; negate lo invierte; recursive busca además en los subdirectorios (omitiendo .git, node_modules, build y .dart_tool). Las rutas relativas se resuelven con state[baseKey] (valor predeterminado, repo_local_path) o, si no, con el directorio del espacio de trabajo de cada ejecución.
    • {"type": "comparison", "left": "{{score}}", "op": "gt", "right": 80} — operadores equals, notEquals, contains, exists, notExists, gt y lt; lee el estado del pipeline, no el sistema de archivos.
    • {"type": "and" | "or", "of": [<predicate>, ...]} — grupos booleanos.
    • {"type": "not", "of": <predicate>} — negación.
  • Switch (extras.switchKey) — {"switchKey": "pr_class", "cases": [...], "default": "standard"}; enruta al primer caso cuyo valor del estado contenga sin distinguir mayúsculas y minúsculas o, si no, a default.
  • Comparación (extras.left / extras.op / extras.right heredados de nivel superior) — equivalente a un predicado comparison, que se conserva para las plantillas escritas antes del árbol.

Las aristas que salen del nodo llevan un routeKey que debe coincidir con la clave devuelta.

Publica los hallazgos consolidados como comentario de revisión de PR mediante el cliente de PR de GitHub.

Clave de estado Descripción
repo_full_name owner/repo (obligatorio)
pr_number Número de PR — entero o cadena numérica (obligatorio)
consolidated_findings Cuerpo del comentario (obligatorio)

Devuelve StepResult.terminal con comment_review_id y commented_at en el estado.

Publica un mensaje en un espacio de mensajería mediante MessagingPort.

Clave de estado Descripción
space_id Espacio de destino (obligatorio)
content Cuerpo del mensaje, leído literalmente del estado (obligatorio)

Escribe posted_space_id y posted_at en el estado.

Despacha un equipo entero en lugar de un solo agente y después se suspende hasta que terminan las ejecuciones de sus miembros.

Campo de configuración Descripción
teamId Equipo con ámbito de espacio de trabajo que se despacha (obligatorio; mutuamente excluyente con agentId)
prompt Texto del objetivo; admite sustitución de {{key}} (obligatorio)
dispatchMode allParallel (valor predeterminado — una tarea por miembro) o manager (despacha solo al líder para que coordine)
outputKey Clave de estado para las salidas recogidas de los miembros
outputSchema, label, extras.spaceId Igual que en conversation.promptAgent

Puerta de aprobación. Despacha al agente aprobador a una conversación (modo de revisión) con el prompt de la puerta y un contrato de salida {decision, reason}, y después se suspende hasta que el aprobador responde mediante submit_output.

Campo de configuración Descripción
agentId El agente aprobador (obligatorio)
prompt Prompt de la puerta (opcional; si falta, se usa uno predeterminado)
outputKey Clave de estado para el payload {decision, reason} recogido
outputSchema De forma predeterminada, un esquema de aprobación (decision: approved / rejected, reason)
label, extras.spaceId Igual que en conversation.promptAgent

La puerta se resuelve únicamente mediante submit_output. BuiltInBodyKeys.humanGate, la biblioteca de tipos de nodo y el texto de prompt de una plantilla integrada mencionan las herramientas MCP approve_step y reject_step; ninguna de las dos existe en ningún paquete ni registro.

Elimina worktrees aislados obsoletos; elige su modo a partir del payload del disparador. Respeta la marca dryRun de la ejecución.

Clave de estado Descripción
ticket_id Libera los worktrees de ese ticket
repo_full_name + pr_number Libera el worktree efímero del editor de PR
space_id Desmonta los worktrees y la carpeta de esa conversación
(ninguna de las anteriores) Barre el espacio de trabajo: directorios desaparecidos, espacios muertos, carpetas de conversaciones huérfanas

outputKey (opcional) recibe una cadena de resumen.

Map / fan-out sobre una colección del estado: renderiza el prompt una vez por elemento, después despacha al agente una sola vez con esos prompts concatenados (separados por ---) y se suspende hasta que termina esa ejecución. Una colección vacía se completa de inmediato con una lista vacía. No es un despacho paralelo por elemento.

Campo de configuración Descripción
agentId Agente que se despacha por elemento (obligatorio)
prompt Prompt por elemento; admite sustitución de {{key}} (obligatorio)
extras.iterableKey Clave de estado que contiene la colección (obligatorio)
extras.itemKey Clave a la que se vincula el elemento actual en el prompt (valor predeterminado, item)
outputKey Clave de estado para la salida recogida (un despacho, no una lista)

Ejecuta otra plantilla de pipeline como subpaso anidado (el subpipeline se ejecuta como ejecución hija con parentPipelineRunId establecido). El paso se suspende hasta que el hijo alcanza un estado terminal; entonces, el estado final del hijo se fusiona bajo outputKey.

Campo de configuración Descripción
extras.templateId Plantilla hija que se ejecuta (obligatorio; un pipeline no puede llamarse a sí mismo)
inputKeys Claves de estado que se copian en el payload del disparador del hijo
outputKey Clave de estado para el estado final fusionado del hijo

Indexación con tree-sitter en segundo plano: recorre un repo, extrae símbolos y aristas en isolates de worker y los incorpora al grafo de código del espacio de trabajo. Transmite el progreso a la fila de ejecución del paso; admite simulación. Tree-sitter y sus gramáticas son nativos obligatorios: si falta una dylib, el servidor nunca llegó a arrancar, y una gramática que no se resuelve a mitad de ejecución hace fallar el paso en lugar de omitirlo.

Clave de estado Descripción
repo_id Repo que se indexa (obligatorio)
repo_local_path Ruta local del checkout (obligatorio)

Diarización de hablantes sin conexión. Lee el audio conservado de la reunión, lo agrupa en hablantes (Person 1, Person 2, …), vuelve a etiquetar los segmentos de la transcripción y reescribe el estado transcript; también emite diarization_spans para el paso paralelo meeting.updateTranscript. Deja la transcripción tal cual cuando no se conservó audio o los modelos no están instalados.

Clave de estado Descripción
meeting_id Reunión que se diariza (obligatorio)

meeting.saveNotes, meeting.addActionItems, meeting.addDecisions

Sección titulada «meeting.saveNotes, meeting.addActionItems, meeting.addDecisions»

Cuerpos deterministas que persisten el resumen de la reunión. Cada uno lee del estado meeting_id y el payload estructurado meeting_outcome del paso de agente ({summary, enhancedNotes, actionItems[], decisions[]}) y escribe su parte en su propia tabla: notas, puntos de acción o decisiones. Las filas guardadas previamente nunca se borran en una ejecución degradada.

Clave de cuerpo Descripción
meeting.identifySpeakers Reconocimiento de hablantes entre reuniones: coteja los hablantes diarizados con los perfiles de voz guardados y aplica automáticamente las coincidencias de confianza alta. No hace nada si no hay perfiles ni embeddings.
meeting.updateTranscript Aplica las etiquetas Person N de diarization_spans y fusiona los fragmentos por ventana en turnos por hablante; después persiste la transcripción limpia. No hace nada cuando la diarización no produjo nada.
meeting.assemblePlayback Ensambla la pista de reproducción mezclada de la reunión a partir de los WAV por canal conservados. No hace nada cuando no se conservó audio.
orchestration.markPhase Al final del DAG de trabajo, escribe centinelas de fallo para los subtickets sin salida y cambia la orquestación a synthesizing.
orchestration.persistDeliverable Escribe la salida de síntesis en el ticket principal, lo completa, publica el entregable y marca la orquestación como completed.
orchestration.awaitApproval Puerta de aprobación parcial: se completa de inmediato cuando la clave del nodo del paso está en el conjunto aprobado de la orquestación; de lo contrario, se suspende hasta que una aprobación posterior la reanude. Es el único cuerpo que establece extras.awaitApproval, lo que lo exime del tiempo de espera de 24 horas para pasos suspendidos.
messaging.createSpace Abre la conversación en la que trabaja el resto de la plantilla, publicada bajo el outputKey del paso (los integrados usan pipeline_space_id). Una lista repoIds vacía no hace checkout de nada; extras['allRepos'] hace checkout de todos los repos del espacio de trabajo. extras['createConversation'] (desactivado de forma predeterminada) también abre un flujo con nombre. Un paso de agente SE UNE a una sala y nunca abre ninguna, así que un fan-out sin este nodo falla al despachar, y conectarlo una sola vez hace que todo el fan-out comparta un solo checkout en lugar de aprovisionar uno para cada uno.
prReview.finalize El último paso de la plantilla de revisión de PR: ordena los nodos de revisión acumulados en grupos de consenso y calcula el veredicto de cada PR.
skills.analyze Ejecuta el análisis de la cadena de suministro de las skills instaladas en un espacio de trabajo y registra cada veredicto.

Un cuerpo de paso devuelve un StepResult (packages/cc_domain/lib/features/pipelines/domain/entities/step_result.dart). Todas las factorías aceptan un mapa mutatedState opcional que se fusiona con el estado del pipeline, excepto failed.

Factoría Firma Descripción
ok StepResult.ok({mutatedState}) Finalización normal; se evalúan los escuchadores posteriores
route StepResult.route(nextRouterKey, {mutatedState}) Finalización del enrutador; selecciona la rama posterior cuyo routeKey coincide
suspendUntilEvent StepResult.suspendUntilEvent(eventType, {mutatedState}) Pausa hasta que se dispara un tipo de evento de dominio concreto
suspendUntilTasksComplete StepResult.suspendUntilTasksComplete(taskIds, {mutatedState}) Pausa hasta que todas las tareas indicadas alcanzan un estado terminal
terminal StepResult.terminal({mutatedState}) La ejecución del pipeline ha terminado — no hay más pasos
failed StepResult.failed(errorMessage) El paso ha fallado con un mensaje de error

Comportamiento de reintento para un cuerpo de nodo que falla (PipelineNodeConfig.retryPolicy; null desactiva los reintentos).

Campo Tipo Descripción
maxAttempts int Total de intentos, incluido el primero (3 = 1 intento + 2 reintentos); debe ser ≥ 1
backoff String linear o exponential (valor predeterminado)
initialDelayMs int Retardo antes del primer reintento, en milisegundos; los retardos posteriores crecen según backoff

Configuración por nodo incluida dentro de la definición del paso (packages/cc_domain/lib/features/pipelines/domain/entities/pipeline_node_config.dart). Tanto los nodos integrados como los personalizados comparten esta forma; cada cuerpo lee únicamente los campos que le interesan.

Campo Tipo Descripción
prompt String? Plantilla de prompt con sustitución de {{key}}
script String? Cuerpo de script bash con sustitución de {{key}}
agentId String? UUID del agente con ámbito de espacio de trabajo que se despacha
inputKeys List<String> Claves de estado que este nodo consume como entrada
repoIds List<String> Repos de los que el nodo messaging.createSpace hace checkout (<repoId> o <repoId>@<branch>). Vacío = ninguno; extras['allRepos'] = todos los repos del espacio de trabajo. Se ignora en los pasos de agente.
outputKey String? Clave de estado bajo la que se escribe la salida de este nodo
label String? Etiqueta legible que se muestra en el lienzo (de forma predeterminada, el ID del paso)
outputSchema Map<String, dynamic>? JSON Schema (subconjunto) que debe cumplir el valor de salida
reducer String? Estrategia de fusión cuando ramas paralelas escriben en el mismo outputKey: append, mergeLists, mergeMaps, sum u override (valor predeterminado)
retryPolicy StepRetryPolicy? Política de reintentos cuando falla el cuerpo; null = un solo intento
continueOnFail bool Cuando es true, un fallo terminal no hace fallar la ejecución; el error se guarda en state['_stepErrors'][stepId]
timeoutMs int? Tiempo de espera real para el cuerpo del nodo, en milisegundos; null = sin tiempo de espera
teamId String? Equipo que se despacha (nodos team.dispatch); mutuamente excluyente con agentId
dispatchMode String? Modo de ejecución del equipo: allParallel o manager
extras Map<String, dynamic> Configuración libre específica de cada cuerpo (por ejemplo, iterableKey, templateId, árboles de condiciones)

Accesores derivados: spaceId (extras['spaceId'] — conversación existente en la que trabajar), createsConversation (extras['createConversation']), conversationTitle (extras['conversationTitle']), modeName (extras['mode'] — chat / review / plan, se lee en el nodo que abre el espacio) y runWhen (extras['runWhen'] — omite el nodo cuando no se cumple).