Etapas de pipeline
Uma etapa de pipeline tem dois eixos independentes:
- Tipo (
StepKind) — como o motor agenda a etapa em relação aos seus gatilhos. - Chave de corpo (
BuiltInBodyKeys) — qual closure de corpo registrada executa o trabalho da etapa.
Tipos de etapa
Seção intitulada “Tipos de etapa”Definidos 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 realiza trabalho algum; o que inicia a execução (manual / evento / agendamento) é rastreado à parte como linhas PipelineTrigger. Seu corpo é o no-op pipeline.trigger. |
listen |
Dispara quando todas as etapas de origem (em StepTrigger.sourceStepIds) são concluídas. |
join |
Dispara quando todas as etapas em waitForStepIds alcançam um estado terminal. |
router |
Ramificação condicional — o corpo retorna StepResult.route(key), que dispara apenas a aresta a jusante cujo routeKey corresponde; os ramos não selecionados são marcados como ignorados. |
forEach |
Agendada como listen (dispara quando todos os sourceStepIds são concluídos). O corpo flow.forEach registrado é o que itera; o motor não gera uma etapa filha por item. |
terminal |
Sentinela do agendador, sem corpo. A execução do pipeline é concluída quando uma etapa terminal termina. |
Corpos das etapas
Seção intitulada “Corpos das etapas”Closures de corpo registradas, 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 do PipelineNodeConfig da etapa (veja abaixo), salvo indicação em contrário; as “chaves de estado” são lidas do estado do pipeline / payload do gatilho no momento da execução.
Uma etapa cujo bodyKey não está registrado produz um aviso no carregamento do modelo e depois nunca é executada — ela não faz a execução falhar. As etapas trigger e terminal são isentas desse aviso: o gatilho ainda executa o corpo no-op pipeline.trigger; uma etapa terminal não tem corpo.
pipeline.trigger
Seção intitulada “pipeline.trigger”Corpo no-op do nó trigger obrigatório — conclui imediatamente para que o motor dispare as etapas de escuta a jusante. Sem configuração.
pipeline.bashScript
Seção intitulada “pipeline.bashScript”Etapa de shell sem agente (bash -c). Os placeholders {{key}} no script são substituídos a partir do estado + payload do gatilho, com valores escapados para o shell.
| Campo de configuração | Descrição |
|---|---|
script |
Comando de shell a executar (obrigatório) |
outputKey |
Chave de estado que recebe o stdout aparado quando a saída é 0 |
Executa com cwd <cc_root>/pipelines/<pipelineRunId>/ (compartilhado por todas as etapas bash de uma execução) e com GITHUB_TOKEN no ambiente. Também grava <stepId>_runDir no estado. Uma saída diferente de zero faz a etapa falhar, com o trecho final do stderr.
conversation.promptAgent
Seção intitulada “conversation.promptAgent”Nó genérico de prompt e despacho: renderiza o prompt, despacha o agente para uma conversa e suspende até que a execução termine. O payload submit_output do agente é coletado no estado sob outputKey.
| Campo de configuração | Descrição |
|---|---|
agentId |
UUID do agente no escopo do espaço de trabalho a despachar (obrigatório) |
prompt |
Texto de instrução; aceita substituição {{key}} (obrigatório) |
outputKey |
Chave de estado do output coletado |
outputSchema |
JSON Schema que o output precisa satisfazer |
label |
Rótulo humano exibido no canvas |
extras.spaceId |
Conversa existente em que trabalhar (padrão: uma oculta) |
extras.conversationTitle |
Fluxo nomeado dentro desse espaço (opcional) |
extras.mode |
Lida no nó que abre a sala (messaging.createSpace), não aqui. Padrão review. |
pipeline.condition
Seção intitulada “pipeline.condition”Corpo de roteador — lê config.extras, avalia uma condição e retorna StepResult.route(key). As condições não são expressões JavaScript; três formas de escrita são aceitas, em ordem de prioridade:
- Árvore de predicados (
extras.predicate) — uma árvore booleana que roteia"true"/"false". Cada nó é um mapa com umtype:{"type": "fileExists", "paths": [...], "baseKey": "repo_local_path", "negate": false, "recursive": false}— verdadeiro quando qualquer caminho listado existe no disco;negateo inverte;recursivetambém pesquisa subdiretórios (ignorando.git,node_modules,build,.dart_tool). Caminhos relativos são resolvidos em relação astate[baseKey](padrãorepo_local_path) ou, na ausência dele, ao diretório do espaço de trabalho por 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 arquivos.{"type": "and" | "or", "of": [<predicate>, ...]}— grupos booleanos.{"type": "not", "of": <predicate>}— negação.
- Switch (
extras.switchKey) —{"switchKey": "pr_class", "cases": [...], "default": "standard"}; roteia para o primeiro caso que o valor de estado contém, sem diferenciar maiúsculas de minúsculas, ou, na ausência dele, paradefault. - Comparação (campos legados de nível superior
extras.left/extras.op/extras.right) — equivalente a um predicadocomparison, mantido para modelos criados antes da árvore.
As arestas que saem do nó carregam um routeKey que precisa corresponder à chave retornada.
prReview.comment
Seção intitulada “prReview.comment”Publica os achados consolidados como um comentário de revisão de PR pelo 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 string numérica (obrigatório) |
consolidated_findings |
Corpo do comentário (obrigatório) |
Retorna StepResult.terminal com comment_review_id e commented_at no estado.
messaging.postSpace
Seção intitulada “messaging.postSpace”Publica uma mensagem em um espaço de mensagens via MessagingPort.
| Chave de estado | Descrição |
|---|---|
space_id |
Espaço de destino (obrigatório) |
content |
Corpo da mensagem, lido literalmente do estado (obrigatório) |
Grava posted_space_id e posted_at no estado.
team.dispatch
Seção intitulada “team.dispatch”Despacha uma equipe inteira em vez de um único agente e depois suspende até que as execuções dos membros terminem.
| Campo de configuração | Descrição |
|---|---|
teamId |
Equipe no escopo do espaço de trabalho a despachar (obrigatório; mutuamente exclusivo com agentId) |
prompt |
Texto do objetivo; aceita substituição {{key}} (obrigatório) |
dispatchMode |
allParallel (padrão — uma tarefa por membro) ou manager (despacha apenas o líder para coordenar) |
outputKey |
Chave de estado dos outputs coletados dos membros |
outputSchema, label, extras.spaceId |
Como em conversation.promptAgent |
human.gate
Seção intitulada “human.gate”Portão de aprovação. Despacha o agente aprovador para uma conversa (modo review) com o prompt do portão e um contrato de saída {decision, reason}, depois suspende até que o aprovador envie via submit_output.
| Campo de configuração | Descrição |
|---|---|
agentId |
O agente aprovador (obrigatório) |
prompt |
Prompt do portão (opcional; um padrão é usado quando ausente) |
outputKey |
Chave de estado do payload {decision, reason} coletado |
outputSchema |
Padrão: um schema de aprovação (decision: approved / rejected, reason) |
label, extras.spaceId |
Como em conversation.promptAgent |
O portão só se resolve por meio de submit_output. BuiltInBodyKeys.humanGate, a biblioteca de tipos de nó e o texto de prompt de um modelo integrado nomeiam ferramentas MCP approve_step e reject_step; nenhuma dessas ferramentas existe em qualquer pacote ou registro.
repos.cleanup
Seção intitulada “repos.cleanup”Remove worktrees isoladas obsoletas; escolhe seu modo a partir do payload do gatilho. Respeita a flag dryRun da execução.
| Chave de estado | Descrição |
|---|---|
ticket_id |
Libera as worktrees desse ticket |
repo_full_name + pr_number |
Libera a worktree efêmera do editor de PR |
space_id |
Derruba as worktrees e a pasta dessa conversa |
| (nenhuma das anteriores) | Varre o espaço de trabalho: diretórios que sumiram, espaços mortos, pastas órfãs de conversas |
outputKey (opcional) recebe uma string de resumo.
flow.forEach
Seção intitulada “flow.forEach”Map / fan-out sobre uma coleção de estado: renderiza o prompt uma vez por item e depois despacha o agente uma única vez, com esses prompts concatenados (separadores ---), e suspende até que essa execução termine. Uma coleção vazia é concluída imediatamente 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; aceita substituição {{key}} (obrigatório) |
extras.iterableKey |
Chave de estado que contém a coleção (obrigatória) |
extras.itemKey |
Chave à qual o item atual é vinculado no prompt (padrão item) |
outputKey |
Chave de estado do output coletado (um despacho, não uma lista) |
flow.callPipeline
Seção intitulada “flow.callPipeline”Executa outro modelo de pipeline como uma subetapa aninhada (o subpipeline roda como uma execução filha com parentPipelineRunId definido). A etapa suspende até que a filha alcance um estado terminal; em seguida, o estado final da filha é mesclado sob outputKey.
| Campo de configuração | Descrição |
|---|---|
extras.templateId |
Modelo filho a executar (obrigatório; um pipeline não pode chamar a si mesmo) |
inputKeys |
Chaves de estado copiadas para o payload do gatilho da filha |
outputKey |
Chave de estado do estado final mesclado da filha |
code.index
Seção intitulada “code.index”Indexação em segundo plano com tree-sitter: percorre um repo, extrai símbolos/arestas em isolates de worker e os ingere no grafo de código do espaço de trabalho. Transmite o progresso para a linha de execução da etapa; aceita dry-run. O tree-sitter e suas gramáticas são natives obrigatórios — uma dylib ausente significa que o servidor nunca iniciou, e uma gramática que falha ao resolver no meio da execução faz a etapa falhar em vez de ignorá-la.
| Chave de estado | Descrição |
|---|---|
repo_id |
Repo a indexar (obrigatório) |
repo_local_path |
Caminho local do checkout (obrigatório) |
meeting.diarize
Seção intitulada “meeting.diarize”Diarização de falantes offline. Lê o áudio retido da reunião, agrupa-o em falantes (Person 1, Person 2, …), reatribui os rótulos dos segmentos da transcrição e reescreve o estado transcript; também emite diarization_spans para a etapa paralela meeting.updateTranscript. Repassa a transcrição inalterada quando nenhum áudio foi retido 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
Seção intitulada “meeting.saveNotes, meeting.addActionItems, meeting.addDecisions”Corpos determinísticos de persistência do resumo de reunião. Cada um lê meeting_id e o payload estruturado meeting_outcome da etapa de agente ({summary, enhancedNotes, actionItems[], decisions[]}) do estado e grava sua parte em sua própria tabela — notas, itens de ação ou decisões. Linhas já salvas nunca são apagadas em uma execução degradada.
Outros corpos integrados
Seção intitulada “Outros corpos integrados”| Chave de corpo | Descrição |
|---|---|
meeting.identifySpeakers |
Reconhecimento de falantes entre reuniões: compara os falantes diarizados com os perfis de voz salvos e aplica automaticamente as correspondências confiáveis. No-op sem perfis/embeddings. |
meeting.updateTranscript |
Aplica os rótulos Person N de diarization_spans e mescla os fragmentos por janela em turnos por falante, depois persiste a transcrição limpa. No-op quando a diarização não produziu nada. |
meeting.assemblePlayback |
Monta a faixa de reprodução mixada da reunião a partir dos WAVs retidos por canal. No-op quando nenhum áudio foi retido. |
orchestration.markPhase |
No fim do DAG de trabalho, grava sentinelas de falha para subtickets sem output e muda a orquestração para synthesizing. |
orchestration.persistDeliverable |
Grava o output da síntese no ticket pai, o conclui, publica o entregável e marca a orquestração como completed. |
orchestration.awaitApproval |
Portão de aprovação parcial: conclui imediatamente quando a chave do nó da etapa está no conjunto aprovado da orquestração; caso contrário, suspende até que uma aprovação posterior a retome. Este é o único corpo que define extras.awaitApproval, o que o isenta do tempo limite de 24 horas para etapas suspensas. |
messaging.createSpace |
Abre a conversa em que o restante do modelo trabalha, publicada sob o outputKey da etapa (os integrados usam pipeline_space_id). Uma lista repoIds vazia não faz checkout de nada; extras['allRepos'] faz checkout de todos os repos do espaço de trabalho. extras['createConversation'] (desativado por padrão) também abre um fluxo nomeado. Uma etapa de agente ENTRA em uma sala e nunca abre uma, então uma distribuição sem esse nó falha no despacho — e conectá-lo uma vez faz com que toda a distribuição compartilhe um único checkout em vez de provisionar um para cada. |
prReview.finalize |
A última etapa do modelo de revisão de PR: classifica os nós de revisão acumulados em grupos de consenso e calcula o veredito da PR. |
skills.analyze |
Executa a varredura de cadeia de suprimentos de skills sobre as skills instaladas de um espaço de trabalho e registra cada veredito. |
Resultados das etapas
Seção intitulada “Resultados das etapas”O corpo de uma etapa retorna um StepResult (packages/cc_domain/lib/features/pipelines/domain/entities/step_result.dart). Todas as factories aceitam um mapa opcional mutatedState mesclado ao estado do pipeline, exceto failed.
| Factory | Assinatura | Descrição |
|---|---|---|
ok |
StepResult.ok({mutatedState}) |
Conclusão normal; as etapas de escuta a jusante são avaliadas |
route |
StepResult.route(nextRouterKey, {mutatedState}) |
Conclusão do roteador; seleciona o ramo a jusante cujo routeKey corresponde |
suspendUntilEvent |
StepResult.suspendUntilEvent(eventType, {mutatedState}) |
Pausa até que um tipo específico de evento de domínio seja emitido |
suspendUntilTasksComplete |
StepResult.suspendUntilTasksComplete(taskIds, {mutatedState}) |
Pausa até que todas as tarefas listadas alcancem um estado terminal |
terminal |
StepResult.terminal({mutatedState}) |
A execução do pipeline terminou — não há mais etapas |
failed |
StepResult.failed(errorMessage) |
A etapa falhou com uma mensagem de erro |
StepRetryPolicy
Seçã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); precisa ser ≥ 1 |
backoff |
String |
linear ou exponential (padrão) |
initialDelayMs |
int |
Atraso antes da primeira nova tentativa, em milissegundos; os atrasos seguintes crescem conforme o backoff |
PipelineNodeConfig
Seção intitulada “PipelineNodeConfig”Configuração por nó carregada dentro da definição da etapa (packages/cc_domain/lib/features/pipelines/domain/entities/pipeline_node_config.dart). Nós integrados e personalizados compartilham esse formato; 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 no escopo do espaço de trabalho a despachar |
inputKeys |
List<String> |
Chaves de estado que este nó consome como entrada |
repoIds |
List<String> |
Repos dos quais o nó messaging.createSpace faz checkout (<repoId> ou <repoId>@<branch>). Vazio = nenhum; extras['allRepos'] = todos os repos do espaço de trabalho. Ignorado em etapas de agente. |
outputKey |
String? |
Chave de estado sob a qual o output deste nó é gravado |
label |
String? |
Rótulo humano exibido no canvas (o padrão é o ID da etapa) |
outputSchema |
Map<String, dynamic>? |
JSON Schema (subconjunto) que o valor de output precisa satisfazer |
reducer |
String? |
Estratégia de mesclagem quando ramos paralelos gravam no mesmo outputKey: append, mergeLists, mergeMaps, sum ou override (padrã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 a execução falhar; o erro é 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? |
Equipe a despachar (nós team.dispatch); mutuamente exclusivo com agentId |
dispatchMode |
String? |
Modo de execução da equipe: allParallel ou manager |
extras |
Map<String, dynamic> |
Configuração livre específica do corpo (por exemplo, iterableKey, templateId, árvores de condição) |
Acessores derivados: spaceId (extras['spaceId'] — conversa existente em que trabalhar), createsConversation (extras['createConversation']), conversationTitle (extras['conversationTitle']), modeName (extras['mode'] — chat / review / plan, lida no nó que abre o espaço) e runWhen (extras['runWhen'] — ignora o nó quando não atendido).