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.
Tipos de paso
Sección titulada «Tipos de 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. |
Cuerpos de paso
Sección titulada «Cuerpos de paso»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.
pipeline.trigger
Sección titulada «pipeline.trigger»Cuerpo nulo del nodo trigger obligatorio — se completa de inmediato para que el motor despliegue en abanico hacia los escuchadores posteriores. Sin configuración.
pipeline.bashScript
Sección titulada «pipeline.bashScript»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.
conversation.promptAgent
Sección titulada «conversation.promptAgent»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. |
pipeline.condition
Sección titulada «pipeline.condition»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 untype:{"type": "fileExists", "paths": [...], "baseKey": "repo_local_path", "negate": false, "recursive": false}— es verdadero cuando alguna de las rutas indicadas existe en el disco;negatelo invierte;recursivebusca además en los subdirectorios (omitiendo.git,node_modules,buildy.dart_tool). Las rutas relativas se resuelven constate[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}— operadoresequals,notEquals,contains,exists,notExists,gtylt; 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, adefault. - Comparación (
extras.left/extras.op/extras.rightheredados de nivel superior) — equivalente a un predicadocomparison, 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.
prReview.comment
Sección titulada «prReview.comment»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.
messaging.postSpace
Sección titulada «messaging.postSpace»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.
team.dispatch
Sección titulada «team.dispatch»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 |
human.gate
Sección titulada «human.gate»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.
repos.cleanup
Sección titulada «repos.cleanup»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.
flow.forEach
Sección titulada «flow.forEach»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) |
flow.callPipeline
Sección titulada «flow.callPipeline»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 |
code.index
Sección titulada «code.index»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) |
meeting.diarize
Sección titulada «meeting.diarize»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.
Otros cuerpos integrados
Sección titulada «Otros cuerpos integrados»| 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. |
Resultados de paso
Sección titulada «Resultados de paso»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 |
StepRetryPolicy
Sección titulada «StepRetryPolicy»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 |
PipelineNodeConfig
Sección titulada «PipelineNodeConfig»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).