콘텐츠로 이동

Human-in-the-loop AI 워크플로 아키텍처

분류: Layer 12 - AI 시스템 & LLM 애플리케이션 | 선수지식: L12-60 (Agent), L12-80 (Security), L12-110/120 (State/Durable workflow)

Human-in-the-loop AI 워크플로 아키텍처 — Approval, Review, Escalation, Audit

섹션 제목: “Human-in-the-loop AI 워크플로 아키텍처 — Approval, Review, Escalation, Audit”

Human-in-the-loop(HITL, 실행 중 사람의 판단을 넣는 구조) AI 워크플로는 LLM(Large Language Model, 대규모 언어 모델)이나 agent가 모든 결정을 자동 실행하지 않고, 위험·불확실성·권한 경계가 있는 지점에서 **사람의 승인(Approval), 수정(Correction), 후보 선택(Selection), 상위 책임자 이관(Escalation)**을 받도록 설계한 실행 구조다. 핵심은 마지막 버튼이 아니라, 사람의 판단 전후로 실행을 멈추고 승인 대상·근거·권한·결과를 이어 붙이는 실행 신뢰 경계다.

  • 권한 있는 action 보호: 결제, 삭제, 고객 발송, 배포 같은 action은 모델 판단만으로 실행하면 위험하다.
  • 불확실성 흡수: confidence가 낮거나 근거가 부족한 경우 사람에게 올려 품질을 보장한다.
  • 책임 소재: “누가 무엇을 보고 승인했는가”가 감사 로그로 남아야 한다.
  • 학습 데이터 생성: 사람 수정은 검수 후 gold dataset(사람이 검수한 기대 결과와 판정 근거를 가진 평가 데이터셋)과 preference data 후보가 된다.
  • UX 신뢰: 사용자는 agent가 멈춘 이유와 필요한 입력을 이해해야 한다.

2.5 선행 기술의 한계 — 완전 자동 agent와 마지막 Approve 버튼의 위험

섹션 제목: “2.5 선행 기술의 한계 — 완전 자동 agent와 마지막 Approve 버튼의 위험”

완전 자동 agent는 낮은 위험의 반복 작업에서는 유용하지만, 외부 발송·삭제·결제·배포처럼 되돌리기 어려운 action에서는 모델의 confidence만으로 책임 경계를 세울 수 없다. 반대로 초기 HITL 구현처럼 마지막에 Approve / Reject 버튼만 붙이는 방식도 충분하지 않다. 버튼은 결정 입력 UI일 뿐이고, 그 버튼을 누를 수 있는 권한, 승인자가 실제로 본 대상, 수정 뒤 재승인, 대기 만료, 실행 결과 연결까지 보장하지 않는다.

  • 검색 결과가 부족하면 사람이 query를 고쳐야 한다.
  • tool call 전에 권한과 파라미터를 승인해야 한다.
  • draft가 애매하면 직접 수정 후 이어가야 한다.
  • 일정 시간이 지나면 다른 담당자에게 escalate해야 한다.
  • 승인자가 본 context와 최종 실행 payload가 같았는지 기록해야 한다.

그래서 Approval·Correction·Selection·Escalation은 서로 다른 버튼 집합보다 넓다. 이들은 각각 외부 효과 허가, 제안 내용 변경, 허용 후보 확정, 판단 권한과 책임의 이동을 뜻한다. 어느 개입이든 durable state(프로세스가 재시작돼도 복원되는 상태)를 바꾸며, 이후 실행이 참조할 결정 기록을 새로 만든다. 수정된 내용을 재승인하지 않거나, 에스컬레이션 뒤 이전 담당자의 권한으로 resume하면 사람을 넣었어도 신뢰 경계는 끊긴다.

LangGraph의 interrupt는 graph 실행 중 특정 지점에서 멈추고 persistence로 상태를 보존한 뒤, Command(resume=...)로 이어가는 패턴을 제공한다. 이때 resume은 interrupt 다음 줄에서 단순히 계속되는 것이 아니라 interrupt가 발생한 node를 처음부터 다시 실행한다. 따라서 node 안에서 interrupt보다 앞에 있는 코드는 다시 실행될 수 있다. 이메일 발송·결제 같은 side effect는 가능한 한 interrupt 뒤로 옮기고, 앞에 둘 수밖에 없다면 재실행돼도 결과가 하나로 수렴하도록 idempotent하게 만들어야 한다. 뒤의 adapter crash/retry 예제와 Silent Failure 절에서 이 경계를 다시 확인한다. 출처: LangGraph human-in-the-loop interrupts. Temporal은 실행 중인 Workflow에 쓰기 메시지를 전달하는 Signal과 Update를 제공한다. Signal은 비동기 쓰기 요청이고, Update는 결과나 오류를 기다릴 수 있는 추적 가능한 동기 쓰기 요청이다. 출처: Temporal Workflow message passing.

둘은 자동 연동되지 않는다. LangGraph interrupt를 Temporal Signal/Update가 알아서 resume하는 것이 아니라 application adapter가 둘을 연결한다. 이때 canonical payload는 schema에 따라 같은 의미를 항상 같은 bytes로 직렬화한 승인 대상이고, effective intent는 그 payload에 tenant·실행 주체/권한·action·resource·schema/policy version을 더한 실제 실행 의미다. **idempotency key(멱등성 키)**는 같은 business operation의 재시도를 식별해 하나의 결과에 연결하는 값이다. 한 프레임워크만 쓸 수도 있고 둘을 함께 쓸 수도 있지만, 어느 경우든 승인 대상 불변성, 권한 재검증, evidence 보존, message-to-resume 상관관계는 애플리케이션 계약이다.

3.1 HITL 개입 유형 비교

Approval

사람의 역할: 실행 허가/거부

예시: "이 이메일을 고객에게 보낼까요?"

Correction

사람의 역할: 모델 산출물 수정

예시: 요약 문장 직접 편집

Selection

사람의 역할: 여러 후보 중 선택

예시: 검색 결과/계획 후보 선택

Escalation

사람의 역할: 더 높은 권한자에게 전달

예시: 환불 금액이 기준 초과

실무 HITL은 modal 하나가 아니라 승인 큐로 운영된다. agent가 멈춘 workflow를 queue item으로 만들고, reviewer가 SLA(Service Level Agreement, 검토 기한에 대한 운영 약속) 안에 승인·수정·거절·에스컬레이션한다.

[Agent workflow]
↓ risk/confidence/policy decision
[Approval Queue]
↓ assign reviewer + deadline + context package
[Review UI]
↓ approve / edit / reject / escalate
[Resume workflow]
↓ canonical payload + effective intent 재계산 비교
[Execution result + audit log + gold dataset candidate]

queue item에는 최소 필드가 필요하다.

필드의미
workflow_idresume할 durable workflow 식별자
risk_levellow/medium/high 또는 점수. SLA·권한·escalation 기준
approval_payload_ref승인자가 보는 canonical approval payload의 덮어쓰지 않는 durable artifact 참조
approval_payload_versionedit마다 증가하는 승인 대상 버전. 결정은 정확히 한 버전에만 유효
payload_hash정규화된 payload bytes의 digest(고정 길이 요약값). ref/version 비교를 보조하지만 단독 불변성 보장은 아님
effective_intenttenant·요청 주체/권한·action·resource·schema/policy version을 묶은 실제 실행 의미
resource_version, precondition승인자가 본 mutable resource 버전과 실행 시 conditional write 조건
generation_provenance조립된 system/user input, sampling, tool/schema, provider route, response까지 잇는 생성 계보
context_summaryreviewer가 볼 결정 요약. 승인 근거 artifact의 대체물이 아님
evidence_artifacts[]artifact_ref가 필수인 근거 묶음. 재현할 version과 digest를 함께 보존
trace_id진단용 trace와 연결하는 선택적 join key. 승인 근거 자체가 아님
deadline_at, expires_at검토 SLA 시각과 승인 후 설정하는 decision 효력 만료 시각
assignee_policyowner, on-call, domain expert, manager 등 배정 규칙
review_event, decision_stateedit를 포함한 검토 event와 pending/approved/rejected/escalated/expired 최종 상태
calibration_outcomereviewer 간 판정 일치도·품질 샘플링에 쓰는 후처리 라벨

운영 규칙:

  • queue는 risk_level, deadline_at, customer_tier, action_type으로 정렬한다. 단순 FIFO(First In, First Out, 먼저 들어온 항목부터 처리)면 고위험·SLA 임박 요청이 뒤로 밀린다.
  • reviewer에게는 action, durable evidence artifact, 위험 이유, 변경 가능한 필드, 이전 유사 결정을 보여준다. sampled/redacted trace는 진단 보조 자료일 수 있지만 유일한 승인 근거가 될 수 없다.
  • edit는 기존 승인을 무효화하고 approval_payload_version을 올린 뒤 다시 waiting_for_review로 보낸다. 새 버전에 대한 명시적 승인 전에는 resume할 수 없다.
  • batch approval은 저위험 반복 작업에만 허용한다. 외부 발송·금전·권한 변경은 item 단위 확인을 유지한다.

payload_hash만으로는 승인 불변성이 부족한가

섹션 제목: “왜 payload_hash만으로는 승인 불변성이 부족한가”

hash는 어떤 bytes가 같은지는 말해도 그 bytes가 무엇을 뜻하는지까지 말하지 않는다. JSON key 순서, 기본값 채움, Unicode 정규화가 다르면 같은 의미가 다른 hash가 될 수 있고, 반대로 이메일 본문 bytes가 같아도 tenant나 수신자 resource, 실행 주체의 권한, 적용 정책 버전이 달라지면 실행 의미는 달라진다. payload_hash가 같다는 이유만으로 오래된 승인이나 다른 tenant의 승인을 재사용하면 안 된다.

승인 불변성은 다음 계약을 함께 만족할 때 성립한다.

  1. Canonical approval payload: schema가 정한 정규 표현으로 직렬화한 payload를 덮어쓰지 않는 artifact에 저장하고 approval_payload_refapproval_payload_version으로 고정한다.
  2. Effective intent: tenant_id, 요청 주체와 필요한 권한, action, 대상 resource, schema_version, policy_version을 승인 대상에 포함한다. “무엇을 누구 권한으로 어디에 실행하는가”를 고정하는 값이다.
  3. Resource precondition: 대상이 mutable resource라면 resource_versionIf-Match·row version 같은 precondition을 intent에 넣는다. 승인 뒤 대상이 바뀌면 같은 action 이름과 payload라도 같은 실행 의미가 아니다.
  4. Generation provenance: 모델 산출물을 승인한다면 model ID/version, 조립이 끝난 system/user input artifact ref/version/digest, sampling 설정, prompt/template ref/version, retrieval artifact refs, tool/schema version, provider/deployment route, provider response ID를 durable metadata로 보존한다. trace가 샘플링되거나 모델 alias가 바뀌어도 어떤 생성 조건과 응답을 검토했는지 추적할 수 있어야 한다.
  5. 재계산 비교: decision 시점과 resume 직전에 저장 artifact를 다시 읽고 canonical hash와 effective intent를 재계산해 승인 기록과 비교한다. 현재 권한·resource 상태가 정책상 재승인을 요구하는 변화인지도 확인한다.
  6. Expiry: expires_at이 지난 결정은 hash가 같아도 stale approval이다. 새 queue item과 현재 정책으로 다시 판단한다.

반례를 보자. {"subject":"점검 안내","body":"..."}의 hash가 그대로여도 tenant A에서 만든 승인을 tenant B의 고객 목록 발송에 붙이면 같은 action이 아니다. 또 policy_version=7에서 허용된 발송이 version 8에서 법무 검토 대상으로 바뀌었다면 payload bytes가 같아도 이전 승인을 실행 근거로 쓸 수 없다.

모든 step을 사람에게 묻는 것은 자동화 실패다. gate는 risk에 따라 둔다. 아래 threshold는 제품에 그대로 복사할 보편 규칙이 아니라, 팀이 정책을 코드로 표현하는 모양을 보여주는 가상 정책 예시다.

if action.risk == "low" and confidence >= 0.9:
auto_execute
elif action.risk == "medium":
ask_reviewer
else:
require_owner_approval

risk는 비용, irreversible(되돌리기 어려운) 여부, 개인정보, 외부 노출, 법적 영향으로 계산한다. 모델 confidence는 입력 신호 중 하나일 수 있지만 잘 보정되지 않은 confidence 하나로 auto-execute를 허용하지 않는다. action의 권한과 피해 범위처럼 모델 밖에서 검증 가능한 조건을 우선한다.

HITL gate는 사람이 바쁘면 곧바로 production bottleneck이 된다. 그래서 queue item마다 SLA와 escalation 정책을 먼저 정의한다. 아래 시간은 위험도별 정답이 아니라 가상 고객지원 시스템의 정책 예시다. 실제 값은 업무 시간, 규제, 되돌리기 가능성, 안전한 fallback 유무를 측정해 정한다.

위험도예시기본 SLA만료 시 동작
Low내부 요약 수정, 태그 추천24h자동 만료 또는 batch review로 이동
Medium고객에게 보낼 초안, 환불 제안4h팀 owner에게 escalate, agent는 대기 유지
High결제·삭제·배포·법률 답변30~60mon-call/manager에게 escalate, 자동 실행 금지
Critical보안 사고 대응, 대량 발송15m 이하incident 채널 호출, 변경 freeze, 수동 runbook

escalation은 단순 알림이 아니라 권한과 책임의 이동이다.

  • 1차 reviewer가 SLA를 넘기면 owner group으로 재배정한다.
  • 금액·고객 등급·법적 영향이 임계값을 넘으면 manager 또는 domain expert 승인을 추가 요구한다.
  • timeout이 지나도 자동 승인하지 않는다. 자동화가 필요하면 “만료 시 취소” 또는 “안전한 fallback 실행”만 허용한다.
  • escalation 이벤트도 audit log에 남긴다. “누가 지연시켰는가”가 아니라 “어떤 위험을 누구에게 넘겼는가”가 핵심이다.

SLA는 시간표만 정해서 지켜지지 않는다. 가상으로 medium-risk 요청이 시간당 24건 들어오고 한 건의 실제 검토가 평균 6분이면 reviewer 한 명의 이론 처리량은 시간당 10건이다. 회의·휴식·에스컬레이션을 제외한 가용률을 70%로 잡으면 유효 처리량은 시간당 7건이므로, 4명은 28건/시간으로 유입 24건/시간을 감당하지만 이용률이 약 86%라 burst에 민감하다. 3명은 21건/시간이라 매시간 3건씩 backlog가 늘고 4시간 뒤 12건이 쌓인다. 그러나 backlog 12건의 단순 평균 대기시간은 12 / 21 ≈ 0.57시간, 약 34분이므로 12건이라는 숫자만으로 4시간 SLA 위험을 결론 내릴 수는 없다. 이 계산은 요청과 처리량이 시간 내내 균일하고 모든 item의 우선순위와 검토시간이 같다고 놓은 결정론적 평균 모델이다. 핵심 위험은 유입 24건/시간이 처리 21건/시간을 지속해서 넘으면 backlog와 대기시간이 끝없이 증가해 결국 SLA를 깨뜨린다는 점이다. 실제 queue에서는 burst, 검토시간 분산, 고위험 item 우선 처리, reviewer 교대가 tail latency를 더 악화시킬 수 있다. 따라서 SLA, 평균과 상위 백분위 검토시간, 시간대별 유입량, reviewer 유효 처리량을 함께 측정하고 인원·우선순위·저위험 자동화 범위를 정해야 한다.

3.3 Interrupt/Resume State와 승인 대상 계약

섹션 제목: “3.3 Interrupt/Resume State와 승인 대상 계약”

HITL에는 최소 세 종류의 durable record가 필요하다. workflow의 중단 상태, 버전이 고정된 canonical approval payload, reviewer decision이다. evidence도 trace 안에만 두지 않고 독립 artifact로 보존한다.

{
"workflow_id": "wf_123",
"blocked_state": "waiting_for_review",
"approval_payload_ref": "artifact://approvals/wf_123/payload/v2",
"approval_payload_version": 2,
"payload_hash": "sha256:...",
"effective_intent": {
"tenant_id": "tenant_7",
"principal_id": "support_agent_4",
"required_permission": "customer_email.send",
"action": "send_email",
"resource": "customer:42/email:primary",
"resource_version": 41,
"precondition": "email_revision == 41",
"schema_version": 3,
"policy_version": 8
},
"evidence_artifacts": [
{
"artifact_ref": "artifact://tickets/ticket_7/snapshot/19",
"artifact_version": 19,
"digest": "sha256:..."
}
],
"trace_id": "trace_diag_9",
"deadline_at": "2026-07-11T05:00:00Z",
"expires_at": "2026-07-11T06:00:00Z"
}

artifact_ref는 나중에도 승인자가 본 근거를 같은 version으로 읽을 수 있는 durable reference다. object storage의 versioned object, append-only document revision, immutable database row가 될 수 있다. trace_id는 해당 실행의 span을 찾는 진단용 join key다. trace는 sampling으로 빠질 수 있고 redaction으로 핵심 값이 제거될 수 있으므로, sampled/redacted trace만 남기고 이를 유일한 승인 근거로 삼으면 안 된다.

첫 독서에서는 blocked_state, 승인 대상의 ref/version, 실제 실행 의미인 effective_intent, 판단 근거인 evidence_artifacts, 검토·효력 기한인 deadline_atexpires_at의 연결을 먼저 잡으면 된다. 모델이 어떤 조건에서 초안을 만들었는지까지 추적해야 하는 시스템은 아래 생성 계보를 같은 decision과 audit event에 연결한다.

선택 심화: generation_provenance 전체 필드 스키마
{
"generation_provenance": {
"model_id": "provider/model-family",
"model_version": "model-snapshot-2026-06",
"assembled_system_input": {
"artifact_ref": "artifact://generations/gen_52/system/v1",
"artifact_version": 1,
"digest": "sha256:..."
},
"assembled_user_input": {
"artifact_ref": "artifact://generations/gen_52/user/v1",
"artifact_version": 1,
"digest": "sha256:..."
},
"sampling": { "temperature": 0.2, "top_p": 0.95, "seed": null },
"prompt_template_ref": "artifact://prompts/support-email/v12",
"prompt_template_version": 12,
"tool_versions": { "ticket_lookup": "4.1.0" },
"tool_schema_version": 7,
"provider_route": "provider-a",
"deployment_route": "apne2/support-primary",
"provider_response_id": "resp_01J...",
"retrieval_artifact_refs": [
{
"artifact_ref": "artifact://tickets/ticket_7/snapshot/19",
"artifact_version": 19,
"digest": "sha256:..."
}
]
}
}

generation provenance는 재현 추적출력 재생을 구분한다. 위 필드들은 어떤 입력·설정·도구·route에서 응답이 나왔는지 추적하고 원인을 비교하게 해 주지만, 같은 값을 다시 호출해도 비결정적인 provider가 같은 token 출력을 돌려준다고 보장하지 않는다. 정확한 출력 재생은 모델을 재호출하는 것이 아니라 저장한 response artifact와 provider_response_id를 읽어야 보장된다.

resume 시에는 같은 workflow_id만 확인하지 않는다. decision이 가리킨 approval_payload_ref/version을 읽어 canonical payload와 effective intent를 재계산하고, hash·권한·policy version·expiry를 모두 비교한다. mutable resource에는 승인 시점 resource_version을 precondition으로 건 conditional write를 사용한다. 실행 시 version이 달라졌다면 TOCTOU(Time Of Check To Time Of Use, 확인과 사용 사이에 상태가 바뀌는 경쟁 조건)로 보고 외부 effect를 만들지 않은 채 reapproval_required로 전환한다. 실행 adapter는 검증을 통과한 canonical artifact에서만 tool argument를 만들며, UI가 보낸 임의 payload를 곧바로 실행하지 않는다.

감사 로그에는 “승인했다”만으로 부족하다. 감사 기록은 UI 요약이나 sampled trace를 복사한 것이 아니라, 승인 대상과 근거 artifact, 결정, 실제 실행 결과를 잇는 append-only event여야 한다.

who: reviewer id / role / delegated authority
when: timestamp / decision expiry
what: approval payload ref / version / hash / effective intent
why: reason / comment / policy version
evidence: durable artifact refs / versions / digests
diagnostic_join: optional trace id
result: execution artifact ref / executed / canceled / failed

payload_hash는 canonical bytes의 변경 탐지를 돕는 안전장치다. 단독으로 승인 불변성을 증명하지 않으므로 audit log에는 canonical ref/version과 effective intent를 함께 남긴다.

추가로 운영 audit log에는 decision context와 queue lifecycle이 남아야 한다.

queue: created_at, assigned_at, payload_edited_at, deadline_at, escalated_at, resolved_at
review_event: viewed / edited / approve_requested / reject_requested / escalate_requested
decision: decision_id, approved / rejected / escalated / expired, expires_at
approval_target: approval_payload_ref, approval_payload_version, payload_hash, effective_intent
evidence: artifact_ref, artifact_version, digest; diagnostic trace_id(optional)
generation_provenance: model/version, assembled system/user input refs, sampling, prompt/template, retrieval, tool/schema versions, provider/deployment route, response id
reviewer_context: reviewer_id, role, delegation_chain, conflict_of_interest flag
execution: canonical_intent_digest, idempotency_scope/key, claim_status, execution_artifact_ref, result, rollback_reference

감사 로그 설계 원칙:

  • append-only로 저장한다. reviewer가 결정을 바꾸면 기존 row를 수정하지 말고 새 event를 추가한다.
  • 승인 근거와 실행 결과를 연결한다. approval record와 실제 tool execution이 workflow_id·decision_id·approval_payload_ref/version·idempotency_key로 join되어야 한다. execution span은 선택적 진단 연결이고, 실행 결과 artifact가 정본이다.
  • 생성 계보를 durable metadata로 남긴다. 움직이는 model alias나 prompt 이름만 기록하지 않고 model ID/version, assembled system/user input ref/version/digest, sampling 설정, prompt/template과 retrieval artifact, tool/schema version, provider/deployment route, response ID를 queue·decision·audit event에 연결한다.
  • 멱등 claim 충돌도 감사한다. 같은 scope/key의 재시도인지, 다른 canonical intent를 붙인 오용인지 구분하고 conflict digest와 거절 결과를 append-only event로 남긴다.
  • 민감 정보는 접근 제어된 artifact와 필요한 digest로 보존한다. 무조건 원문을 audit row에 복제하지 않되 reviewer가 본 승인 대상과 evidence version은 재현할 수 있어야 한다. redacted summary만으로 원본 근거를 대체하지 않는다.
  • 감사와 디버깅을 분리한다. app log는 장애 분석용이고, audit log는 책임·권한·증거 보존용이다.

두 runtime을 함께 쓸 때 이 문서는 다음처럼 좁은 adapter 경계를 택한다. Temporal 자체가 handler에서 Activity 실행을 금지해서가 아니라, 승인 message 처리와 graph resume의 소유권을 섞지 않기 위한 설계 선택이다.

  1. API adapter가 승인 요청을 검증한 뒤 decision_idapproval_payload_version만 Temporal Signal/Update로 전달한다.
  2. Temporal message handler는 이 두 값을 Workflow state에 기록한다. handler 안에서 artifact를 읽거나 LangGraph를 resume하지 않는다.
  3. Temporal Workflow 본문은 해당 state를 관찰하고 resume_langgraph Activity를 예약한다. Activity 예약·재시도·완료는 Temporal Event History에 남는다.
  4. Activity가 immutable decision/payload/evidence artifact를 읽고 권한·expiry·effective intent를 재검증한 뒤, 해당 thread의 checkpoint를 지정해 Command(resume=...)로 LangGraph를 이어간다. Activity는 새 checkpoint/result ref를 반환한다.

정본(source of truth)도 역할별로 나눈다. Temporal Event History는 message 수신과 Activity 예약·재시도·결과 같은 orchestration의 정본이고, LangGraph checkpoint는 graph node/state와 interrupt/resume 진행의 정본이다. payload와 evidence 원문은 durable artifact store의 정본이다. Temporal history에 LangGraph 전체 state를 복제하거나 LangGraph checkpoint를 Temporal retry 상태의 정본으로 삼지 않는다.

Temporal Activity는 LangGraph resume을 성공시킨 직후 worker가 죽어 그 결과를 Temporal에 반환하지 못하면 재시도될 수 있다. 이때 Command(resume=...)를 다시 호출하지 않으려면 Activity attempt 번호가 아니라 (langgraph_thread_id, expected_checkpoint_id, interrupt_id, decision_id, approval_payload_version)을 resume request identity로 사용한다.

짧은 crash/retry 예시를 보자.

  1. Activity attempt A가 (thread-7, cp-41, int-9, decision-8, v2)를 claim하고 LangGraph를 resume한다.
  2. LangGraph는 cp-42consumed_resume_contract_key=(thread-7, cp-41, int-9, decision-8, v2)result_ref=artifact://graph-runs/thread-7/cp-42를 저장한다.
  3. A가 이 값을 Temporal에 반환하기 직전에 crash한다. Temporal Event History에는 Activity 완료가 기록되지 않아 attempt B가 실행된다.
  4. B의 claim_or_get은 같은 binding과 cp-42의 소비 marker를 찾고 기존 result_ref를 반환한다. B는 Command(resume=...)를 두 번째로 호출하지 않는다.

첫 독서의 결론은 adapter가 승인 message와 graph resume을 연결하되, retry가 같은 interrupt를 새 작업처럼 소비하지 않도록 기존 checkpoint/result를 먼저 찾는다는 것이다. interrupt 이전 코드의 재실행과 Activity 재시도가 겹칠 수 있으므로 side effect는 interrupt 뒤에 두거나 멱등하게 만든다. 아래 선택 심화는 여러 worker와 외부 provider까지 포함해 이 원칙을 구현하는 세부 프로토콜이며, 첫 독해에서는 건너뛰어도 된다.

선택 심화: resume ledger, lease/fencing, outbox, provider dedupe와 tombstone

이 선택 심화에서 사용하는 네 용어를 먼저 정의한다.

  • lease(임대): 특정 worker가 claim을 처리할 수 있는 권한을 제한된 시간 동안 빌리는 계약이다. 만료 전에는 다른 worker가 takeover하지 않고, 만료 뒤에는 현재 상태를 확인한 후 새 owner가 이어받을 수 있다.
  • fencing token(차단 토큰): takeover 때마다 증가하는 세대 번호다. 저장소와 dispatcher가 최신 번호의 write만 받아, lease를 잃은 이전 worker의 늦은 checkpoint·결과 기록을 거절한다.
  • outbox reservation(아웃박스 예약): 외부 provider에 보낼 operation과 immutable argument를 실제 발송 전에 DB에 선점 기록하는 것이다. resource precondition 검사와 같은 transaction에 저장해, 검증 직후 대상이 바뀐 요청이 발송 queue에 들어가는 창을 닫는다.
  • binding tombstone(바인딩 묘비): active dedup row를 정리한 뒤에도 과거 operation_id와 scope/key·intent의 결합을 최소 정보로 남기는 만료 기록이다. 오래된 key가 새 business operation에 다시 연결되는 것을 막는다.
Temporal Activity retry를 위한 atomic resume 계약
섹션 제목: “Temporal Activity retry를 위한 atomic resume 계약”

expected_checkpoint_id는 reviewer가 본 interrupt를 포함한 checkpoint이고, interrupt_id는 그 checkpoint 안에서 소비할 정확한 중단점이다. 병렬 interrupt가 없다면 구현에 따라 둘을 하나의 expected checkpoint/interrupt id로 표현할 수 있지만, 어떤 중단점을 소비하는지는 반드시 고정해야 한다. decision_idapproval_payload_version은 같은 thread라도 다른 사람 판단이나 수정 전 payload가 resume 권한을 재사용하지 못하게 한다.

resume_contract_key = (
langgraph_thread_id,
expected_checkpoint_id,
interrupt_id,
decision_id,
approval_payload_version
)

resume gateway의 claim_or_get은 다음 계약을 한 저장 권한 아래에서 수행한다.

UNIQUE(
langgraph_thread_id,
expected_checkpoint_id,
interrupt_id,
decision_id,
approval_payload_version
)
UNIQUE(langgraph_thread_id, expected_checkpoint_id, interrupt_id) # consumption slot
1. interrupt consumption binding을 조회한다.
2. binding이 없으면 현재 checkpoint == expected_checkpoint_id이고 interrupt_id가 pending인지 확인한다.
3. 확인과 같은 atomic conditional write에서 전체 resume_contract_key,
decision/payload digest, lease, fencing_token을 bind한다.
4. 같은 key가 이미 result_checkpoint_id/result_ref를 가지면 그 결과를 반환한다.
5. 같은 key가 claimed 상태라면 LangGraph checkpoint metadata에서
consumed_resume_contract_key를 조회한다.
- 같은 key로 checkpoint가 전진함: 기존 result를 복원·기록하고 반환
- interrupt가 여전히 pending이고 lease가 만료됨: token을 높여 takeover
- 어느 쪽인지 판정 불가: resume_unknown으로 두고 재호출 금지
6. 같은 interrupt slot이 다른 decision/version/digest에 bind되어 있으면 conflict로 거절한다.

별도 resume ledger에서 claim만 하고 LangGraph를 무조건 다시 호출하면 3번과 5번 사이의 crash gap이 남는다. 따라서 application adapter는 LangGraph checkpointer 쪽에 consumed_resume_contract_key, fencing token, result_checkpoint_id/result_ref를 남기고, interrupt 소비와 checkpoint write에 compare-and-set 또는 같은 transaction 수준의 조건부 쓰기를 적용해야 한다. checkpointer와 resume ledger를 같은 transaction에 둘 수 없다면 두 저장소를 정확히 한 번이라고 부르면 안 된다. retry는 먼저 둘을 조회해 reconcile하고, 기존 소비를 확인할 수 없을 때는 resume_unknown에서 멈춰야 한다.

반대로 Activity attempt가 LangGraph checkpoint를 쓰기 전에 죽었다면, pending interrupt와 lease 만료가 모두 확인된 뒤 다음 attempt가 더 큰 fencing token으로 takeover한다. 이 계약의 권위 경계는 명확하다. Temporal Event History는 Activity를 다시 시도할지 결정하고, LangGraph checkpointer와 resume binding은 해당 interrupt가 이미 소비됐는지 판정하며, 외부 이메일·결제 effect는 뒤의 execution ledger·outbox·provider 조회가 판정한다. 그러므로 보장 목표는 end-to-end exactly-once가 아니라 중복 resume을 조회·조정 가능한 하나의 결과에 수렴시키고, 불명 상태에서는 새 effect를 만들지 않는 것이다.

HITL 구현 산출물은 상태 머신으로 남겨야 한다. 버튼 이름이 아니라 어떤 상태에서 어떤 이벤트가 허용되는지가 계약이다.

현재 상태이벤트다음 상태검증/부수 효과
queuedassignee 결정assignedreviewer role·conflict-of-interest 확인
assignedreviewer가 열람waiting_for_reviewapproval/evidence ref/version과 durable generation provenance 기록
waiting_for_reviewapproveapprovedcanonical payload·effective intent·resource precondition 재계산 비교, reviewer 권한 확인, expires_at 고정
waiting_for_revieweditwaiting_for_review새 artifact/version/hash와 diff 생성, 이전 decision 무효화, 새 버전 재승인 요구
waiting_for_reviewrejectrejected실행 금지, 거절 사유 필수
waiting_for_reviewescalateescalatedassignee policy·필요 권한 재평가, deadline 재설정
waiting_for_reviewdeadline 초과escalated / expired정책에 따라 상위 권한자로 이관하거나 만료 후 safe fallback·취소. 자동 승인 금지
escalated새 assignee 결정assigned새 reviewer 권한·conflict-of-interest 확인, deadline 재설정
approvedLangGraph resume claim/getresume_claimed / graph_resumedexpected checkpoint/interrupt와 decision/payload version을 원자 bind. 기존 result면 graph_resumed 반환
resume_claimedcheckpoint advance/result 기록graph_resumed같은 resume contract key와 최신 fencing token으로 interrupt 소비 marker·result checkpoint/ref 기록
graph_resumed외부 effect claimexecution_claimedscoped key/intent에 대한 동시 실행 권한만 원자 획득하고 lease_owner, lease_expires_at, fencing_token 기록
execution_claimedheartbeatexecution_claimed현재 fencing token의 owner만 lease 연장. 만료된 owner의 write·outbox dispatch는 거절
execution_claimedstale claim takeoverexecution_claimedlease 만료 확인 후 새 owner가 claim하고 fencing token 증가. 이전 worker의 늦은 결과는 거절
execution_claimed같은 scope/key 재요청execution_claimed / executed같은 digest면 기존 in-progress/result 반환, 다른 payload·intent digest면 409 IdempotencyConflict로 거절
execution_claimedresource precondition 불일치reapproval_requiredresource version 검사와 outbox reservation을 같은 DB conditional write transaction으로 수행. 0건이면 재승인
reapproval_required새 payload version 생성waiting_for_review이전 decision·execution claim은 재사용하지 않고 새 version을 새 decision/key로 승인받음
execution_claimedtool 실행 성공/명시적 실패executed / failedprovider 응답과 durable execution artifact를 audit에 연결
execution_claimedprovider timeout/연결 단절execution_unknown실패로 단정하지 않는다. provider에서 effect가 발생했지만 응답만 유실됐을 수 있으므로 즉시 새 key로 retry 금지
execution_unknownreconcileexecuted / retry_readyresponse ID·operation key로 provider 상태 조회. effect 확인 시 결과 저장, 미발생이 확인된 경우만 같은 operation으로 retry
retry_readyretryexecution_claimed같은 operation/key로 새 lease와 더 큰 fencing token을 얻은 뒤 재시도. 새 business operation으로 rebinding 금지
왜 idempotency key만으로는 외부 effect 중복을 막지 못하는가
섹션 제목: “왜 idempotency key만으로는 외부 effect 중복을 막지 못하는가”

idempotency key는 문자열일 뿐이다. worker A와 B가 동시에 key가 없다고 읽은 뒤 각각 provider를 호출하는 check-then-act 구조라면, 같은 key를 보냈어도 이메일은 두 번 발송될 수 있다. atomic claim이 보장하는 범위는 같은 시점에 실행 권한을 얻는 worker가 하나라는 것뿐이다. provider effect의 exactly-once, worker crash 뒤 복구, timeout 결과 판정까지 보장하지 않는다.

canonical_effective_intent_digest는 승인 payload ref/version/hash와 tenant, principal/permission, action, resource id/version, schema/policy version을 정규화한 뒤 만든다. execution ledger의 기본 계약은 다음과 같다.

UNIQUE(idempotency_scope, idempotency_key)
1. scope + key + canonical_effective_intent_digest를 execution_claim에 INSERT
2. conflict면 저장된 digest를 비교
- 같음: 기존 in-progress 상태 또는 result_ref 반환
- 다름: 409 IdempotencyConflict, 실행 금지
3. 내부 DB effect면 같은 transaction에서 resource_version precondition, business mutation, result_ref를 commit
4. 외부 provider effect면 lease owner/expiry와 fencing token을 발급하고 outbox reservation으로 넘김

외부 provider 호출은 로컬 DB transaction에 함께 commit할 수 없다. 이때 outbox는 DB transaction에서 발송할 operation을 먼저 기록하고 별도 dispatcher가 provider로 전달하는 테이블/큐다. 애플리케이션은 승인된 resource_version을 확인하는 conditional write와 immutable outbox reservation 생성을 같은 DB transaction으로 묶는다. 예를 들어 UPDATE customer_email SET reserved_by=:operation_id WHERE id=:id AND version=41 AND reserved_by IS NULL이 1건일 때만 같은 transaction에서 outbox row를 INSERT한다. 0건이면 commit하지 않고 reapproval_required로 간다. 이렇게 해야 version 확인 직후 resource가 바뀌고도 오래된 argument가 outbox에 들어가는 창이 닫힌다.

claim이 오래 걸리면 owner는 heartbeat로 lease를 연장한다. lease가 만료된 claim만 다른 worker가 takeover할 수 있고, takeover 때 fencing token을 증가시킨다. outbox dispatcher와 result write는 최신 token만 받아 늦게 깨어난 이전 worker를 차단한다. 그래도 provider가 같은 scope/key와 같은 요청은 동일 결과를 반환하고, 같은 key와 다른 payload는 거절하며, 약속된 retention 동안 concurrent request도 하나의 effect로 합치는 idempotency 계약을 제공해야 한다. provider timeout은 실패가 아니라 결과 불명일 수 있으므로 execution_unknown으로 두고 response ID나 operation key로 reconcile한다. effect가 없다고 확인된 뒤에만 같은 operation/key로 retry_ready에서 재시도한다. provider 계약이나 조회 API가 없다면 ledger와 fencing이 동시 호출은 줄여도 provider 성공 직후 응답 유실을 정확히 한 번으로 만들 수는 없다.

scope는 최소 environment + tenant_id + provider/action처럼 서로 다른 보안·실행 영역이 key를 공유하지 않게 정한다. retention은 client retry, workflow replay, delayed queue redelivery가 가능한 최대 기간보다 길어야 하고 provider의 보장 기간과 맞춰야 한다. active dedup row를 정리해도 operation_id ↔ scope/key ↔ canonical intent digest binding tombstone을 남기거나, 만료 operation의 scope/key를 조회해 거절하는 별도 인덱스를 유지해야 한다. 과거 key가 새 business operation에 binding되면 안 되며, 만료된 operation의 재요청은 410 OperationExpired처럼 거절하고 새 operation ID와 key를 발급한다. audit용 intent digest와 result ref 보존만으로는 이 재사용 거절 계약을 대신할 수 없다.

Worked example: AI 이메일 초안에서 발송 결과까지
섹션 제목: “Worked example: AI 이메일 초안에서 발송 결과까지”

가상의 고객지원 정책에서 send_email은 medium risk이고 4시간 안에 검토해야 한다고 하자. 이 수치와 분류는 설명용이며, 실제 제품의 보편 SLA가 아니다.

  1. 초안 생성과 중단: agent가 ticket snapshot artifact://tickets/t-7/snapshot/19를 근거로 이메일 초안을 만든다. workflow는 수신자·제목·본문뿐 아니라 tenant_id, customer_email.send 권한, customer resource와 resource_version=41, schema/policy version을 canonical payload v1에 저장하고 waiting_for_review에서 멈춘다. queue에는 model ID/version, prompt template v12의 durable ref, retrieval artifact ref를 generation_provenance로 함께 남긴다.
  2. 사람 수정과 새 승인 대상: reviewer가 과도한 환불 약속을 삭제하고 제목을 수정한다. 시스템은 v1을 덮어쓰지 않고 artifact://approvals/wf-123/payload/v2를 만든다. queue가 가리키는 승인 대상은 v2로 바뀌고 v1에 대한 미완료 decision은 무효화되며, v2가 다시 waiting_for_review가 된다. edit 자체를 승인으로 간주하지 않는다.
  3. 승인: reviewer가 v2와 ticket snapshot을 보고 승인한다. decision record는 v2의 ref/version/hash, effective intent, evidence artifact ref, expires_at을 고정한다. 화면용 redacted trace가 없어도 durable artifact로 판단을 재현할 수 있어야 한다.
  4. Temporal retry-safe graph resume: resume_langgraph Activity는 (thread-123, cp-7, int-2, decision-8, v2)claim_or_get한다. 첫 attempt가 LangGraph의 새 checkpoint cp-8을 저장한 뒤 Temporal 결과 기록 전에 죽어도, retry는 cp-8의 소비 marker와 result ref를 반환하며 두 번째 Command(resume=...)를 호출하지 않는다.
  5. 외부 effect claim과 TOCTOU 차단: graph가 발송 단계에 도달하면 worker는 prod:tenant-7:send_email scope에서 operation key와 v2 canonical effective intent digest를 UNIQUE(scope, key)로 claim한다. 이어서 customer email version 41에 대한 conditional reservation과 승인된 수신자 argument의 immutable outbox INSERT를 같은 DB transaction으로 수행한다. version이 42이거나 이미 예약됐다면 둘 다 commit하지 않고 reapproval_required로 전환해 payload v3를 다시 승인받는다. graph resume 중복 방지와 provider effect 중복 방지는 서로 다른 권위 경계이며, 단순 재조회 뒤 outbox를 별도 저장하지 않으므로 확인과 사용 사이의 창이 닫힌다.
  6. deadline escalation: 4시간 안에 결정이 없으면 자동 발송하지 않고 owner group으로 이관한다. 에스컬레이션은 새 assignee와 deadline을 기록하며, 정책상 reviewer 권한이 달라지면 승인 전에 다시 검사한다.
  7. 실행 결과 연결: idempotency 계약을 제공하는 provider에 scoped key를 보내고 message id와 응답 snapshot을 artifact://executions/wf-123/send/1에 저장한다. audit event는 workflow_id, decision_id, approval payload v2, canonical intent digest, idempotency scope/key, generation provenance, execution artifact를 연결한다. 따라서 “어떤 생성 조건과 근거로 무엇을 만들고, 누가 어느 resource version을 승인했으며, 실제로 무엇이 발송됐는가”를 한 경로로 재구성할 수 있다.

이 예제의 경계는 사람의 존재가 아니라 version이 고정된 판단과 실제 외부 효과의 연결이다. reviewer가 v1을 봤는데 v2가 발송되거나, 두 번째 resume이 새 발송을 만들거나, deadline 뒤 묵시적으로 승인되면 HITL UI가 있어도 실행 신뢰 경계는 실패한다.

사람에게 raw trace 전체를 던지면 안 된다. HITL UI는 다음을 보여준다.

  • agent가 하려는 action
  • durable artifact로 다시 열 수 있는 근거와 출처
  • 위험 수준과 이유
  • 수정 가능한 필드
  • 승인/거절/수정/에스컬레이션 버튼
  • 실행 후 되돌릴 수 있는지

reviewer 화면은 요청 요약 → 승인 payload 버전 → 근거 → 위험/정책 → 수정 가능 필드 → 결정 순서로 구성한다. 금지 필드는 raw prompt, 전체 trace dump, secret이 포함될 수 있는 tool argument 원문, 다른 reviewer의 사전 판단이다. 이런 필드는 판단 오염과 PII(Personally Identifiable Information, 개인 식별 정보) 장기 보관을 만들 수 있다. UI에는 허용된 view를 보여주되 결정 API는 durable approval_payload_ref/version과 필수 evidence artifact_ref/version/digest를 받는다. trace_id와 redacted diff는 진단 보조 정보다.

Endpoint요청/응답 계약실패 시 처리
GET /approval-items/:idredacted view, payload ref/version/hash, effective intent, evidence artifact refs, expiry권한 없음·만료 item은 decision button 비활성화
POST /approval-items/:id/decisiondecision, reason, payload ref/version; edit이면 새 canonical artifact 생성stale version·intent 불일치는 409 Conflict, edit은 새 버전 재검토로 전환
POST /workflows/:workflow_id/resumethread id, expected checkpoint/interrupt id, decision id, payload versionresume key를 원자 bind하고 기존 checkpoint/result를 우선 반환. 다른 decision/version은 409, 판정 불가는 resume_unknown

HITL 결과는 평가 시스템으로 되돌아간다.

human correction
-> gold dataset candidate
-> prompt/retrieval failure label
-> regression eval
-> gate threshold 조정

사람 개입을 비용으로만 보지 말고 데이터 수집 지점으로 설계한다. 다만 모든 수정이 곧바로 정답은 아니다. correction과 결정 근거를 검수하고, 합의된 policy/rubric version과 함께 versioned gold dataset에 편입해야 한다.

Labeling은 답변 품질 점수, 오류 유형, 선호 쌍처럼 Feedback Loop에서 평가·학습 데이터를 수집하는 개입 유형이다. Approval·Correction·Selection·Escalation처럼 현재 workflow의 외부 effect를 허가하거나 막는 실행 gate로 분류하지 않는다. 같은 UI에서 승인과 label을 함께 받을 수는 있지만, approved decision과 quality_label event를 분리해야 label 수정이 실행 권한을 바꾸거나 승인 기록을 덮어쓰지 않는다.

reviewer도 하나의 평가 시스템이다. 사람마다 승인 기준이 다르면 HITL은 안전장치가 아니라 랜덤 gate가 된다. calibration은 reviewer 판정을 일관되게 만드는 운영 절차다. **rubber-stamp(고무도장을 찍듯 내용을 충분히 검토하지 않고 관성적으로 승인하는 행위)**가 반복되면 사람을 둔 것만으로는 안전 경계가 되지 않는다.

Calibration 항목측정 신호조치
Inter-reviewer agreement같은 샘플에 대한 reviewer 간 일치율rubric 보강, 예시 추가, 교육
Override rate승인 후 agent 실행을 사람이 다시 되돌린 비율risk 기준 상향, reviewer 권한 재검토
Rubber-stamp rate검토 시간이 비정상적으로 짧고 승인율이 높음batch 기준 재조정, 샘플 감사 확대
Escalation accuracyescalate된 케이스가 실제 high-risk였는지escalation threshold 조정
Drift in decisions같은 유형의 승인/거절 비율이 시간에 따라 변함gold dataset 재평가, policy versioning

고정 샘플의 크기나 dual review 비율에는 보편 정답이 없다. 예를 들어 한 팀이 50~200개 calibration sample로 시작할 수 있지만, 이는 가상 운영 예시일 뿐이다. 희귀한 high-risk 사례가 충분히 포함되는지, 불일치율이 안정적으로 보이는지에 따라 늘리거나 층화해야 한다. 사람이 수정했다는 사실만으로 gold label이 되는 것도 아니다. reviewer 간 합의와 적용된 rubric/policy version이 함께 남아야 versioned gold dataset으로 쓸 수 있다.

증상깨진 경계먼저 고칠 것
승인자가 rubber stamp너무 많은 저위험 요청이 사람 주의를 소진risk threshold 재평가, 가상 정책으로 허용한 저위험 batch 범위 검증
hash는 같은데 다른 tenant에 실행payload bytes만 비교하고 effective intent를 누락tenant·권한·action·resource·schema/policy version 재계산 비교
edit 직후 승인 없이 실행correction을 새 승인 대상으로 만들지 않음immutable payload version 생성 후 waiting_for_review로 전환
workflow가 영원히 대기deadline·escalation 없음deadline timer, owner fallback, 만료 후 자동 실행 금지
승인 근거를 재현할 수 없음sampled/redacted trace를 유일한 evidence로 사용durable artifact_ref/version/digest를 queue·decision·audit에 필수화
선택 심화: Activity retry가 graph를 두 번 resumeLangGraph 성공 뒤 Temporal 결과 기록 전 crash를 새 요청으로 처리resume contract key로 claim_or_get하고 checkpoint의 기존 result를 반환
선택 심화: 같은 resume이 이메일을 두 번 발송key만 전달하고 concurrent claim을 원자화하지 않음scoped UNIQUE(scope, key) claim과 provider idempotency 계약
선택 심화: 같은 key로 다른 payload가 실행됨key conflict에서 canonical intent를 비교하지 않음저장 digest와 요청 digest 비교, 불일치면 409와 audit event
선택 심화: timeout 뒤 재시도로 중복 발송결과 불명을 실패로 단정하고 새 key로 재호출execution_unknown에서 provider reconcile 후 같은 operation/key로만 retry
선택 심화: 오래된 worker가 결과를 덮어씀lease takeover 뒤 이전 worker를 차단하지 않음heartbeat·lease expiry와 fencing token을 outbox/result write에서 검증
선택 심화: 정리한 key가 새 작업에 재사용됨active dedup row 삭제와 operation binding 삭제binding tombstone 또는 만료 key 거절 인덱스를 유지하고 새 operation/key 발급
승인 뒤 바뀐 고객 주소로 발송승인 시 검증과 실행 사이 mutable resource 변경resource_version precondition, conditional write 실패 시 재승인
생성 조건을 재현할 수 없음model alias·trace만 남기고 provenance를 유실assembled input·sampling·tool/schema·route·response ID까지 durable하게 보존
수정 내용이 학습에 안 쓰임feedback pipeline과 검수 단계 없음correction을 versioned gold dataset 후보로 저장하고 합의된 label만 편입
승인 큐 backlog 폭증SLA·우선순위 정책 없음risk/deadline priority queue, auto-expire
reviewer마다 기준 다름calibration set·rubric version 없음gold dataset 기반 calibration, rubric versioning, high-risk dual review sample
  • 고객에게 발송되는 AI 이메일/메시지 승인
  • 코드 변경 agent의 patch review
  • 운영 자동화 runbook의 위험 action 승인
  • 의료·법률·금융 답변의 전문가 검수
  • 채용/평가 시스템의 AI 추천 결과 검토

HITL을 넣을지는 “사람이 보면 더 안전하다”는 직감만으로 정하지 않는다. 사람이 실제로 판단할 정보와 권한이 있는지, 대기 비용보다 피해 감소가 큰지, 결정 뒤 실행을 기술적으로 묶을 수 있는지를 본다.

조건우선할 개입
실행 내용은 확정됐고 외부 effect 허가만 필요Approval
산출물의 일부를 사람이 고쳐야 함Correction 후 새 payload version에 대한 Approval
허용된 여러 후보 중 맥락 판단이 필요Selection, 선택 결과를 canonical payload에 반영
현재 담당자의 권한·전문성으로 결정 불가Escalation, 새 assignee policy와 deadline 기록
낮은 위험이고 규칙으로 충분히 검증 가능자동 실행. 불필요한 HITL은 rubber stamp와 backlog를 만든다
사람도 재현 가능한 근거를 받지 못함승인 요청 전 evidence pipeline부터 보완

특히 “사람이 한 번 봤다”와 “실행이 승인됐다”를 구분한다. draft quality review만 통과했는데 실제 수신자·tenant·권한이 나중에 붙는다면, 품질 검수는 했어도 발송 action을 승인한 것은 아니다.

개념 A개념 B차이점
HITLManual QAHITL은 실행 중 decision gate, Manual QA는 별도 검수 단계
ApprovalCorrection승인/거절 vs 내용을 직접 수정
InterruptBreakpointinterrupt는 운영 중 동적 중단, breakpoint는 개발/디버깅 개념
EscalationRetryescalation은 사람/권한 변경, retry는 같은 작업 재시도
Audit logApplication logaudit은 책임과 증거, app log는 디버깅 중심

7. 체크리스트 체크

  • 어떤 action이 human approval을 요구하는지 risk 기준으로 정의할 수 있다
  • approval queue item schema와 우선순위 기준을 설계할 수 있다
  • interrupt/resume에 필요한 canonical payload ref/version, effective intent, resource precondition, expiry를 설계할 수 있다
  • 선택 심화: idempotency scope/key와 canonical intent를 원자적으로 claim하고 같은 key의 다른 payload를 거절할 수 있다
  • risk별 SLA와 escalation policy를 상태 머신에 포함할 수 있다
  • 승인 UI에 보여줄 근거·위험·수정 필드와 durable evidence artifact를 정할 수 있다
  • timeout과 escalation 경로를 상태 머신에 포함할 수 있다
  • queue와 audit log에 durable generation provenance를 남기고, 선택 심화에서는 execution claim/result까지 append-only로 연결할 수 있다
  • reviewer calibration set과 disagreement 처리 절차를 운영할 수 있다
  • human correction을 검수해 versioned gold dataset으로 연결할 수 있다

8. 선택 부록: 운영 설계 점검 순서

섹션 제목: “8. 선택 부록: 운영 설계 점검 순서”

본문의 계약을 이해하는 데 아래 절차가 필수는 아니다. 실제 queue를 도입할 때 순서가 필요하면 펼쳐 본다.

승인 queue와 reviewer calibration 도입 절차
  1. action inventory에서 외부 effect, 최대 피해, 되돌리기 가능성, 필요한 권한을 적고 Approval·Correction·Selection·Escalation 중 필요한 개입을 고른다.
  2. queue schema에 immutable approval_payload_ref/version, effective intent와 resource_version precondition, evidence artifact_ref/version/digest, generation provenance, deadline/expiry를 넣는다. trace_id는 선택적 진단 필드로 분리한다.
  3. 상태 전이 테스트에서 edit 뒤 재승인, stale version·expiry 거절, deadline escalation, resource version 변경 시 재승인을 먼저 검증한다. 선택 심화 프로토콜을 적용한다면 concurrent resume claim과 같은 key의 다른 payload 거절도 추가한다.
  4. 가상 운영 예시로 50~200개 층화 calibration sample을 시작점으로 삼을 수 있다. 실제 크기는 high-risk 희귀 사례 coverage와 reviewer 불일치의 안정성으로 결정한다.
  5. reviewer decision에 rubric_versionpolicy_version을 남긴다. disagreement는 공통 기준을 갱신하되 바뀐 기준은 새 queue item부터 적용한다.
  6. high-risk route의 dual/expert review 비율도 고정 법칙으로 두지 않는다. 불일치, override, 실제 피해 신호를 보고 조정한다.
  7. correction은 바로 gold label로 승격하지 않는다. 합의된 판정 근거와 artifact version을 묶어 gold dataset 후보로 저장한 뒤 검수한다.

human-in-the-loop, LangGraph interrupt, Temporal Signal, Temporal Update, application adapter, approval workflow, canonical effective intent, generation provenance, resource precondition, TOCTOU, idempotency scope, atomic execution claim, audit trail, durable artifact, AI governance, review queue, SLA, reviewer calibration, dual review, active learning

10. 내가 직접 확인해볼 것 체크

  • "AI가 고객 이메일 초안 작성 → 사람이 수정/승인 → 발송" 상태도 작성
  • approval queue table schema와 priority sorting 규칙 작성
  • risk score 기준을 비용·외부 노출·되돌릴 수 있음으로 나눠보기
  • medium/high/critical risk별 SLA와 escalation owner 정의
  • payload hash만 같은 tenant/policy가 다른 반례와 canonical ref/version·effective intent 검증 설계
  • LangGraph resume 뒤 Temporal Activity 결과 기록 전에 crash한 재시도에서 기존 checkpoint/result가 반환되는 테스트 작성
  • 선택 심화: 동시 resume 두 개가 같은 scope/key를 claim할 때 한 요청만 실행되는 unique constraint 테스트 작성
  • 선택 심화: 같은 idempotency key에 다른 payload를 붙이면 409로 거절하고 retention 뒤에도 key를 재사용하지 않는 정책 작성
  • 승인 뒤 resource version이 바뀌면 conditional write가 실패하고 재승인으로 돌아가는 상태도 작성
  • sampled trace 없이도 evidence artifact로 승인 근거를 재현하는 schema 만들기
  • model ID/version, prompt/template version, retrieval artifact refs를 queue와 audit에 연결하기
  • audit log event를 created/assigned/escalated/approved로 나누고, 선택 심화에서는 execution_claimed/executed와 execution artifact까지 연결하기
  • 가상 calibration sample 50개를 두 reviewer가 판정했을 때 disagreement를 기록하는 schema 만들기
  • 수정된 답변을 gold dataset 후보로 저장하는 schema 만들기
  1. HITL은 마지막 Approve 버튼이 아니라 승인·수정·선택·에스컬레이션 전후를 잇는 실행 신뢰 경계다.
  2. 승인은 사람이 본 대상·근거·권한·유효기간을 고정하고, 내용이 바뀌거나 만료되면 다시 받아야 한다.
  3. interrupt/resume은 같은 중단점을 가리키는 안정된 resume identity를 쓰고, 재시도 전에 기존 checkpoint/result를 조회해 중복 진행을 피한다.
  4. graph를 한 번만 resume하는 문제와 이메일·결제 같은 외부 effect를 멱등하게 만드는 문제는 서로 다른 경계다.
  5. 승인 큐는 durable evidence, SLA·escalation, audit, reviewer calibration을 하나의 application contract로 연결한다.

최종 수정: 2026-07-12