콘텐츠로 이동

Human-in-the-Loop 패턴

에이전트 시스템은 완전히 수동인 상태에서 완전히 자율적인 상태까지 연속적인 스펙트럼 위에 존재합니다.

완전 수동 완전 자율
│ │
▼ ▼
[매 단계 승인] → [위험 단계만 승인] → [요약 후 승인] → [자율 실행]
Human-heavy Agent-heavy
안전 ↑ 속도 ↓ 안전 ↓ 속도 ↑

어느 지점이 적합한지는 작업의 가역성, 실패 비용, 에이전트 신뢰도에 따라 결정됩니다.

1. 비가역적 작업 전 (Irreversible Actions)

섹션 제목: “1. 비가역적 작업 전 (Irreversible Actions)”

삭제, 배포, 금전 전송처럼 되돌릴 수 없는 작업 직전에는 반드시 사람의 확인이 필요합니다.

에이전트: "프로덕션 데이터베이스에서 user_id=1234 레코드를 삭제하려 합니다"
[이 작업은 되돌릴 수 없습니다]
사람: [확인 / 취소]

2. 신뢰도 임계값 이하 (Low Confidence)

섹션 제목: “2. 신뢰도 임계값 이하 (Low Confidence)”

에이전트가 자신의 결정에 불확실함을 감지했을 때 스스로 사람에게 도움을 요청합니다.

if agent.confidence_score < 0.7:
result = await request_human_clarification(
question=agent.ambiguity_description,
options=agent.candidate_actions,
)

3. 예상치 못한 상황 (Unexpected State)

섹션 제목: “3. 예상치 못한 상황 (Unexpected State)”

에이전트가 실행 중 예상과 다른 상황을 발견했을 때입니다.

에이전트: "config.yml 파일을 수정하려 했는데,
파일이 이미 수정된 상태입니다 (최근 변경: 5분 전).
계속 덮어쓸까요?"

동일한 작업을 N번 이상 실패했을 때 자동 에스컬레이션합니다.

MAX_RETRIES = 3
if attempt_count >= MAX_RETRIES:
await escalate_to_human(
context=current_task,
failure_log=recent_errors,
message=f"{MAX_RETRIES}번 시도 후 실패했습니다. 도움이 필요합니다."
)

에이전트가 명확한 정책 경계에 도달했을 때입니다.

에이전트: "이 작업은 외부 API 키가 필요하지만
현재 컨텍스트에서 제공받지 못했습니다.
API 키를 제공하거나 작업을 취소해 주세요."
interface HumanReviewRequest {
taskId: string;
agentAction: AgentAction;
reason: string;
urgency: 'low' | 'medium' | 'high';
timeout: number; // ms
}
async function requestHumanReview(req: HumanReviewRequest): Promise<Decision> {
// 슬랙/이메일 등으로 알림 발송
await notify(req);
// 타임아웃 내 응답 대기
const decision = await waitForDecision(req.taskId, req.timeout);
if (!decision) {
// 타임아웃 시 기본 정책 적용
return getDefaultDecision(req.urgency);
}
return decision;
}
function getDefaultDecision(urgency: string): Decision {
// 긴급도에 따라 타임아웃 기본값 결정
// high urgency → 거부 (안전 우선)
// low urgency → 승인 (편의 우선)
return urgency === 'high' ? { approved: false } : { approved: true };
}

에스컬레이션은 승인 레벨을 높이는 방향(더 많은 사람 개입)과 낮추는 방향(더 많은 자율)으로 모두 작동합니다.

트리거방향예시
연속 N회 실패승격 (더 많은 개입)3번 실패 → Manual 전환
연속 M회 성공강등 (더 많은 자율)10번 성공 → Auto 허용
특정 경로 접근즉시 승격/prod/ 접근 → 항상 Manual
신뢰도 점수 저하즉시 승격confidence < 0.5 → 즉시 중단
비정상 속도즉시 승격1분에 50개 이상 도구 호출
class EscalationPolicy:
def __init__(self):
self.consecutive_failures = 0
self.consecutive_successes = 0
self.current_level = ApprovalLevel.SEMI_AUTO
def on_success(self):
self.consecutive_failures = 0
self.consecutive_successes += 1
if self.consecutive_successes >= 10:
self.current_level = ApprovalLevel.AUTO
self.consecutive_successes = 0
def on_failure(self):
self.consecutive_successes = 0
self.consecutive_failures += 1
if self.consecutive_failures >= 3:
self.current_level = ApprovalLevel.MANUAL
self.consecutive_failures = 0
def on_sensitive_path(self, path: str):
if any(path.startswith(p) for p in SENSITIVE_PATHS):
self.current_level = ApprovalLevel.MANUAL

사람의 응답을 기다리는 동안 에이전트가 차단되지 않도록 비동기 패턴을 사용할 수 있습니다.

에이전트 → 승인 요청 발송 → 상태: WAITING_APPROVAL
(다른 독립 작업 계속 진행)
사람 → 승인/거부 응답
에이전트 → 응답 수신 → 원래 작업 재개 또는 취소

이 패턴은 LangGraph의 interrupt() 메커니즘이나 Temporal 같은 워크플로우 엔진으로 구현합니다.

Human-in-the-Loop는 단순히 “사람이 승인하는 것”이 아닙니다. 비가역 작업, 낮은 신뢰도, 예상치 못한 상황, 반복 실패, 정책 경계라는 5가지 트리거를 기반으로 동적으로 자율도를 조정하는 지능형 에스컬레이션 시스템입니다. 에이전트가 스스로 멈추고 질문하도록 설계하는 것이 가장 중요한 원칙입니다.