본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 17. 01:36

빨간 버튼에서 손을 떼세요: HazelJS에서의 승인(Approvals), 이벤트(Events), 그리고 재개 가능한

요약

HazelJS를 사용하여 AI 에이전트의 위험한 도구 실행을 제어하는 승인(Approval) 워크플로 구현 방법을 설명합니다. 모델이 제안하고 인간이나 시스템이 승인하며, 상태를 유지한 채 실행을 재개할 수 있는 안전한 에이전트 설계 패턴을 다룹니다.

핵심 포인트

  • 위험한 도구에 requiresApproval 설정을 통해 1급 정책 객체로 관리
  • 중앙 승인 지점을 통해 멀티 에이전트 환경의 보안 및 거버넌스 강화
  • 실행 일시 중지 후 상태를 잃지 않고 재개하는 resume 기능 지원
  • 개발 환경과 운영 환경을 구분한 승인 프로세스 구현 가이드

모델이 확신을 느끼는 즉시 어떤 주문이든 환불하거나 어떤 데이터베이스 행이든 삭제할 수 있는 "도움이 되는" 에이전트를 상상해 보세요. 법무팀은 데모 자료를 승인하고, 재무팀이 지난 화요일의 환불액이 왜 두 배로 늘었는지 묻기 전까지는 아무도 processRefund의 세부 조항을 읽지 않습니다.

HazelJS는 위험한 도구들을 **1급 정책 객체 (first-class policy objects)**로 취급합니다. 해당 도구들에 **requiresApproval: true**를 표시하고, 런타임(runtime)에서 **TOOL_APPROVAL_REQUESTED**를 리스닝(listen)하며, 오직 신뢰할 수 있는 워크플로(workflow)에서만 **approveToolExecution**을 호출하세요. 동일한 런타임은 실행이 인간의 입력을 위해 일시 중지되었을 때 **resume(executionId)**를 지원합니다. 따라서 "매니저의 승인을 기다림"은 단순한 채팅 완성(chat completions) 위에 얹은 해킹이 아닙니다.

핵심 아이디어: 모델은 제안하고, 런타임은 차단하며, 인간 또는 시스템이 허가하고, 상태(state)를 잃지 않고 재개할 수 있습니다.

승인이 "단순한 또 다른 if 문"이 아닌 이유

접근 방식리스크
"프롬프트를 신뢰함"모델의 드리프트(drift) 발생; 탈옥(jailbreaks)이 의도를 우회함
...

멀티 에이전트(Multi-agent) 설정은 이를 악화시킵니다. 감독자(supervisors)와 위임자(delegates)는 도구에 부수 효과(side effects)가 있는 에이전트로 경로를 지정할 수 있습니다. **중앙 승인(Central approval)**은 그래프가 얼마나 많은 워커(workers)를 사용하든 상관없이 하나의 제어 지점(choke point)을 유지합니다.

프로젝트 설정

npx @hazeljs/cli g app agent-approvals --template=ai-native
cd agent-approvals
cp .env.example .env

Postgres + pgvector(기본 AI-native 템플릿과 동일)를 시작하고, **npm run db:push**를 실행한 뒤, **OPENAI_API_KEY**를 설정하고 **npm run dev**를 실행하세요.

인간이 필요한 도구 표시하기

import { Agent, Tool } from '@hazeljs/agent';

@Agent({
...

읽기 전용(Read-only) 도구는 빠르게 유지됩니다. 거버넌스(governance)가 필요한 변동(mutations) 작업에만 requiresApproval을 부여합니다.

가벼운 컨트롤러(controller)를 통해 HTTP를 노출합니다 (동반 데모는 **POST /billing**을 사용합니다):

import { Body, Controller, Post } from '@hazeljs/core';
import { AgentService } from '@hazeljs/agent';

...

리스닝 및 승인 (개발 vs 운영)

개발: 통합 테스트를 위한 자동 승인

import { Service } from '@hazeljs/core';
import { AgentService, AgentEventType } from '@hazeljs/agent';
import type { AgentEvent, ToolApprovalEventData } from '@hazeljs/agent';
...

인간의 검토를 의도적으로 전혀 거치지 않으려는 것이 아니라면, 이 리스너(listener)를 프로덕션(production) 환경에 절대 배포하지 마세요.

Production: 관리자 API를 통한 인큐(enqueue) 및 승인

  1. TOOL_APPROVAL_REQUESTED 발생 시, { requestId, executionId, toolName, payload }를 큐(queue) 또는 DB 행(row)에 푸시(push)합니다.
  2. 런타임(runtime)을 로드하고 approveToolExecution(requestId, approvedByUserId)를 호출하는 POST /admin/approvals/:requestId/approve 엔드포인트를 노출합니다.
  3. 선택적으로 거절(deny) API를 노출하고, 거절된 승인을 사용자에게 보이는 에러로 매핑합니다.

이벤트 페이로드(payload) 형태는 @hazeljs/agent에 문서화되어 있습니다.

일시 중단 후 재개 (Resume after pause)

실행(execution)이 일시 중단/재개(pause/resume)를 위해 구축된 경우, **AgentService.resume(executionId, input?)**은 전체 체인(chain)을 다시 시작하는 대신 저장된 체크포인트(checkpoint)에서 계속 진행합니다. 이를 다음 기능들과 함께 사용하세요:

  • Slack / 이메일 승인 — 워커(worker)가 웹훅(webhook)을 수신하고, 승인을 호출한 다음 resume을 실행합니다.
  • @hazeljs/flow — 에이전트 이벤트와 함께 감사 타임라인(audit timeline)을 제공하며, 외부 승인 신호를 기다리는 모델 WAIT 노드(nodes)를 사용합니다.

로그와 지원 티켓(support tickets)에 초기 execute 결과의 **executionId**를 사용하여, 운영자가 "어떤 실행이 Jane의 승인을 기다렸는가?"를 상관 분석(correlate)할 수 있도록 하세요.

검증 가능한 시나리오

Windows (PowerShell): curl.exe 또는 -d @body.json을 사용하세요.

0) 빌드 (Build) — 도구 parametersdescription이 포함되어 있고, 승인 리스너(approval listener)가 unknownAgentEvent<ToolApprovalEventData> 캐스트(cast)를 사용하는 경우(현재 @hazeljs/agent 타이핑 기준), npm run build가 통과해야 합니다.

1) 읽기 전용 경로 (Read-only path)status.json:

{ "message": "What is the status of order ORD-1001?" }
curl.exe -s -X POST http://localhost:3000/billing 
-H "Content-Type: application/json" 
-d "@status.json"

{ "response": "...", "executionId": "..." }와 함께 HTTP 200 응답을 기대할 수 있으며, 멈춰 있는 실행(stuck run)은 발생하지 않습니다 (모델은 requiresApproval 없이 getOrderStatus를 호출해야 합니다).

2) 환불 경로 (Refund path)refund.json (문구는 모델이 issueRefund를 선택하는 데 도움을 줍니다):

{
  "message": "Refund order ORD-1001 for 5000 cents. Reason: defective item — customer approved in ticket 4412."
}
curl.exe -s -X POST http://localhost:3000/billing 
-H "Content-Type: application/json" 
-d "@refund.json"

개발 전용 (dev-only) 부트스트랩 리스너(bootstrap listener)를 사용하더라도 완료된 응답을 확인할 수 있습니다. 런타임은 툴 본문이 실행되기 전에 **tool.approval.requested**를 방출하고, 즉시 **approveToolExecution**을 거쳐 **tool.approval.granted**를 방출합니다. 프로덕션(production) 환경에서는 자동 승인(auto-approve)을 제거하고, 대신 관리자 API(admin API)를 통해 approveToolExecution을 실행하도록 구성하세요.

프로덕션 적용 전 변경해야 할 사항

  • 무조건적인 자동 승인 제거: NODE_ENV 또는 피처 플래그(feature flag) 뒤로 숨기세요.
  • 승인 결정 영속화 (Persist approval decisions): 감사를 위해 행위자 ID(actor id), 타임스탬프(timestamp), 요청 ID(request id)를 함께 저장하세요.
  • 타임아웃 (Timeout): 실행이 워커(worker)를 영원히 점유하지 않도록 멈춰 있는 승인에 타임아웃을 설정하고, 사용자에게 "만료됨(expired)" 상태를 표시하세요.
  • 감독자(Supervisor) + 승인: 라우팅된 모든 워커는 툴 정책(tool policy)을 상속받는다는 점을 기억하세요. 어떤 워커가 requiresApproval 툴을 노출하는지 문서화해야 합니다.

requiresApproval, 런타임 이벤트(runtime events), 그리고 **resume**은 HazelJS가 **멀티 에이전트의 강력함 (multi-agent power)**을 **엔터프라이즈 제어 (enterprise controls)**와 호환되도록 유지하는 방법입니다. LLM은 여전히 오케스트레이션(orchestrate)을 수행할 수 있지만, 되돌릴 수 없는 작업은 오직 귀하의 시스템만이 열 수 있는 문 뒤에 머물게 됩니다. @hazeljs/agent를 읽어보시고, 장기 대기(long-lived waits)가 필요한 경우 @hazeljs/flow를 참고하세요. 전체 작동 코드 예제는 GitHub에서 확인할 수 있습니다.

패키지 링크

패키지 링크

HazelJS site

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0