휴먼 인 더 루프

Human in the loop

되돌리기 어려운 행동을 에이전트가 실행하기 전, 사람의 승인·거부를 명시적으로 받는 카드.

다른 이름: Approval cardConfirm before actionAgent approval gate
···
html
<div class="card" id="card">
  <div class="head"><i class="warn">!</i><strong>승인이 필요해요</strong></div>
  <p>임시 파일 <b>12개</b>를 삭제합니다. 이 작업은 되돌릴 수 없어요.</p>
  <div class="row">
    <button class="deny" id="deny">거부</button>
    <button class="approve" id="ok">승인</button>
  </div>
</div>
css
.card{width:min(280px,94%);border:1px solid var(--line);border-radius:13px;background:var(--surface);padding:14px;display:grid;gap:9px;transition:opacity .2s}
.head{display:flex;align-items:center;gap:8px;font-size:12.5px}
.warn{width:18px;height:18px;border-radius:50%;background:#f5a524;color:#fff;font-style:normal;font-weight:800;font-size:11px;display:grid;place-items:center;flex-shrink:0}
p{margin:0;font-size:11.5px;color:var(--muted);line-height:1.55}
.row{display:flex;gap:8px}
.deny,.approve{flex:1;border-radius:9px;padding:8px 0;font-size:12px;font-weight:700;cursor:pointer;border:1px solid var(--line)}
.deny{background:var(--bg);color:var(--muted)}
.approve{background:var(--accent);color:#fff;border-color:var(--accent)}
.card[data-approved] .row{display:none}
.status{display:none;align-items:center;gap:6px;font-size:12px;font-weight:700;color:#0f9d78}
.card[data-approved] .status{display:flex}
.status i{width:16px;height:16px;border-radius:50%;background:var(--accent-3);display:inline-grid;place-items:center}
.status i::after{content:"";width:4px;height:7px;border:solid #fff;border-width:0 1.4px 1.4px 0;transform:rotate(45deg) translate(-1px,-1px)}
js
const card = document.getElementById('card'), ok = document.getElementById('ok');
const row = document.querySelector('.row');
const status = document.createElement('div');
status.className = 'status';
status.innerHTML = '<i></i><span>승인됨 — 삭제를 실행합니다</span>';
card.appendChild(status);
function approve() {
  card.setAttribute('data-approved', '');
  setTimeout(reset, 2000);
}
function reset() { card.removeAttribute('data-approved'); setTimeout(approve, 1600); }
setTimeout(approve, 1600);
ok.addEventListener('click', approve);

에이전트가 파일 삭제, 결제, 메일 발송처럼 되돌리기 어려운 행동을 하기 직전에 멈춰서, "이걸 실행해도 될까요?"를 명시적으로 묻는 패턴입니다. 무엇을 하려는지(행동), 무엇에 대해(대상), 어떤 영향이 있는지(예: "파일 12개, 복구 불가")를 카드 안에 구체적으로 적어야 사람이 실제로 판단할 근거가 생깁니다.

승인/거부 두 버튼은 명확히 구분되는 색과 위치를 가져야 합니다 — 특히 거부·취소 쪽을 실수로 누르기 쉬운 자리에 두지 않는 게 중요합니다(위험한 건 실행 쪽이지 취소 쪽이 아닙니다). 승인 즉시 카드는 "승인됨" 상태로 바뀌고 실행이 이어집니다.

이 패턴의 신뢰는 "정말로 막아주는가"에 달려 있습니다. 승인 없이도 어차피 실행되는 카드라면 장식일 뿐이고, 한 번 그게 들통나면 이후의 모든 승인 요청을 사용자가 형식적으로만 누르게 됩니다.

언제 쓰나

되돌리기 어렵거나 비용이 드는 행동(삭제, 결제, 외부 발송) 전에는 반드시 씁니다. 조회처럼 부작용이 없는 행동까지 매번 승인받게 하면 그 자체가 마찰이 되어 사람들이 승인을 습관적으로 누르게 됩니다.