Skip to main content

훅이 존재하는 이유

시스템 프롬프트의 지시사항은 제안입니다. 충분히 고집스럽거나 혼란스러운 LLM은 이를 무시할 수 있습니다. 대부분의 에이전트 동작에서는 정확히 그것이 원하는 것입니다 — 지시사항은 모델이 적응할 수 있는 여지를 제공합니다. 하지만 일부 요구사항은 제안이 아닙니다. “모든 민감한 도구 호출은 반드시 기록되어야 한다.” “쓰기 작업은 조직이 읽기 전용 모드일 때 차단된다.” “¥50k 이상의 결제는 실행 전에 사람의 승인이 필요하다.” 이것들은 불변식입니다 — 모델이 어떤 턴에서 결정하든 관계없이 반드시 유지되어야 하는 시스템의 사실입니다. 훅은 에이전트의 실행 생명주기에서 잘 정의된 지점에서 LLM 루프 외부에서 실행되는 코드입니다. LLM은 훅을 볼 수 없습니다. LLM은 훅에 이의를 제기할 수 없습니다. LLM은 훅을 단계를 건너뛰도록 설득할 수 없습니다. PreToolUse 훅이 allow=False를 반환하면, 추론 추적이 아무리 강력하더라도 도구 호출은 발생하지 않습니다. 이것이 중요한 아키텍처 구분입니다: 훅은 FIM One이 “에이전트는 ~해야 한다”를 “에이전트는 ~할 수 없다”로 바꾸는 방법입니다.

훅이 연결되는 위치

현재 세 가지 훅 포인트가 정의되어 있습니다. 각각은 에이전트가 한 번의 루프 반복 중에 통과하는 경계를 표시합니다: 같은 포인트의 여러 훅은 우선순위 순서대로 실행됩니다. 이전 PreToolUse 훅의 재작성된 인자는 이후 훅으로 전달되므로 미들웨어가 구성됩니다.

훅 vs. 명령어 선택 기준

요구사항을 프롬프트 명령어로 해결할지 훅으로 해결할지 결정하는 것은 “런타임 어설션 vs. 코드 주석” 계산과 동일합니다: 경험칙: 잘못된 동작이 인시던트라면 훅을 사용하세요. 잘못된 동작이 사소한 불편함이라면 명령어로 충분합니다.

훅 계약

훅은 PreToolUseHook, PostToolUseHook, 또는 SessionStartHook의 서브클래스이며 하나의 필수 메서드를 가집니다:
전달된 HookContexttool_name, tool_args, agent_id, user_id, 그리고 엔진이 요청별 사실(조직 id, 대화 id, 커넥터 작업의 requires_confirmation 플래그 등)로 채우는 유연한 metadata 딕셔너리를 포함합니다. 반환된 HookResult는 결과를 제어합니다:
  • allow: bool = True — 도구 호출 진행 여부 (PostToolUse / SessionStart에서는 무시됨)
  • error: str | None — 인간이 읽을 수 있는 이유로, 차단될 때 LLM에 관찰로 표시됨
  • modified_args: dict | None — 설정된 경우, 실행 전에 도구 인자를 대체함
  • modified_result: Any | None — 설정된 경우 (PostToolUse), LLM으로 반환되기 전에 관찰을 대체함
  • side_effects: list[str] — 훅이 수행한 작업의 감사 추적으로, 에이전트의 추적에 병합됨

사례 연구: FeishuGateHook

이 시스템 위에 출시된 첫 번째 훅은 FeishuGateHook입니다. 이는 PreToolUse 훅으로, requires_confirmation=True로 표시된 모든 도구를 조직의 Feishu 그룹에 게시된 인간 승인 카드로 변환합니다. 이 훅은 전체 생명주기를 실행합니다: 이 설계가 제공하는 것:
  • 도구 호출이 진정으로 일시 중지됩니다. 에이전트의 SSE 스트림은 “I will call oa__purchase_pay”와 관찰 사이에서 일시 중지됩니다. 사용자는 에이전트가 대기 중인 것을 보며, 이는 내부에서 일어나는 일과 일치합니다.
  • 승인이 프로세스 재시작을 견딥니다. 대기 중인 행은 데이터베이스에 있으며, 메모리에는 없습니다. 카드가 미결 상태일 때 백엔드가 재시작되면, 다음 폴링이 중단된 지점부터 계속됩니다.
  • 결정이 감사됩니다. ConfirmationRequestpayload, responded_at, responded_by_open_id 및 최종 상태를 유지합니다. 누가 무엇을 언제 승인했는지에 대한 감사 가능한 기록입니다.
  • 의사 결정 루프에 LLM이 없습니다. 모델이 도구 호출을 생성합니다. 인간이 판정을 생성합니다. 훅은 결정론적 다리입니다.
FeishuGateHook은 구성된 Feishu Channel에 의존합니다. 훅은 채널의 send_interactive_card() 메서드를 통해 카드를 전송하고 채널이 파싱한 콜백 이벤트를 수신합니다. 이러한 분리는 의도적입니다. 훅은 “승인 상태 머신”을 소유하고, 채널은 “IM 플랫폼 메커니즘”을 소유합니다. 동일한 훅은 내일 Slack이나 WeCom을 대상으로 할 수 있으며, 로직을 변경하지 않고 채널 구현만 변경하면 됩니다.

계획된 훅 (v0.9)

네 가지 훅 패턴이 v0.9 로드맵에 있으며, 모두 동일한 라이프사이클을 재사용합니다: 사용자 정의 훅 레이어도 계획 중입니다: 에이전트당 YAML 구성(hooks: [...])으로 일치하는 도구 이벤트에서 실행할 셸 명령 또는 Python 호출 가능 객체를 선언합니다. 이는 최신 에이전트 프레임워크(Claude Code, OpenDevin)가 수렴한 패턴을 따릅니다 — 훅 기반 강제 실행은 “반드시 발생해야 하는” 로직을 프롬프트 밖으로 유지합니다.

Hooks vs. Channels

두 추상화는 직교하는 문제를 해결합니다: Hooks는 Channels를 사용합니다 — 외부 세계와 통신해야 하는 hook(카드 전송, 알림 게시, 그룹으로 에스컬레이션)은 조직의 Channel을 호출합니다. 이를 사용하는 hook이 없는 channel도 여전히 유용합니다(예: 에이전트가 도구를 통해 사전에 알림을 보낼 수 있음). 하지만 승인 게이트 패턴은 특히 두 부분이 모두 준비되어 있어야 합니다. 다르게 말하면: Channels는 “인간과 어떻게 통신할 것인가”라는 인프라이고, Hooks는 “언제 인간과 통신해야 하는가”라는 정책입니다. 프로덕션 인간-루프 워크플로우에는 둘 다 필요합니다.

Current state (v0.8.4)

Shipped된 것과 앞으로 진행할 것의 스냅샷:
  • HookRegistry, HookContext, HookResult primitives가 ReAct와 DAG 모두에 연결됨
  • PreToolUseHook / PostToolUseHook / SessionStartHook 추상 기본 클래스
  • FeishuGateHook — 완성됨. ConfirmationRequest 테이블, 폴링 루프, 타임아웃/만료, 콜백 기반 상태 전환 포함
  • ✅ Feishu 채널 콜백 엔드포인트가 card.action.trigger를 디코딩하고 pending 행을 업데이트함
  • ✅ Agent 레벨 hook 선언: agent.model_config_json.hooks.class_hooks가 모든 ReAct/DAG 세션에서 인스턴스화된 HookRegistry로 해석됨
  • Hooks가 모든 실행 표면에서 작동: 메인 채팅 경로(Portal, API, DAG), 위임된 sub-agent(CallAgentTool), Workflow AGENT 노드. Eval Center는 의도적으로 hooks를 우회함 (자동화된 평가는 인간 승인에서 차단되면 안 됨). 위임된 agent는 호출자의 registry를 상속하지 않음 — 자신의 model_config_json에서 자신의 것을 빌드함. 확인 闸门이 모든 agent에 자동으로 첨부되므로, 재구축은 상속의 상위 집합임: delegate는 闸门 plus 자신이 선언한 것을 얻고, 闸门은 호출자의 require_confirmation_for_all이 아닌 delegate의 것을 해석함. 두 표면 모두 closed fail: hooks를 빌드할 수 없는 agent는 실행되지 않음.
  • AuditLogHook, ReadOnlyGuard, ResultTruncateHook, ConnectorRateLimitHook (v0.9)
  • ❌ 사용자 정의 YAML hook 선언 (v0.9)
Hook System은 v0.9 프로덕션 강화를 위한 하중을 지탱하는 기초임. 첫 번째 사용자(FeishuGateHook)는 또한 자체적으로 프로덕션 기능이므로, 전체 hook 카탈로그를 기다리지 않고 2026-04-24 roadshow를 위해 스켈레톤이 조기에 shipped됨.