콘텐츠로 이동

Human-in-the-Loop와 자율성 슬라이더

에이전틱 루프를 설계할 때 “얼마나 많은 것을 에이전트에게 맡길 것인가”는 기술적 질문인 동시에 위험 관리 질문이다. 완전 자동화(full autonomy)는 속도와 확장성을 극대화하지만, 에이전트가 되돌리기 어려운 행동(프로덕션 DB 삭제, 대규모 파일 덮어쓰기, 외부 API 호출)을 잘못 수행할 경우 피해가 크다. 반대로 매 스텝마다 인간 승인을 요구하면 자동화의 이점이 사라진다.

**Human-in-the-Loop(HITL)**는 이 양 극단 사이의 어느 지점에 개입 시점을 설계하는 것이다. Anthropic은 이를 **자율성 슬라이더(autonomy slider)**로 시각화한다. 슬라이더를 어디에 놓느냐는 태스크의 위험도, 되돌릴 수 있는지 여부(reversibility), 에이전트에 대한 신뢰 수준에 따라 달라진다.

┌──────────────────────────────────────────────────────────────┐
│ 자율성 슬라이더 │
│ │
│ 완전 수동 ◀─────────────────────────────────▶ 완전 자동 │
│ │
│ ① 매 스텝 승인 매 도구 호출마다 인간 확인 후 실행 │
│ │
│ ② 위험 행동 승인 낮은 위험은 자동, 되돌릴 수 없는 행동만 승인│
│ │
│ ③ 완료 시 검토 루프 자동 실행 후 최종 결과만 인간 검토 │
│ │
│ ④ 완전 자동 검토 없이 전체 자동화 (YOLO 모드) │
└──────────────────────────────────────────────────────────────┘

HITL을 루프에 통합할 때 **승인 게이트(approval gate)**는 네 가지 요소로 구성된다.

첫째, 중단(Pause). 특정 조건에서 루프를 일시 정지한다. 모든 도구 실행 전, 또는 특정 위험 도구(파일 삭제, 외부 API 호출 등)가 요청될 때 중단한다.

둘째, 알림(Notify). 인간에게 현재 상태와 승인이 필요한 행동을 알린다. 알림은 충분한 컨텍스트를 포함해야 한다. “정말 실행할까요?“가 아니라 “현재 auth.py 231번째 줄의 비밀번호 검증 로직을 삭제하려 합니다. 이 변경이 테스트 환경이 아닌 프로덕션 코드에 영향을 줍니다. 승인하시겠습니까?“처럼 구체적이어야 한다.

셋째, 검토(Review). 인간이 계획된 행동을 보고 수락, 수정, 거부 중 하나를 선택한다.

넷째, 재개(Resume). 인간의 결정(수락 또는 수정된 지시)을 컨텍스트에 추가하고 루프를 재개한다.

LangGraph는 이 패턴을 interrupt() 기본 요소로 구현한다. 그래프 노드 실행 중 interrupt()를 호출하면 실행이 멈추고, 인간의 입력을 기다린다. 인간이 값을 제공하면 그 값을 반환값으로 받아 실행을 재개한다.

# 개념 이해용 의사 코드이며 실제 API와 다를 수 있습니다.
# LangGraph의 interrupt() 패턴 (개념 설명용)
from langgraph.types import interrupt
def dangerous_action_node(state: dict) -> dict:
"""
되돌릴 수 없는 행동 전에 인간 승인을 요청하는 노드.
"""
planned_action = state["planned_action"]
# 위험 행동 감지 → 루프 중단 후 인간 확인
if is_irreversible(planned_action):
# interrupt(): 실행 일시 정지, 인간 입력 대기
human_decision = interrupt({
"question": "다음 행동을 실행할까요?",
"action": planned_action,
"risk": "되돌릴 수 없음",
"context": state.get("context_summary", "")
})
if human_decision == "approve":
result = execute(planned_action)
elif human_decision == "reject":
result = {"status": "cancelled_by_human"}
else:
# 인간이 수정한 행동
result = execute(human_decision["modified_action"])
else:
# 안전한 행동은 자동 실행
result = execute(planned_action)
return {**state, "last_result": result}

LangGraph의 checkpointerinterrupt()가 결합되면 루프 상태를 DB에 저장한 채로 무기한 대기할 수 있다. 인간이 몇 시간 후에 응답해도 루프 상태가 유지된다.

멱등성(Idempotency): HITL의 전제 조건

섹션 제목: “멱등성(Idempotency): HITL의 전제 조건”

HITL이 실제로 안전하게 작동하려면 **멱등성(idempotency)**이 전제되어야 한다. 인간이 승인을 취소하거나 루프를 재시작할 때, 이미 실행된 행동이 중복으로 실행되지 않아야 한다.

멱등성을 보장하는 주요 패턴은 다음과 같다.

패턴 설명 예시
멱등 키(Idempotency Key) 각 행동에 고유 ID 부여, 중복 실행 방지 DB INSERT에 UUID 사용
체크 후 실행 행동 전 현재 상태 확인, 이미 적용됐으면 스킵 파일 존재 확인 후 생성
트랜잭션 로그 완료된 행동 기록, 재시작 시 스킵 목록 참조 completed_actions.json
읽기 전용 사전 검사 쓰기 전에 읽기로 현재 상태 파악 수정 전 현재 파일 내용 확인
# 개념 이해용 의사 코드이며 실제 API와 다를 수 있습니다.
import hashlib
def idempotent_execute(action: dict, completed_log: set) -> dict:
"""
멱등성 보장: 이미 완료된 행동은 재실행하지 않음.
"""
# 행동 고유 식별자 생성
action_id = hashlib.sha256(str(action).encode()).hexdigest()[:16]
if action_id in completed_log:
return {"status": "already_done", "action_id": action_id}
result = execute(action)
completed_log.add(action_id)
persist_log(completed_log) # 재시작 대비 영구 저장
return {"status": "done", "result": result, "action_id": action_id}

어떤 자율성 레벨을 선택해야 할까? Anthropic은 작업의 특성에 따라 다음 기준을 권장한다.

작업 특성 권장 자율성 레벨
되돌릴 수 없는 행동 포함 해당 행동 직전 승인 게이트
반복적이고 검증된 패턴 완료 후 검토 또는 완전 자동
새롭거나 불확실한 태스크 주요 의사결정 시점마다 승인
파괴적 행동 (삭제, 배포) 항상 인간 승인
읽기 전용 분석 완전 자동 가능

**완전 자율화(YOLO 모드)**는 잘 이해된 작업, 되돌릴 수 있는 행동만 포함, 충분히 검증된 에이전트에서만 적절하다. Simon Willison은 “YOLO 모드”를 에이전트가 아무 제약 없이 실행되는 위험한 설정으로 명시적으로 경계한다.

지나치게 많은 승인 요청. 모든 사소한 행동에 승인을 요구하면 사용자는 “항상 승인”하는 습관이 생겨, 정작 중요한 승인 시점에도 무비판적으로 통과시킨다. 위험도 기반으로 선별적으로 승인을 요청해야 효과가 있다.

컨텍스트 없는 알림. “다음 행동을 실행할까요?“라는 질문만으로는 인간이 올바른 결정을 내리기 어렵다. 현재 상태, 행동의 영향 범위, 왜 이 행동이 필요한지를 함께 제공해야 한다.

재개 시 멱등성 미보장. 루프를 재시작할 때 이미 완료된 행동이 다시 실행되면, HITL이 오히려 더 큰 피해를 낳을 수 있다.

Human-in-the-Loop는 에이전트의 자율성을 제한하는 것이 아니다. 에이전트가 신뢰를 쌓아가는 과정이다. 처음에는 모든 위험 행동에 승인을 요구하고, 에이전트의 신뢰도가 검증될수록 자율성 슬라이더를 점진적으로 오른쪽으로 옮기는 것이 건전한 배포 전략이다.

퀴즈를 불러오는 중…

참고 자료