파이프라인 단계
파이프라인 단계에는 서로 독립적인 두 축이 있습니다:
- 종류(
StepKind) — 엔진이 트리거를 기준으로 단계를 어떻게 스케줄링 하는지. - 본문 키(
BuiltInBodyKeys) — 등록된 본문 클로저 중 어느 것이 단계의 작업을 실행 하는지.
단계 종류
섹션 제목: “단계 종류”packages/cc_domain/lib/features/pipelines/domain/entities/step_kind.dart에 정의되어 있습니다.
| 종류 | 스케줄링 의미 |
|---|---|
trigger |
필수 진입점으로, 템플릿당 정확히 하나이며 항상 첫 노드입니다. 자체적으로는 아무 작업도 하지 않습니다. 실행을 시작하는 것(수동 / 이벤트 / 스케줄)은 별도로 PipelineTrigger 행으로 추적됩니다. 본문은 아무 일도 하지 않는 pipeline.trigger입니다. |
listen |
모든 소스 단계(StepTrigger.sourceStepIds에 있는)가 완료되면 발화합니다. |
join |
waitForStepIds의 모든 단계가 종료 상태에 도달하면 발화합니다. |
router |
조건 분기로, 본문이 StepResult.route(key)를 반환하면 routeKey가 일치하는 하위 엣지만 발화합니다. 선택되지 않은 분기는 건너뛴 것으로 표시됩니다. |
forEach |
listen처럼 스케줄링됩니다(모든 sourceStepIds가 완료되면 발화). 실제로 반복하는 것은 등록된 flow.forEach 본문이며, 엔진이 항목마다 자식 단계를 하나씩 생성하지는 않습니다. |
terminal |
본문이 없는 스케줄러 센티널입니다. 파이프라인 실행은 terminal 단계가 끝나면 완료됩니다. |
단계 본문
섹션 제목: “단계 본문”BuiltInBodyKeys의 상수로 키가 지정된 등록된 본문 클로저입니다(packages/cc_domain/lib/features/pipelines/domain/templates/builtin_template_seeds.dart). 모두 25개이며 아래에 전부 나열합니다. 별도 언급이 없으면 구성은 단계의 PipelineNodeConfig(아래 참조)에서 읽습니다. “상태 키”는 실행 시점에 파이프라인 상태 / 트리거 페이로드에서 읽습니다.
bodyKey가 등록되지 않은 단계는 템플릿 로드 시 경고를 내고 그 뒤로는 절대 실행되지 않습니다. 실행이 실패하지는 않습니다. trigger와 terminal 단계는 이 경고에서 제외됩니다. 트리거는 여전히 아무 일도 하지 않는 pipeline.trigger 본문을 실행하고, terminal 단계는 본문이 없습니다.
pipeline.trigger
섹션 제목: “pipeline.trigger”필수 trigger 노드의 아무 일도 하지 않는 본문으로, 즉시 완료되어 엔진이 하위 리스너로 팬아웃합니다. 구성이 없습니다.
pipeline.bashScript
섹션 제목: “pipeline.bashScript”에이전트가 필요 없는 셸 단계입니다(bash -c). 스크립트의 {{key}} 플레이스홀더는 상태 + 트리거 페이로드에서 셸 이스케이프된 값으로 치환됩니다.
| 구성 필드 | 설명 |
|---|---|
script |
실행할 셸 명령(필수) |
outputKey |
종료 코드 0일 때 공백이 제거된 stdout을 받는 상태 키 |
cwd <cc_root>/pipelines/<pipelineRunId>/(한 실행의 모든 bash 단계가 공유)와 환경의 GITHUB_TOKEN으로 실행됩니다. 또한 <stepId>_runDir을 상태에 기록합니다. 0이 아닌 종료 코드는 stderr 마지막 부분과 함께 단계를 실패시킵니다.
conversation.promptAgent
섹션 제목: “conversation.promptAgent”범용 프롬프트 및 디스패치 노드입니다. 프롬프트를 렌더링하고, 에이전트를 대화로 디스패치한 뒤 실행이 끝날 때까지 일시 중단합니다. 에이전트의 submit_output 페이로드는 outputKey 아래 상태로 수집됩니다.
| 구성 필드 | 설명 |
|---|---|
agentId |
디스패치할 워크스페이스 범위 에이전트 UUID(필수) |
prompt |
지시 텍스트이며 {{key}} 치환을 지원합니다(필수) |
outputKey |
수집된 출력을 담을 상태 키 |
outputSchema |
출력이 충족해야 하는 JSON Schema |
label |
캔버스에 표시되는 사람이 읽는 레이블 |
extras.spaceId |
작업할 기존 대화(기본값: 숨겨진 대화) |
extras.conversationTitle |
그 스페이스 안의 이름 있는 스트림(선택) |
extras.mode |
방을 여는 노드(messaging.createSpace)에서 읽으며 여기서는 읽지 않습니다. 기본값 review. |
pipeline.condition
섹션 제목: “pipeline.condition”라우터 본문으로, config.extras를 읽고 조건을 평가한 뒤 StepResult.route(key)를 반환합니다. 조건은 JavaScript 표현식이 아닙니다. 우선순위 순으로 세 가지 작성 형태를 지원합니다:
- 프레디킷 트리(
extras.predicate) —"true"/"false"로 라우팅하는 불리언 트리입니다. 각 노드는type이 있는 맵입니다:{"type": "fileExists", "paths": [...], "baseKey": "repo_local_path", "negate": false, "recursive": false}— 나열된 경로 중 하나라도 디스크에 존재하면 true입니다.negate는 이를 뒤집고,recursive는 하위 디렉터리도 검색합니다(.git,node_modules,build,.dart_tool은 건너뜀). 상대 경로는state[baseKey](기본값repo_local_path)를 기준으로 해석하며, 그렇지 않으면 실행별 워크스페이스 디렉터리를 기준으로 합니다.{"type": "comparison", "left": "{{score}}", "op": "gt", "right": 80}— 연산자는equals,notEquals,contains,exists,notExists,gt,lt이며, 파일 시스템이 아니라 파이프라인 상태를 읽습니다.{"type": "and" | "or", "of": [<predicate>, ...]}— 불리언 그룹입니다.{"type": "not", "of": <predicate>}— 부정입니다.
- 스위치(
extras.switchKey) —{"switchKey": "pr_class", "cases": [...], "default": "standard"}; 상태 값이 대소문자를 구분하지 않고 처음으로 포함하는 케이스로 라우팅하고, 없으면default로 갑니다. - 비교(레거시 최상위
extras.left/extras.op/extras.right) —comparison프레디킷과 동등하며, 트리 이전에 작성된 템플릿을 위해 유지됩니다.
노드에서 나가는 엣지는 반환된 키와 일치해야 하는 routeKey를 가집니다.
prReview.comment
섹션 제목: “prReview.comment”GitHub PR 클라이언트를 통해 통합된 발견 사항을 PR 리뷰 댓글로 게시합니다.
| 상태 키 | 설명 |
|---|---|
repo_full_name |
owner/repo(필수) |
pr_number |
PR 번호 — int 또는 숫자 문자열(필수) |
consolidated_findings |
댓글 본문(필수) |
상태에 comment_review_id와 commented_at을 담아 StepResult.terminal을 반환합니다.
messaging.postSpace
섹션 제목: “messaging.postSpace”MessagingPort를 통해 메시징 스페이스에 메시지를 게시합니다.
| 상태 키 | 설명 |
|---|---|
space_id |
대상 스페이스(필수) |
content |
메시지 본문이며 상태에서 그대로 읽습니다(필수) |
posted_space_id와 posted_at을 상태에 기록합니다.
team.dispatch
섹션 제목: “team.dispatch”단일 에이전트 대신 팀 전체를 디스패치한 뒤, 멤버들의 실행이 끝날 때까지 일시 중단합니다.
| 구성 필드 | 설명 |
|---|---|
teamId |
디스패치할 워크스페이스 범위 팀(필수, agentId와 상호 배타적) |
prompt |
목표 텍스트이며 {{key}} 치환을 지원합니다(필수) |
dispatchMode |
allParallel(기본값, 멤버마다 작업 하나) 또는 manager(조정을 위해 리더만 디스패치) |
outputKey |
수집된 멤버 출력을 담을 상태 키 |
outputSchema, label, extras.spaceId |
conversation.promptAgent와 같습니다 |
human.gate
섹션 제목: “human.gate”승인 게이트입니다. 승인자 에이전트를 게이트 프롬프트와 {decision, reason} 출력 계약과 함께 대화(리뷰 모드)로 디스패치한 뒤, 승인자가 submit_output으로 제출할 때까지 일시 중단합니다.
| 구성 필드 | 설명 |
|---|---|
agentId |
승인자 에이전트(필수) |
prompt |
게이트 프롬프트(선택, 없으면 기본값 사용) |
outputKey |
수집된 {decision, reason} 페이로드를 담을 상태 키 |
outputSchema |
기본값은 승인 스키마입니다(decision: approved / rejected, reason) |
label, extras.spaceId |
conversation.promptAgent와 같습니다 |
게이트는 오직 submit_output을 통해서만 해소됩니다. BuiltInBodyKeys.humanGate, 노드 유형 라이브러리, 그리고 내장 템플릿 하나의 프롬프트 텍스트는 모두 approve_step과 reject_step MCP 도구를 언급하지만, 두 도구 모두 어떤 패키지나 레지스트리에도 존재하지 않습니다.
repos.cleanup
섹션 제목: “repos.cleanup”오래된 격리 워크트리를 제거하며, 트리거 페이로드에서 모드를 고릅니다. 실행의 dryRun 플래그를 따릅니다.
| 상태 키 | 설명 |
|---|---|
ticket_id |
해당 티켓의 워크트리를 해제 |
repo_full_name + pr_number |
임시 PR 편집기 워크트리를 해제 |
space_id |
해당 대화의 워크트리와 폴더를 정리 |
| (위 어느 것도 아님) | 워크스페이스를 정리합니다: 사라진 디렉터리, 죽은 스페이스, 고아 대화 폴더 |
outputKey(선택)는 요약 문자열을 받습니다.
flow.forEach
섹션 제목: “flow.forEach”상태 컬렉션에 대한 맵 / 팬아웃입니다. 항목마다 프롬프트를 한 번씩 렌더링한 뒤, 그 프롬프트들을 (--- 구분자로) 이어 붙여 에이전트를 한 번 디스패치하고 그 실행이 끝날 때까지 일시 중단합니다. 빈 컬렉션은 빈 목록과 함께 즉시 완료됩니다. 항목마다 병렬 디스패치를 하나씩 하는 것이 아닙니다.
| 구성 필드 | 설명 |
|---|---|
agentId |
항목마다 디스패치할 에이전트(필수) |
prompt |
항목별 프롬프트이며 {{key}} 치환을 지원합니다(필수) |
extras.iterableKey |
컬렉션을 담은 상태 키(필수) |
extras.itemKey |
프롬프트에서 현재 항목이 바인딩되는 키(기본값 item) |
outputKey |
수집된 출력을 담을 상태 키(목록이 아니라 디스패치 하나) |
flow.callPipeline
섹션 제목: “flow.callPipeline”다른 파이프라인 템플릿을 중첩 하위 단계로 실행합니다(하위 파이프라인은 parentPipelineRunId가 설정된 자식 실행으로 실행됩니다). 단계는 자식이 종료 상태에 도달할 때까지 일시 중단한 뒤, 자식의 최종 상태가 outputKey 아래로 병합됩니다.
| 구성 필드 | 설명 |
|---|---|
extras.templateId |
실행할 자식 템플릿(필수, 파이프라인은 자기 자신을 호출할 수 없음) |
inputKeys |
자식의 트리거 페이로드로 복사되는 상태 키 |
outputKey |
자식의 병합된 최종 상태를 담을 상태 키 |
code.index
섹션 제목: “code.index”백그라운드 tree-sitter 인덱싱입니다. 저장소를 순회하며 심볼/엣지를 워커 아이솔레이트에서 추출하고 워크스페이스 코드 그래프에 넣습니다. 진행 상황을 단계 실행 행으로 스트리밍하며, dry-run을 지원합니다. Tree-sitter와 그 문법은 필수 네이티브입니다. dylib이 없으면 서버가 아예 부팅되지 않았고, 실행 중 문법 확인에 실패하면 건너뛰지 않고 단계를 실패시킵니다.
| 상태 키 | 설명 |
|---|---|
repo_id |
인덱싱할 저장소(필수) |
repo_local_path |
체크아웃의 로컬 경로(필수) |
meeting.diarize
섹션 제목: “meeting.diarize”오프라인 화자 분리입니다. 회의의 보관된 오디오를 읽고 화자(Person 1, Person 2, …)로 클러스터링한 뒤, 전사 세그먼트 레이블을 다시 붙이고 transcript 상태를 다시 씁니다. 병렬 meeting.updateTranscript 단계를 위해 diarization_spans도 내보냅니다. 보관된 오디오가 없거나 모델이 설치되어 있지 않으면 전사를 그대로 통과시킵니다.
| 상태 키 | 설명 |
|---|---|
meeting_id |
화자 분리할 회의(필수) |
meeting.saveNotes, meeting.addActionItems, meeting.addDecisions
섹션 제목: “meeting.saveNotes, meeting.addActionItems, meeting.addDecisions”결정적인 회의 요약 저장 본문입니다. 각각 상태에서 meeting_id와 에이전트 단계의 구조화된 meeting_outcome 페이로드({summary, enhancedNotes, actionItems[], decisions[]})를 읽고 자기 부분을 자체 테이블에 기록합니다. 즉 노트, 실행 항목, 결정 사항입니다. 저하된 실행에서 이전에 저장된 행은 절대 지워지지 않습니다.
기타 내장 본문
섹션 제목: “기타 내장 본문”| 본문 키 | 설명 |
|---|---|
meeting.identifySpeakers |
회의 간 화자 인식입니다. 분리된 화자를 저장된 음성 프로필과 대조하고 신뢰도 높은 일치를 자동 적용합니다. 프로필/임베딩이 없으면 아무 일도 하지 않습니다. |
meeting.updateTranscript |
diarization_spans의 Person N 레이블을 적용하고 창별 조각을 화자별 턴으로 병합한 뒤, 정리된 전사를 영속화합니다. 화자 분리가 아무것도 생성하지 않았으면 아무 일도 하지 않습니다. |
meeting.assemblePlayback |
보관된 채널별 WAV에서 회의의 믹스된 재생 트랙을 구성합니다. 보관된 오디오가 없으면 아무 일도 하지 않습니다. |
orchestration.markPhase |
작업 DAG의 끝에서 출력이 없는 하위 티켓에 실패 센티널을 기록하고 오케스트레이션을 synthesizing으로 전환합니다. |
orchestration.persistDeliverable |
합성 출력을 상위 티켓에 기록하고, 티켓을 완료한 뒤, 산출물을 게시하고 오케스트레이션을 completed로 표시합니다. |
orchestration.awaitApproval |
부분 승인 게이트입니다. 단계의 노드 키가 오케스트레이션의 승인 집합에 들어 있으면 즉시 완료하고, 그렇지 않으면 나중 승인이 재개할 때까지 일시 중단합니다. extras.awaitApproval을 설정하는 유일한 본문이며, 이 때문에 24시간 일시 중단 단계 타임아웃에서 제외됩니다. |
messaging.createSpace |
템플릿의 나머지가 작업하는 대화를 열고, 단계의 outputKey 아래에 게시합니다(내장은 pipeline_space_id 사용). repoIds 목록이 비어 있으면 아무것도 체크아웃하지 않으며, extras['allRepos']는 모든 워크스페이스 저장소를 체크아웃합니다. extras['createConversation'](기본값 꺼짐)은 이름 있는 스트림도 엽니다. 에이전트 단계는 방에 참여할 뿐 방을 열지 않으므로, 이 노드가 없는 팬아웃은 디스패치 시 실패합니다. 그리고 이 노드를 한 번 연결해 두면 팬아웃 전체가 각자 하나씩 프로비저닝하는 대신 하나의 체크아웃을 공유합니다. |
prReview.finalize |
PR 리뷰 템플릿의 마지막 단계입니다. 누적된 리뷰 노드를 합의 버킷으로 분류하고 PR별 판정을 계산합니다. |
skills.analyze |
워크스페이스에 설치된 스킬에 대해 스킬 공급망 검사를 실행하고 각 판정을 기록합니다. |
단계 결과
섹션 제목: “단계 결과”단계 본문은 StepResult를 반환합니다(packages/cc_domain/lib/features/pipelines/domain/entities/step_result.dart). 모든 팩터리는 파이프라인 상태로 병합되는 선택적 mutatedState 맵을 받으며, failed는 예외입니다.
| 팩터리 | 시그니처 | 설명 |
|---|---|---|
ok |
StepResult.ok({mutatedState}) |
정상 완료이며 하위 리스너가 평가됩니다 |
route |
StepResult.route(nextRouterKey, {mutatedState}) |
라우터 완료이며 routeKey가 일치하는 하위 분기를 선택합니다 |
suspendUntilEvent |
StepResult.suspendUntilEvent(eventType, {mutatedState}) |
특정 도메인 이벤트 유형이 발화할 때까지 일시 중단합니다 |
suspendUntilTasksComplete |
StepResult.suspendUntilTasksComplete(taskIds, {mutatedState}) |
나열된 모든 작업이 종료 상태에 도달할 때까지 일시 중단합니다 |
terminal |
StepResult.terminal({mutatedState}) |
파이프라인 실행이 끝났습니다 — 더 이상 단계가 없습니다 |
failed |
StepResult.failed(errorMessage) |
단계가 오류 메시지와 함께 실패했습니다 |
StepRetryPolicy
섹션 제목: “StepRetryPolicy”실패하는 노드 본문의 재시도 동작입니다(PipelineNodeConfig.retryPolicy, null이면 재시도 비활성화).
| 필드 | 유형 | 설명 |
|---|---|---|
maxAttempts |
int |
첫 시도를 포함한 총 시도 횟수(3 = 시도 1회 + 재시도 2회), 1 이상이어야 합니다 |
backoff |
String |
linear 또는 exponential(기본값) |
initialDelayMs |
int |
첫 재시도 전 지연(밀리초), 이후 지연은 backoff에 따라 증가합니다 |
PipelineNodeConfig
섹션 제목: “PipelineNodeConfig”단계 정의 안에 담기는 노드별 구성입니다(packages/cc_domain/lib/features/pipelines/domain/entities/pipeline_node_config.dart). 내장 노드와 사용자 지정 노드 모두 이 형태를 공유하며, 각 본문은 자기가 관심 있는 필드만 읽습니다.
| 필드 | 유형 | 설명 |
|---|---|---|
prompt |
String? |
{{key}} 치환이 있는 프롬프트 템플릿 |
script |
String? |
{{key}} 치환이 있는 Bash 스크립트 본문 |
agentId |
String? |
디스패치할 워크스페이스 범위 에이전트 UUID |
inputKeys |
List<String> |
이 노드가 입력으로 사용하는 상태 키 |
repoIds |
List<String> |
messaging.createSpace 노드가 체크아웃하는 저장소(<repoId> 또는 <repoId>@<branch>). 비어 있으면 없음, extras['allRepos']는 모든 워크스페이스 저장소입니다. 에이전트 단계에서는 무시됩니다. |
outputKey |
String? |
이 노드의 출력이 기록되는 상태 키 |
label |
String? |
캔버스에 표시되는 사람이 읽는 레이블(기본값은 단계 ID) |
outputSchema |
Map<String, dynamic>? |
출력 값이 충족해야 하는 JSON Schema(부분집합) |
reducer |
String? |
병렬 분기가 같은 outputKey에 쓸 때의 병합 전략: append, mergeLists, mergeMaps, sum 또는 override(기본값) |
retryPolicy |
StepRetryPolicy? |
본문 실패 시 재시도 정책, null = 단일 시도 |
continueOnFail |
bool |
true이면 종료 실패가 실행을 실패시키지 않으며, 오류는 state['_stepErrors'][stepId] 아래에 보관됩니다 |
timeoutMs |
int? |
노드 본문의 실제 시간 기준 타임아웃(밀리초), null = 타임아웃 없음 |
teamId |
String? |
디스패치할 팀(team.dispatch 노드), agentId와 상호 배타적 |
dispatchMode |
String? |
팀 실행 모드: allParallel 또는 manager |
extras |
Map<String, dynamic> |
본문별 자유 형식 구성(예: iterableKey, templateId, 조건 트리) |
파생 접근자: spaceId(extras['spaceId'] — 작업할 기존 대화), createsConversation(extras['createConversation']), conversationTitle(extras['conversationTitle']), modeName(extras['mode'] — chat / review / plan, 스페이스를 여는 노드에서 읽음), runWhen(extras['runWhen'] — 충족되지 않으면 노드를 건너뜀).