Skip to content

Approval 노드

DAG workflow node는 사람이 게이트를 승인하거나 거절할 때까지 워크플로 실행을 일시 중지하는 approval 필드를 지원합니다. approval node를 사용하면 AI 기반 노드 사이에 사람 검토 단계를 넣을 수 있습니다. 예를 들어 비용이 큰 구현 작업을 시작하기 전에 생성된 계획을 검토할 수 있습니다.

Web UI 사용자: workflow 수준에 interactive: true를 추가하세요. 이 값이 없으면 workflow가 background worker로 dispatch되어 approval gate 메시지가 chat window에 표시되지 않습니다. Web Execution Mode를 참고하세요.

name: plan-approve-implement
description: Plan, get approval, then implement
interactive: true # Required for Web UI: ensures approval gates appear in chat
nodes:
- id: plan
prompt: |
Analyze the codebase and create a detailed implementation plan.
$USER_MESSAGE
- id: review-gate
approval:
message: "Review the plan above before proceeding with implementation."
depends_on: [plan]
- id: implement
command: implement
depends_on: [review-gate]

실행이 review-gate에 도달하면 workflow가 일시 중지되고 사용 중인 플랫폼(CLI, Slack, GitHub 등)으로 사용자에게 메시지를 보냅니다. Web UI에서는 메시지가 chat에 표시되려면 interactive: true가 필요합니다.

  1. Pause: executor가 workflow run status를 paused로 설정하고 approval context(node ID와 message)를 run metadata에 저장합니다.
  2. Notify: approval prompt와 승인/거절 방법이 담긴 메시지를 사용자에게 보냅니다.
  3. Wait: 사용자가 조치를 취할 때까지 workflow는 paused 상태로 유지됩니다. paused run은 worktree path guard를 막으므로 같은 경로에서 다른 workflow가 시작될 수 없습니다.
  4. Approve: 사용자가 승인하면 approval node에 대한 node_completed event가 기록되고 run이 재개 가능한 상태로 전환됩니다. 자연어 메시지(권장)와 CLI는 즉시 auto-resume됩니다. 명시적인 /workflow approve command는 승인을 기록합니다. 재개하려면 후속 메시지를 보내세요.
  5. Reject: 사용자가 거절합니다.
    • on_reject가 없을 때: workflow가 즉시 취소됩니다.
    • on_reject가 있을 때: executor가 $REJECTION_REASON을 치환한 뒤 on_reject.prompt를 AI로 실행하고, 같은 gate에서 다시 일시 중지합니다. 사용자가 승인하거나 on_reject.max_attempts에 도달할 때까지 반복되며, 최대 횟수에 도달하면 workflow가 취소됩니다.
- id: gate-name
approval:
message: "Human-readable prompt shown to the user"
capture_response: true # optional: store comment as $gate-name.output
on_reject: # optional: AI rework on rejection instead of cancel
prompt: "Fix based on feedback: $REJECTION_REASON"
max_attempts: 3 # optional: default 3, range 1–10
depends_on: [upstream-node] # optional
when: "$plan.output != ''" # optional condition
trigger_rule: all_success # optional (default: all_success)
FieldTypeRequiredDescription
approval.messagestringYesworkflow가 일시 중지될 때 사용자에게 표시되는 메시지
approval.capture_responsebooleanNotrue이면 사용자의 approval comment가 downstream node에서 사용할 수 있도록 $<node-id>.output에 저장됩니다. 기본값: false
approval.on_reject.promptstringNo사용자가 거절했을 때 AI로 실행할 prompt template입니다. $REJECTION_REASON은 reject reason으로 치환됩니다. 실행 후 workflow는 같은 gate에서 다시 일시 중지됩니다
approval.on_reject.max_attemptsintegerNoworkflow가 취소되기 전 on_reject prompt가 실행될 수 있는 최대 횟수입니다. 범위: 1–10. 기본값: 3

Approval node는 AI agent를 호출하지 않으므로 AI 전용 필드(model, provider, context, output_format, allowed_tools, denied_tools, hooks, mcp, skills, idle_timeout)를 지원하지 않습니다. (on_reject.prompt는 workflow의 default provider를 사용하는 별도 AI node로 실행됩니다.)

표준 DAG 필드(id, depends_on, when, trigger_rule, retry)는 예상대로 동작합니다.

같은 대화에 답변을 입력하기만 하면 됩니다. 시스템은 paused workflow를 감지하고 메시지를 approval response로 처리합니다.

User: "Looks good, but add error handling for the edge cases"
→ System auto-approves, resumes workflow with your message as $gate.output
(only if capture_response: true is set)

이 방식은 모든 플랫폼(Web, Slack, Telegram, Discord, GitHub)에서 동작합니다.

거절하려면 대신 /workflow reject <run-id>를 사용하세요.

CLI는 non-interactive 방식이므로 명시적인 command를 사용합니다.

Terminal window
# Approve (resumes the workflow immediately)
bun run cli workflow approve <run-id>
bun run cli workflow approve <run-id> --comment "Looks good, proceed"
# Reject
# Without on_reject: cancels the workflow
# With on_reject: records feedback, triggers AI rework, re-pauses
bun run cli workflow reject <run-id>
bun run cli workflow reject <run-id> --reason "Plan needs more test coverage"
/workflow approve <run-id> looks good
/workflow reject <run-id> needs changes

Paused workflow는 dashboard에 amber pulsing badge로 표시됩니다. workflow card에서 Approve 또는 Reject를 직접 클릭하세요.

Terminal window
# Approve
curl -X POST http://localhost:3090/api/workflows/runs/<run-id>/approve \
-H "Content-Type: application/json" \
-d '{"comment": "Approved"}'
# Reject
curl -X POST http://localhost:3090/api/workflows/runs/<run-id>/reject \
-H "Content-Type: application/json" \
-d '{"reason": "Needs revision"}'

기본적으로 사용자의 approval comment는 downstream에서 사용할 수 없습니다. $<node-id>.output은 빈 문자열입니다. comment를 node output으로 캡처하려면 capture_response: true를 설정하세요.

nodes:
- id: gate
approval:
message: "Any special instructions for implementation?"
capture_response: true # Makes the user's comment available as $gate.output
depends_on: [plan]
- id: implement
prompt: |
Implement the plan. User instructions: $gate.output
depends_on: [gate]

capture_response: true가 없으면 downstream node는 $gate.output을 참조하지 않아야 합니다. 값이 빈 문자열이기 때문입니다.

AI 재작업을 포함한 거절(on_reject)

Section titled “AI 재작업을 포함한 거절(on_reject)”

on_reject가 설정되어 있으면 거절이 workflow를 취소하지 않습니다. 대신 executor가 rejection reason을 포함한 AI prompt를 실행하고 같은 gate에서 다시 일시 중지합니다.

- id: review-gate
approval:
message: "Review the implementation plan."
capture_response: true
on_reject:
prompt: |
The reviewer rejected the plan with this feedback: $REJECTION_REASON
Revise the plan to address the feedback, then summarize the changes.
max_attempts: 3 # After 3 rejections, the workflow is cancelled. Default: 3.
depends_on: [plan]

$REJECTION_REASON 변수는 거절한 사용자가 제공한 --reason text로 치환됩니다. AI 재작업 이후 workflow는 다시 일시 중지되어 reviewer가 다시 승인하거나 거절할 수 있습니다.

  1. Workflow가 approval gate에서 일시 중지됩니다
  2. Reviewer가 거절합니다. rejection_count가 증가하고 rejection_reason이 저장됩니다
  3. rejection_count < max_attempts이면 on_reject.prompt가 AI로 실행되고 workflow가 다시 일시 중지됩니다
  4. rejection_count >= max_attempts이면 workflow가 취소됩니다
  • 여러 approval node: 지원됩니다. 각 node가 workflow를 독립적으로 일시 중지합니다.
  • 병렬 layer의 approval: 같은 layer의 다른 node는 정상적으로 완료됩니다. workflow는 layer boundary에서 일시 중지됩니다.
  • paused 상태에서 server restart: run은 database에 유지됩니다. restart 후에도 사용자가 승인하거나 거절할 수 있습니다.
  • paused run 포기: /workflow abandon <id> 또는 dashboard의 Abandon button을 사용하세요.

Approval node는 기존 resume infrastructure(workflow lifecycle PR #871)를 재사용합니다. 승인되면 run이 잠시 failed status를 거쳐 findResumableRun이 이를 잡을 수 있게 합니다. 이렇게 해서 resume logic 중복을 피합니다. metadata.approval_response 필드는 approved-then-resumed run과 실제 failed run을 구분합니다.