패턴 07. 다중 경로 Judge — 3개 이상의 루트 처리
학습 목표
이 튜토리얼을 마치면 다음을 할 수 있습니다.
- Judge 노드의
route_values에 3개 이상의 값을 선언하고 각각의 엣지를 연결할 수 있다 JUDGE_ROUTE_COVERAGE_ERROR를 이해하고 모든 라우트가 커버됐는지 검증할 수 있다- 하나의 목적지 노드(
finalize)에 여러 엣지가 연결되는 패턴을 설계할 수 있다 - 승인/수정요청/거절 3-way 라우팅 패턴을 구현할 수 있다
- 복잡한 Judge 분기에서 흔히 발생하는
JUDGE_DEAD_END에러를 예방할 수 있다
사전 준비
04_judge_and_loop.md완료 (Judge 기본 이해)06_human_gate.md완료 권장
euleragent agent list
euleragent pattern list
1. 왜 3-way 라우팅이 필요한가?
4 튜토리얼의 Judge는 이진(binary) 결정이었습니다: finalize 또는 revise. 하지만 실제 비즈니스 프로세스는 종종 더 복잡합니다.
PR 코드 리뷰 시나리오:
- approve: 코드가 좋음, 바로 머지
- request_changes: 수정 필요, 개발자에게 피드백 전달
- reject: 근본적인 문제, 재설계 필요
문서 검토 시나리오:
- publish: 즉시 발행
- minor_edit: 가벼운 수정 후 발행
- major_revision: 전면 수정 필요
지원 티켓 처리 시나리오:
- resolve: 해결 완료
- escalate: 다음 레벨로 에스컬레이션
- reject: 지원 범위 밖, 거절
2. 다중 라우트의 규칙
규칙 1: 모든 route_values는 엣지를 가져야 한다
judge:
route_values: [approve, request_changes, reject]
edges:
- from: evaluate
to: finalize
when: "judge.route == approve" # approve 커버
- from: evaluate
to: revise
when: "judge.route == request_changes" # request_changes 커버
- from: evaluate
to: finalize # 같은 목적지도 가능!
when: "judge.route == reject" # reject 커버
규칙 2: 여러 route_value가 같은 목적지로 갈 수 있다
approve와 reject 모두 finalize로 가는 패턴도 유효합니다. (approve는 성공으로 완료, reject는 즉시 종료로 완료)
규칙 3: JUDGE_DEAD_END 방지
Judge 노드에서 출발하는 모든 경로가 결국 finalize에 도달해야 합니다. 도달 불가능한 분기가 있으면 JUDGE_DEAD_END 에러가 발생합니다.
3. PR 리뷰 패턴 설계
PR 코드를 분석하고, 3-way로 평가하는 패턴:
approve → finalize (PR 승인, 머지 안내)
request_changes → revise → PR 수정 → evaluate (루프)
reject → finalize (PR 거절, 재설계 안내)
완전한 흐름:
┌─────────────────────────────────────────────────────────────────┐
│ code.pr_review 패턴 흐름도 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [analyze] │
│ │ PR 코드 분석 (llm/execute) │
│ │ when: true │
│ ▼ │
│ [evaluate] ─────── when: judge.route == approve ───────────────┐
│ │ 3-way 평가 (judge/evaluator_v1) │
│ ├─────── when: judge.route == reject ────────────────────────┤
│ │ when: judge.route == request_changes │
│ ▼ │
│ [draft_feedback] │
│ │ 수정 요청 피드백 작성 (llm/execute) │
│ │ when: true │
│ ▼ │
│ [apply_changes] │
│ │ 수정 사항 적용 (llm/execute) │
│ │ when: true │
│ └────────────────────────► [evaluate] (루프 최대 3회) │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ [FINALIZE] review_result.md 저장 │◄──┘
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
4. YAML 작성
pr_review.yaml 파일을 생성합니다.
id: code.pr_review
version: 1
category: code
description: "PR 코드 리뷰 — 승인/수정요청/거절 3-way Judge 라우팅"
defaults:
# request_changes 루프 최대 3회
max_iterations: 3
max_total_tool_calls: 20
pass_threshold: 0.85
nodes:
# ── 노드 1: analyze ──
- id: analyze
kind: llm
runner:
mode: execute
# PR 분석은 코드 읽기만 허용
exclude_tools: [web.search, web.fetch, shell.exec]
prompt:
system_append: |
당신은 시니어 코드 리뷰어입니다.
제공된 PR(Pull Request)을 분석하세요.
분석 항목:
1. 코드 변경 범위 및 목적
2. 설계 패턴 준수 여부
3. 잠재적 버그 또는 엣지 케이스
4. 테스트 커버리지
5. 성능 영향
6. 보안 고려사항
출력: 구조화된 분석 보고서 (마크다운)
artifacts:
primary: analysis.md
# ── 노드 2: evaluate (3-way Judge) ──
- id: evaluate
kind: judge
judge:
schema: evaluator_v1
# 3개의 route_values — 모두 엣지가 필요!
route_values: [approve, request_changes, reject]
prompt:
system_append: |
당신은 테크 리드입니다. PR 분석을 바탕으로 최종 결정을 내리세요.
결정 기준:
- 'approve' (score >= 0.85):
* 코드 품질이 높고 버그 없음
* 테스트 충분
* 설계 원칙 준수
→ 즉시 머지 가능
- 'request_changes' (score 0.55-0.84):
* 수정 가능한 문제가 있음
* 구체적인 피드백으로 개선 가능
* 전면 재설계는 불필요
→ 수정 후 재검토
- 'reject' (score < 0.55):
* 근본적인 설계 문제
* 수정으로 해결 불가
* 재설계 또는 다른 접근법 필요
→ 이번 PR 종료, 재시작
중요: suggestions에 구체적이고 실행 가능한 피드백을 작성하세요.
# ── 노드 3: draft_feedback ──
# request_changes 라우트에서만 실행
- id: draft_feedback
kind: llm
runner:
mode: execute
exclude_tools: [web.search, web.fetch]
prompt:
system_append: |
코드 리뷰 피드백 문서를 작성하세요.
Judge의 suggestions를 바탕으로 개발자가 이해하기 쉬운
구체적인 수정 요청사항을 작성하세요.
형식:
## 필수 수정사항 (must fix)
## 권장 수정사항 (should fix)
## 선택 개선사항 (nice to have)
각 항목에 코드 예시나 참고 링크를 포함하세요.
artifacts:
primary: feedback.md
# ── 노드 4: apply_changes ──
- id: apply_changes
kind: llm
runner:
mode: execute
exclude_tools: [web.search, web.fetch]
prompt:
system_append: |
피드백(feedback.md)을 반영하여 코드를 수정하세요.
수정된 코드와 변경 사항 요약을 작성하세요.
출력:
1. 수정된 코드 파일 (코드 블록 포함)
2. 변경사항 요약 (무엇을, 왜 변경했는지)
artifacts:
primary: revised_code.md
edges:
# 분석 완료 후 평가
- from: analyze
to: evaluate
when: "true"
# ─── 3-way Judge 라우팅 ───
# approve → finalize (성공 완료)
- from: evaluate
to: finalize
when: "judge.route == approve"
# request_changes → 피드백 작성 → 수정 → 재평가
- from: evaluate
to: draft_feedback
when: "judge.route == request_changes"
# reject → finalize (거절 완료)
# 같은 목적지(finalize)이지만 다른 라우트 — 유효한 패턴
- from: evaluate
to: finalize
when: "judge.route == reject"
# request_changes 루프 연결
- from: draft_feedback
to: apply_changes
when: "true"
- from: apply_changes
to: evaluate
when: "true"
finalize:
artifact: review_result.md
5. 검증
euleragent pattern validate pr_review.yaml
예상 출력:
Validating pattern: pr_review.yaml
Stage 1 (Schema) PASS
Stage 2 (Structural) PASS
Judge node 'evaluate': route_values coverage check
approve → finalize (when: judge.route == approve) ✓
request_changes → draft_feedback (when: judge.route == request_changes) ✓
reject → finalize (when: judge.route == reject) ✓
All 3 route_values covered ✓
Stage 3 (IR Analysis) PASS
Cycle: evaluate → draft_feedback → apply_changes → evaluate (bounded: max_iterations=3) ✓
Dead-end check: all paths reach finalize ✓
approve path: evaluate → finalize ✓
request_changes path: evaluate → draft_feedback → apply_changes → evaluate → ... → finalize ✓
reject path: evaluate → finalize ✓
Validation complete: 0 errors, 0 warnings
6. 컴파일 — 라우트 커버리지 확인
euleragent pattern compile pr_review.yaml
컴파일 출력에서 Judge의 route_coverage 확인:
{
"nodes": {
"evaluate": {
"kind": "judge",
"judge": {
"route_values": ["approve", "request_changes", "reject"],
"route_coverage": {
"approve": {
"covered": true,
"destination": "finalize",
"path_to_finalize": ["finalize"]
},
"request_changes": {
"covered": true,
"destination": "draft_feedback",
"path_to_finalize": ["draft_feedback", "apply_changes", "evaluate", "...finalize"]
},
"reject": {
"covered": true,
"destination": "finalize",
"path_to_finalize": ["finalize"]
}
},
"all_paths_reach_finalize": true
}
}
}
}
7. 실행 시뮬레이션
시나리오 A: approve 경로
cp pr_review.yaml .euleragent/patterns/
euleragent pattern run code.pr_review my-agent \
--task "PR #142: 사용자 인증 미들웨어 리팩토링 — JWT 토큰 검증 로직 개선, 테스트 12개 추가" \
--project default
예상 출력 (approve 경로):
[run:j1a4f8e2] Starting pattern: code.pr_review
✓ analyze Completed (9s)
변경: 3개 파일, +247/-89 lines
테스트: 12개 추가, 커버리지 87% → 94%
✓ evaluate Completed (6s)
score: 0.91 → route: approve
reason: "코드 품질 우수, 테스트 충분, JWT 보안 처리 올바름"
✓ finalize Completed
Review result: APPROVED
Artifact: .euleragent/runs/j1a4f8e2/artifacts/review_result.md
시나리오 B: request_changes 경로
euleragent pattern run code.pr_review my-agent \
--task "PR #143: 결제 처리 모듈 추가 — Stripe 통합, 에러 핸들링 부재" \
--project default
예상 출력 (request_changes 경로):
[run:k2b5g9f3] Starting pattern: code.pr_review
✓ analyze Completed (11s)
문제 발견: 에러 핸들링 부재, 트랜잭션 롤백 미구현
✓ evaluate Completed (7s)
score: 0.62 → route: request_changes
suggestions:
- "결제 실패 시 롤백 로직 추가 (Stripe.error.CardError 처리)"
- "멱등성 키(idempotency key) 구현 — 중복 결제 방지"
- "단위 테스트 추가 (최소 실패 케이스 5개)"
✓ draft_feedback Completed (8s) — feedback.md 작성
✓ apply_changes Completed (14s) — 에러 핸들링 추가, 테스트 작성
✓ evaluate Completed (6s)
score: 0.88 → route: approve
✓ finalize Completed
Review result: APPROVED (after 1 revision)
Artifact: .euleragent/runs/k2b5g9f3/artifacts/review_result.md
시나리오 C: reject 경로
euleragent pattern run code.pr_review my-agent \
--task "PR #144: 전체 인증 시스템 재작성 — 설계 문서 없음, 기존 API 호환성 파괴" \
--project default
예상 출력 (reject 경로):
[run:l3c6h0g4] Starting pattern: code.pr_review
✓ analyze Completed (10s)
심각: 기존 API 34개와 하위 호환성 파괴
✓ evaluate Completed (8s)
score: 0.31 → route: reject
reason: "근본적인 설계 문제 — 하위 호환성 파괴, 마이그레이션 경로 없음"
✓ finalize Completed
Review result: REJECTED
Artifact: .euleragent/runs/l3c6h0g4/artifacts/review_result.md
reject 경로는 draft_feedback, apply_changes 노드를 거치지 않고 즉시 finalize로 이동합니다.
8. JUDGE_ROUTE_COVERAGE_ERROR 의도적으로 발생시키기
reject 라우트에 대한 엣지를 제거해봅니다.
edges:
- from: evaluate
to: finalize
when: "judge.route == approve"
- from: evaluate
to: draft_feedback
when: "judge.route == request_changes"
# reject 엣지 제거!
- from: draft_feedback
to: apply_changes
when: "true"
- from: apply_changes
to: evaluate
when: "true"
검증:
euleragent pattern validate pr_review_broken.yaml
예상 출력:
Stage 2 (Structural) FAIL
ERROR [JUDGE_ROUTE_COVERAGE_ERROR]
Node 'evaluate': route_value 'reject' has no outgoing edge.
All values in route_values must have a corresponding edge
with matching 'when: "judge.route == reject"' condition.
route_values declared: [approve, request_changes, reject]
route_values covered: [approve, request_changes]
route_values missing: [reject]
Fix: Add an edge from 'evaluate' for route 'reject'.
9. JUDGE_DEAD_END 이해하기
JUDGE_DEAD_END는 특정 라우트의 경로가 finalize에 도달하지 못할 때 발생합니다.
# 잘못된 예 — JUDGE_DEAD_END
nodes:
- id: evaluate
kind: judge
judge:
route_values: [approve, request_changes, reject]
- id: dead_end_node # finalize로 가는 엣지가 없는 노드
edges:
- from: evaluate
to: finalize
when: "judge.route == approve"
- from: evaluate
to: draft_feedback
when: "judge.route == request_changes"
- from: evaluate
to: dead_end_node # 이 노드는 finalize에 도달 불가!
when: "judge.route == reject"
- from: draft_feedback
to: evaluate
when: "true"
# dead_end_node에서 다른 노드로 가는 엣지 없음
이 패턴은 JUDGE_ROUTE_COVERAGE_ERROR는 통과하지만 (엣지는 있으므로), Stage 3 IR 분석에서 JUDGE_DEAD_END를 감지합니다.
10. 주요 개념 설명
같은 목적지에 여러 라우트
edges:
- from: evaluate
to: finalize
when: "judge.route == approve" # 성공 완료
- from: evaluate
to: finalize
when: "judge.route == reject" # 거절도 완료
이는 완전히 유효한 패턴입니다. 두 경로 모두 finalize로 가지만, review_result.md에 기록되는 내용이 다릅니다 (approve_message vs reject_message). 런타임은 Judge의 reason을 finalize 아티팩트에 포함합니다.
복잡한 분기에서 max_iterations 계산
request_changes 루프 (evaluate → draft_feedback → apply_changes → evaluate)에서 max_iterations: 3이면:
- 1회: request_changes → 수정 → 재평가
- 2회: request_changes → 수정 → 재평가
- 3회: request_changes → 수정 → 재평가
- 4회: max_iterations 도달 → 강제 finalize
approve나 reject 경로는 루프에 포함되지 않으므로 카운트에 영향을 주지 않습니다.
route_values 순서
route_values의 선언 순서는 라우팅에 영향을 주지 않습니다. Judge LLM이 자율적으로 값을 선택합니다. 순서는 문서화 목적으로만 사용합니다.
11. 실습 과제: PR 리뷰 패턴 확장
과제 1: 보안 리뷰 노드 추가
approve 전에 보안 특화 검토를 추가합니다.
evaluate → approve → security_check → finalize
→ request_changes → (루프)
→ reject → finalize
security_check 노드:
- kind: llm
- 보안 취약점만 집중 검토
- 심각한 보안 문제 발견 시 reject로 다시 보내는 Judge 노드 추가
과제 2: 4-way 라우팅
route_values: [approve, minor_fix, major_revision, reject]
각 라우트 설계:
- approve: 즉시 finalize
- minor_fix: 작은 수정 → apply_changes → finalize (루프 없음)
- major_revision: 전면 수정 → analyze → evaluate (루프)
- reject: 즉시 finalize (거절)
검증 후 실행해보세요.
다음 단계
다중 경로 Judge 패턴을 완성했습니다. 이제 보안과 규정 준수를 위한 특수 패턴을 배웁니다.
- 다음 튜토리얼: 08_airgap_and_ops.md — 외부 네트워크를 완전히 차단하는 에어갭 패턴
- 고급 토폴로지: 09_advanced_patterns.md — 두 개의 독립적인 Judge 사이클