Passos de pipeline
Um passo de pipeline tem dois eixos independentes:
- Tipo (
StepKind) — como o motor agenda o passo em relação aos respetivos disparadores. - Chave de corpo (
BuiltInBodyKeys) — que closure de corpo registada executa o trabalho do passo.
Tipos de passo
Secção intitulada “Tipos de passo”Definido em packages/cc_domain/lib/features/pipelines/domain/entities/step_kind.dart.
| Tipo | Semântica de agendamento |
|---|---|
trigger |
Ponto de entrada obrigatório — exatamente um por modelo, sempre o primeiro nó. Não faz trabalho próprio; o que inicia a execução (manual / evento / agendamento) é registado em separado como linhas PipelineTrigger. O seu corpo é o no-op pipeline.trigger. |
listen |
Dispara quando todos os passos de origem (em StepTrigger.sourceStepIds) terminam. |
join |
Dispara quando todos os passos em waitForStepIds atingem o estado terminal. |
router |
Ramificação condicional — o corpo devolve StepResult.route(key), que dispara apenas a aresta a jusante cuja routeKey corresponde; os ramos não selecionados são marcados como ignorados. |
forEach |
Agendado como listen (dispara quando todos os sourceStepIds terminam). É o corpo registado flow.forEach que itera; o motor não cria um passo filho por item. |
terminal |
Sentinela do agendador sem corpo. A execução do pipeline conclui-se quando um passo terminal termina. |
Corpos dos passos
Secção intitulada “Corpos dos passos”Closures de corpo registadas, indexadas pelas constantes em BuiltInBodyKeys (packages/cc_domain/lib/features/pipelines/domain/templates/builtin_template_seeds.dart). São 25, todas listadas abaixo. A configuração é lida a partir do PipelineNodeConfig do passo (ver abaixo), salvo indicação em contrário; as «chaves de estado» são lidas do estado do pipeline / da carga útil do disparador no momento da execução.
Um passo cujo bodyKey não esteja registado produz um aviso na carga do modelo e depois nunca é executado — não faz falhar a execução. Os passos trigger e terminal estão isentos desse aviso: o disparador continua a executar o corpo no-op pipeline.trigger; um passo terminal não tem corpo.
pipeline.trigger
Secção intitulada “pipeline.trigger”Corpo no-op do nó trigger obrigatório — conclui-se de imediato para que o motor se distribua em leque pelos ouvintes a jusante. Sem configuração.
pipeline.bashScript
Secção intitulada “pipeline.bashScript”Passo de shell sem agente (bash -c). Os marcadores {{key}} no script são substituídos a partir do estado + da carga útil do disparador, com valores escapados para shell.
| Campo de configuração | Descrição |
|---|---|
script |
Comando de shell a executar (obrigatório) |
outputKey |
Chave de estado que recebe o stdout sem espaços em branco na saída com código 0 |
É executado com o cwd <cc_root>/pipelines/<pipelineRunId>/ (partilhado por todos os passos bash de uma execução) e GITHUB_TOKEN no ambiente. Escreve também <stepId>_runDir no estado. Uma saída com código diferente de zero faz falhar o passo com a parte final do stderr.
conversation.promptAgent
Secção intitulada “conversation.promptAgent”Nó genérico de prompt e despacho: processa o prompt, despacha o agente para uma conversa e suspende-se até a execução terminar. A carga útil de submit_output do agente é recolhida para o estado sob outputKey.
| Campo de configuração | Descrição |
|---|---|
agentId |
UUID do agente com âmbito de espaço de trabalho a despachar (obrigatório) |
prompt |
Texto de instrução; suporta substituição {{key}} (obrigatório) |
outputKey |
Chave de estado da saída recolhida |
outputSchema |
JSON Schema que a saída tem de satisfazer |
label |
Etiqueta apresentada no canvas |
extras.spaceId |
Conversa existente onde trabalhar (predefinição: uma conversa oculta) |
extras.conversationTitle |
Fluxo com nome dentro desse espaço (opcional) |
extras.mode |
Lido no nó que abre a sala (messaging.createSpace), não aqui. Predefinição review. |
pipeline.condition
Secção intitulada “pipeline.condition”Corpo de router — lê config.extras, avalia uma condição e devolve StepResult.route(key). As condições não são expressões JavaScript; são suportadas três formas de escrita, por ordem de prioridade:
- Árvore de predicados (
extras.predicate) — uma árvore booleana que encaminha para"true"/"false". Cada nó é um mapa com umtype:{"type": "fileExists", "paths": [...], "baseKey": "repo_local_path", "negate": false, "recursive": false}— verdadeiro quando qualquer dos caminhos listados existe no disco;negateinverte-o;recursiveprocura também em subdiretórios (ignorando.git,node_modules,build,.dart_tool). Os caminhos relativos são resolvidos em relação astate[baseKey](predefiniçãorepo_local_path) ou, na sua ausência, ao diretório do espaço de trabalho de cada execução.{"type": "comparison", "left": "{{score}}", "op": "gt", "right": 80}— operadoresequals,notEquals,contains,exists,notExists,gt,lt; lê o estado do pipeline, não o sistema de ficheiros.{"type": "and" | "or", "of": [<predicate>, ...]}— grupos booleanos.{"type": "not", "of": <predicate>}— negação.
- Switch (
extras.switchKey) —{"switchKey": "pr_class", "cases": [...], "default": "standard"}; encaminha para o primeiro caso que o valor de estado contenha sem distinção de maiúsculas e minúsculas ou, caso contrário, paradefault. - Comparação (as chaves de nível superior antigas
extras.left/extras.op/extras.right) — equivalente a um predicadocomparison, mantida para os modelos escritos antes da árvore.
As arestas que saem do nó transportam uma routeKey que tem de corresponder à chave devolvida.
prReview.comment
Secção intitulada “prReview.comment”Publica as constatações consolidadas como um comentário de revisão da PR através do cliente de PR do GitHub.
| Chave de estado | Descrição |
|---|---|
repo_full_name |
owner/repo (obrigatório) |
pr_number |
Número da PR — int ou cadeia numérica (obrigatório) |
consolidated_findings |
Corpo do comentário (obrigatório) |
Devolve StepResult.terminal com comment_review_id e commented_at no estado.
messaging.postSpace
Secção intitulada “messaging.postSpace”Publica uma mensagem num espaço de mensagens através de MessagingPort.
| Chave de estado | Descrição |
|---|---|
space_id |
Espaço de destino (obrigatório) |
content |
Corpo da mensagem, lido literalmente do estado (obrigatório) |
Escreve posted_space_id e posted_at no estado.
team.dispatch
Secção intitulada “team.dispatch”Despacha uma equipa inteira em vez de um único agente e depois suspende-se até as execuções dos membros terminarem.
| Campo de configuração | Descrição |
|---|---|
teamId |
Equipa com âmbito de espaço de trabalho a despachar (obrigatório; mutuamente exclusivo com agentId) |
prompt |
Texto do objetivo; suporta substituição {{key}} (obrigatório) |
dispatchMode |
allParallel (predefinição — uma tarefa por membro) ou manager (despachar apenas o líder para coordenar) |
outputKey |
Chave de estado das saídas recolhidas dos membros |
outputSchema, label, extras.spaceId |
Como em conversation.promptAgent |
human.gate
Secção intitulada “human.gate”Portão de aprovação. Despacha o agente aprovador para uma conversa (modo de revisão) com o prompt do portão e um contrato de saída {decision, reason} e depois suspende-se até o aprovador submeter através de submit_output.
| Campo de configuração | Descrição |
|---|---|
agentId |
O agente aprovador (obrigatório) |
prompt |
Prompt do portão (opcional; na sua ausência é usado um predefinido) |
outputKey |
Chave de estado da carga útil {decision, reason} recolhida |
outputSchema |
Toma por predefinição um esquema de aprovação (decision: approved / rejected, reason) |
label, extras.spaceId |
Como em conversation.promptAgent |
O portão só resolve através de submit_output. BuiltInBodyKeys.humanGate, a biblioteca de tipos de nó e o texto do prompt de um dos modelos incorporados referem as ferramentas MCP approve_step e reject_step; nenhuma das ferramentas existe em qualquer pacote ou registo.
repos.cleanup
Secção intitulada “repos.cleanup”Remove worktrees isoladas obsoletas; escolhe o modo a partir da carga útil do disparador. Respeita o sinalizador dryRun da execução.
| Chave de estado | Descrição |
|---|---|
ticket_id |
Liberta as worktrees desse ticket |
repo_full_name + pr_number |
Liberta a worktree efémera do editor de PR |
space_id |
Desmantela as worktrees e a pasta dessa conversa |
| (nenhuma das anteriores) | Varre o espaço de trabalho: diretórios desaparecidos, espaços mortos, pastas de conversa órfãs |
outputKey (opcional) recebe uma cadeia de resumo.
flow.forEach
Secção intitulada “flow.forEach”Map / fan-out sobre uma coleção de estado: processa o prompt uma vez por item e depois despacha o agente uma única vez com esses prompts concatenados (separadores ---) e suspende-se até essa execução terminar. Uma coleção vazia conclui-se de imediato com uma lista vazia. Não é um despacho paralelo por item.
| Campo de configuração | Descrição |
|---|---|
agentId |
Agente a despachar por item (obrigatório) |
prompt |
Prompt por item; suporta substituição {{key}} (obrigatório) |
extras.iterableKey |
Chave de estado que contém a coleção (obrigatório) |
extras.itemKey |
Chave à qual o item atual é associado no prompt (predefinição item) |
outputKey |
Chave de estado da saída recolhida (um despacho, não uma lista) |
flow.callPipeline
Secção intitulada “flow.callPipeline”Executa outro modelo de pipeline como subpasso aninhado (o subpipeline corre como uma execução filha com parentPipelineRunId definido). O passo suspende-se até o filho atingir um estado terminal e depois o estado final do filho é integrado sob outputKey.
| Campo de configuração | Descrição |
|---|---|
extras.templateId |
Modelo filho a executar (obrigatório; um pipeline não pode chamar-se a si próprio) |
inputKeys |
Chaves de estado copiadas para a carga útil do disparador do filho |
outputKey |
Chave de estado do estado final integrado do filho |
code.index
Secção intitulada “code.index”Indexação tree-sitter em segundo plano: percorre um repo, extrai símbolos/arestas em isolates de worker e ingere-os no grafo de código do espaço de trabalho. Transmite o progresso para a linha da execução do passo; suporta execução a seco. O tree-sitter e as respetivas gramáticas são nativos obrigatórios — uma dylib em falta significa que o servidor nunca arrancou, e uma gramática que não se resolva a meio da execução faz falhar o passo em vez de o ignorar.
| Chave de estado | Descrição |
|---|---|
repo_id |
Repo a indexar (obrigatório) |
repo_local_path |
Caminho local do checkout (obrigatório) |
meeting.diarize
Secção intitulada “meeting.diarize”Diarização de falantes offline. Lê o áudio retido da reunião, agrupa-o em falantes (Person 1, Person 2, …), reetiqueta os segmentos da transcrição e reescreve o estado transcript; emite também diarization_spans para o passo paralelo meeting.updateTranscript. Deixa passar a transcrição inalterada quando não foi retido áudio ou os modelos não estão instalados.
| Chave de estado | Descrição |
|---|---|
meeting_id |
Reunião a diarizar (obrigatório) |
meeting.saveNotes, meeting.addActionItems, meeting.addDecisions
Secção intitulada “meeting.saveNotes, meeting.addActionItems, meeting.addDecisions”Corpos determinísticos de persistência do resumo da reunião. Cada um lê meeting_id e a carga útil estruturada meeting_outcome do passo do agente ({summary, enhancedNotes, actionItems[], decisions[]}) a partir do estado e escreve a sua parte na sua própria tabela — notas, itens de ação ou decisões. As linhas guardadas anteriormente nunca são eliminadas numa execução degradada.
Outros corpos incorporados
Secção intitulada “Outros corpos incorporados”| Chave do corpo | Descrição |
|---|---|
meeting.identifySpeakers |
Reconhecimento de falantes entre reuniões: compara os falantes diarizados com perfis de voz guardados e aplica automaticamente as correspondências com confiança. No-op sem perfis/embeddings. |
meeting.updateTranscript |
Aplica as etiquetas Person N de diarization_spans e funde fragmentos por janela em turnos por falante, e depois persiste a transcrição limpa. No-op quando a diarização não produziu nada. |
meeting.assemblePlayback |
Monta a faixa de reprodução misturada da reunião a partir dos WAV por canal retidos. No-op quando não foi retido áudio. |
orchestration.markPhase |
No fim do DAG de trabalho, escreve sentinelas de falha para sub-tickets sem saída e muda a orquestração para synthesizing. |
orchestration.persistDeliverable |
Escreve a saída da síntese no ticket principal, conclui-o, publica a entrega e marca a orquestração como completed. |
orchestration.awaitApproval |
Portão de aprovação parcial: conclui-se de imediato quando a chave do nó do passo está no conjunto aprovado da orquestração; caso contrário, suspende-se até uma aprovação posterior o retomar. É o único corpo que define extras.awaitApproval, o que o isenta do tempo limite de 24 horas para passos suspensos. |
messaging.createSpace |
Abre a conversa em que o resto do modelo trabalha, publicada sob o outputKey do passo (os incorporados usam pipeline_space_id). Uma lista repoIds vazia não faz nenhum checkout; extras['allRepos'] faz o checkout de todos os repos do espaço de trabalho. extras['createConversation'] (desativado por predefinição) abre também um fluxo com nome. Um passo de agente JUNTA-SE a uma sala e nunca abre nenhuma, pelo que um fan-out sem este nó falha no despacho — e ligá-lo uma vez significa que todo o fan-out partilha um único checkout em vez de aprovisionar um para cada. |
prReview.finalize |
O último passo do modelo de revisão de PR: ordena os nós de revisão acumulados em agrupamentos de consenso e calcula o veredicto da PR. |
skills.analyze |
Executa a análise à cadeia de abastecimento das skills instaladas num espaço de trabalho e regista cada veredicto. |
Resultados dos passos
Secção intitulada “Resultados dos passos”O corpo de um passo devolve um StepResult (packages/cc_domain/lib/features/pipelines/domain/entities/step_result.dart). Todos os construtores aceitam um mapa opcional mutatedState integrado no estado do pipeline, exceto failed.
| Construtor | Assinatura | Descrição |
|---|---|---|
ok |
StepResult.ok({mutatedState}) |
Conclusão normal; os ouvintes a jusante são avaliados |
route |
StepResult.route(nextRouterKey, {mutatedState}) |
Conclusão do router; seleciona o ramo a jusante cuja routeKey corresponde |
suspendUntilEvent |
StepResult.suspendUntilEvent(eventType, {mutatedState}) |
Pausa até um tipo específico de evento de domínio disparar |
suspendUntilTasksComplete |
StepResult.suspendUntilTasksComplete(taskIds, {mutatedState}) |
Pausa até todas as tarefas listadas atingirem o estado terminal |
terminal |
StepResult.terminal({mutatedState}) |
A execução do pipeline terminou — não há mais passos |
failed |
StepResult.failed(errorMessage) |
O passo falhou com uma mensagem de erro |
StepRetryPolicy
Secção intitulada “StepRetryPolicy”Comportamento de nova tentativa para o corpo de um nó que falha (PipelineNodeConfig.retryPolicy; null desativa as novas tentativas).
| Campo | Tipo | Descrição |
|---|---|---|
maxAttempts |
int |
Total de tentativas, incluindo a primeira (3 = 1 tentativa + 2 novas tentativas); tem de ser ≥ 1 |
backoff |
String |
linear ou exponential (predefinição) |
initialDelayMs |
int |
Atraso antes da primeira nova tentativa, em milissegundos; os atrasos seguintes crescem segundo backoff |
PipelineNodeConfig
Secção intitulada “PipelineNodeConfig”Configuração por nó transportada dentro da definição do passo (packages/cc_domain/lib/features/pipelines/domain/entities/pipeline_node_config.dart). Tanto os nós incorporados como os personalizados partilham esta forma; cada corpo lê apenas os campos que lhe interessam.
| Campo | Tipo | Descrição |
|---|---|---|
prompt |
String? |
Modelo de prompt com substituição {{key}} |
script |
String? |
Corpo de script bash com substituição {{key}} |
agentId |
String? |
UUID do agente com âmbito de espaço de trabalho a despachar |
inputKeys |
List<String> |
Chaves de estado que este nó consome como entrada |
repoIds |
List<String> |
Repos que o nó messaging.createSpace faz checkout (<repoId> ou <repoId>@<branch>). Vazio = nenhum; extras['allRepos'] = todos os repos do espaço de trabalho. Ignorado nos passos de agente. |
outputKey |
String? |
Chave de estado sob a qual é escrita a saída deste nó |
label |
String? |
Etiqueta apresentada no canvas (predefinição: o ID do passo) |
outputSchema |
Map<String, dynamic>? |
JSON Schema (subconjunto) que o valor de saída tem de satisfazer |
reducer |
String? |
Estratégia de integração quando ramos paralelos escrevem o mesmo outputKey: append, mergeLists, mergeMaps, sum ou override (predefinição) |
retryPolicy |
StepRetryPolicy? |
Política de novas tentativas em caso de falha do corpo; null = uma única tentativa |
continueOnFail |
bool |
Quando verdadeiro, uma falha terminal não faz falhar a execução; o erro fica guardado em state['_stepErrors'][stepId] |
timeoutMs |
int? |
Tempo limite em tempo real para o corpo do nó, em milissegundos; null = sem tempo limite |
teamId |
String? |
Equipa a despachar (nós team.dispatch); mutuamente exclusivo com agentId |
dispatchMode |
String? |
Modo de execução da equipa: allParallel ou manager |
extras |
Map<String, dynamic> |
Configuração específica do corpo, de forma livre (por exemplo, iterableKey, templateId, árvores de condições) |
Acessores derivados: spaceId (extras['spaceId'] — conversa existente onde trabalhar), createsConversation (extras['createConversation']), conversationTitle (extras['conversationTitle']), modeName (extras['mode'] — chat / review / plan, lido no nó que abre o espaço) e runWhen (extras['runWhen'] — ignora o nó quando não se verifica).