훅

훅 (Hooks)

에이전트 실행의 여러 지점에서 셸 명령을 실행해 동작을 결정적으로 제어해요.

출처: 문서

본문

훅을 사용하면 에이전트 라이프사이클의 핵심 지점에서 셸 명령이나 스크립트를 실행할 수 있어요. LLM의 동작과 함께 동작하는 결정적 제어를 제공해 검증, 로깅, 환경 설정 등을 가능하게 해요.

참고 — 사용 사례:

  • 실행 전에 도구 입력 검증 또는 변환
  • 모든 도구 호출을 감사 파일에 로깅
  • 커스텀 규칙에 기반한 위험한 작업 차단
  • 모델에 도달하기 전에 사용자 프롬프트 검증·서툴게(redact)·풍부화
  • 사용자에게 확인하지 않고 프로그래매틱하게 도구 호출 승인·거부
  • 컨텍스트 윈도우 컴팩션 steer 또는 거부(veto)
  • 멀티 에이전트 설정에서 하위 에이전트 핸드오프 감사
  • 세션 시작 시 환경 설정
  • 세션 종료 시 리소스 정리
  • 사용자에게 반환하기 전에 모델 응답 로깅 또는 검증
  • 에이전트 오류·경고에 대한 외부 알림 전송

훅 유형 (Hook Types)

Docker Agent는 다음 훅 이벤트를 디스패치해요:

이벤트 발화 시점 차단 가능?
pre_tool_use 도구 호출 실행 전 예
tool_response_transform 도구 실행과 런타임의 응답 방출/기록 사이 아니오
post_tool_use 도구 완료 후 — 성공과 실패 둘 다에 발화 예
permission_request 런타임이 사용자에게 도구 승인을 요청하기 직전 예
session_start 세션이 시작·재개될 때 아니오
user_prompt_submit 사용자 메시지당 한 번, 제출 후 모델 실행 전 예
user_steering_messages_submit 큐잉된 steering 메시지가 배출될 때마다(턴 중, 중단 후, 유휴 중) 예
user_followup_submit 큐잉된 후속 메시지가 새 턴을 시작할 때마다(턴 끝) 예
turn_start 모든 에이전트 턴 시작 시(모델 호출마다) 아니오
turn_end 모든 에이전트 턴 종료 시 — 턴이 왜 끝났든 발화 아니오
before_llm_call 모든 모델 호출 직전(turn_start 이후) 예
after_llm_call 모든 성공적인 모델 호출 후, 응답 기록 전 아니오
session_end 세션 종료 시 아니오
pre_compact 런타임이 세션 트랜스크립트를 컴팩션하기 직전 예
before_compaction 컴팩션이 실행되기 직전 — 거부하거나 커스텀 요약 제공 가능 예
after_compaction 성공적인 컴팩션 후(요약이 세션에 적용됨) 아니오
subagent_stop 하위 에이전트(전송된 작업 / 백그라운드 / 스킬 하위 세션) 종료 시 아니오
on_user_input 에이전트가 사용자 입력을 기다릴 때 아니오
stop 모델이 응답을 끝낼 때 아니오
notification 에이전트가 알림(오류 또는 경고)을 방출할 때 아니오
on_error 런타임이 턴 중 오류를 만날 때(notification과 함께 발화) 아니오
on_max_iterations 런타임이 구성된 max_iterations 한도에 도달할 때 아니오
on_agent_switch 런타임이 활성 에이전트를 옮길 때(transfer_task, handoff, return) 아니오
on_session_resume 사용자가 max_iterations를 넘어 계속 진행을 명시적으로 승인할 때 아니오
on_tool_approval_decision 런타임의 승인 체인(permissions / yolo / readonly / ask)이 해석된 후 아니오
worktree_create docker agent run --worktree가 git worktree를 만든 후, 세션 전 예

참고 — 두 컴팩션 이벤트: pre_compact와 before_compaction 둘 다 컴팩션 직전에 발화해요. pre_compact는 원래 이벤트이고 additional_context로 지침을 추가해 LLM 생성 요약을 steer하는 데 가장 적합해요. before_compaction은 더 새로운 구조화 이벤트예요: 입력/출력 토큰 수, 모델의 컨텍스트 한도, compaction_reason을 담아 핸들러가 실제 세션 압박에 기반해 결정할 수 있고, hook_specific_output.summary로 LLM 생성 요약을 그대로 대체할 수 있어요.

구성 (Configuration)

에이전트 YAML 파일의 에이전트 hooks: 블록 아래에서 직접 훅을 구성할 수 있어요:

agents:
  root:
    model: openai/gpt-4o
    description: An agent with hooks
    instruction: You are a helpful assistant.
    hooks:
      # Run before specific tools
      pre_tool_use:
      - matcher: "shell|edit_file"
        hooks:
        - type: command
          command: "./scripts/validate-command.sh"
          timeout: 30

      # Run after all tool calls
      post_tool_use:
      - matcher: "*"
        hooks:
        - type: command
          command: "./scripts/log-tool-call.sh"

      # Run when session starts
      session_start:
      - type: command
        command: "./scripts/setup-env.sh"

      # Run when session ends
      session_end:
      - type: command
        command: "./scripts/cleanup.sh"

      # Run when agent is waiting for user input
      on_user_input:
      - type: command
        command: "./scripts/notify.sh"

      # Run when the model finishes responding
      stop:
      - type: command
        command: "./scripts/log-response.sh"

      # Run on agent errors and warnings
      notification:
      - type: command
        command: "./scripts/alert.sh"

각 이벤트는 훅 목록을 받아요. 단일 훅은 목록 대시 없이 매핑으로 직접 쓸 수도 있어요:

stop:
  type: command
  command: "./scripts/log-response.sh"

전역(사용자 수준) 훅 (Global, user-level hooks)

전역 훅은 실행하는 모든 에이전트에 같은 훅 구성을 적용하게 해줘요. 사용자 구성 파일 ~/.config/cagent/config.yaml의 settings.hooks 아래에 정의해요:

# ~/.config/cagent/config.yaml
settings:
  hooks:
    session_start:
    - type: command
      command: "~/.config/cagent/hooks/session-start.sh"
    pre_compact:
    - type: command
      command: "~/.config/cagent/hooks/pre-compact.sh"
    pre_tool_use:
    - matcher: "shell"
      hooks:
      - type: command
        command: "~/.config/cagent/hooks/check-shell.sh"

전역 훅은 에이전트 수준 훅과 같은 스키마를 사용하고 가산(additive)돼요. 이벤트가 여러 곳에 구성되면 모든 일치 훅이 이 순서로 실행돼요:

  • 에이전트 YAML의 에이전트 구성 훅
  • settings.hooks의 전역 훅
  • <config-dir>/hooks.d/의 훅 드롭인(사전순 파일 순서)
  • --hook-* 플래그의 CLI 훅

전역 훅은 개별 에이전트가 억제할 수 없어요. 사용자 전체 감사 로깅, 개인 가드레일, 알림, 어디서든 적용돼야 하는 설정/정리 동작에 사용하세요.

훅 드롭인 파일 (Hook drop-in files, hooks.d)

Docker Agent와 통합하는 외부 도구(터미널 에뮬레이터, IDE, 감사·관측 사이드카)가 훅을 설치하려고 사용자의 config.yaml을 다시 쓰지 않아야 해요. 대신 Docker Agent는 사용자 구성 옆의 hooks.d 디렉토리(기본: ~/.config/cagent/hooks.d/)의 모든 *.yaml / *.yml 파일도 로드해요. 각 파일은 settings.hooks의 내용과 같은 스키마의 독립형 hooks 블록이에요:

# ~/.config/cagent/hooks.d/50-mytool.yaml
session_start:
- type: command
  command: mytool notify --event session-start
stop:
- type: command
  command: mytool notify --event stop
  • 파일은 사전순으로 로드되고 settings.hooks 뒤에 가산적으로 병합돼요(순서 제어에 10-, 50- 같은 숫자 접두사 사용).
  • 잘못된 파일은 로그 경고와 함께 건너뛰어져요. 깨진 드롭인이 실행을 깨지 않아요.
  • 통합 설치 = 자체 포함 파일 하나 쓰기; 제거 = 삭제. 공유 파일 편집 없음, 도구 간 충돌 없음.

구성 디렉토리는 --config-dir 플래그나 DOCKER_AGENT_CONFIG_DIR(레거시 CAGENT_CONFIG_DIR) 환경 변수로 이동할 수 있고, 외부 도구가 비기본 구성 디렉토리에서 hooks.d를 찾는 데 사용할 수 있어요.

내장 훅 (Built-in Hooks)

셸 명령 훅 외에 Docker Agent는 작은 내장 훅 라이브러리를 제공해요 — 하위 프로세스를 스폰하지 않고 실행되는 프로세스 내 Go 함수예요. type: builtin으로 호출하며, command는 내장의 등록 이름이고 args는 내장의 파라미터로 전달돼요.

hooks:
  turn_start:
  - type: builtin
    command: add_date
  - type: builtin
    command: add_prompt_files
    args:
    - GUIDELINES.md
    - PROJECT.md
  session_start:
  - type: builtin
    command: add_environment_info
  before_llm_call:
  - type: builtin
    command: max_iterations
    args: [ "50" ]

내장은 보통 제로 구성이고 프로세스를 포크하지 않으므로 동등한 셸 훅보다 빠르다. "매 턴/세션에 컨텍스트 주입"의 일반적인 패턴을 기본으로 다뤄요.

사용 가능한 내장 (Available built-ins)

내장 이벤트 Args 하는 일
add_date turn_start 없음 Today's date: YYYY-MM-DD를 앞에 붙여 모델이 항상 현재 날짜를 알게 해요.
add_environment_info session_start 없음 작업 디렉토리, git-repo 상태, OS, CPU 아키텍처 추가.
add_prompt_files turn_start [file1, file2, ...] workdir 계층(위로 걸어가)과 홈 디렉토리에서 각 명명된 파일을 읽어 내용을 추가.
add_git_status turn_start 없음 git status --short --branch 출력 추가(git 저장소 밖이거나 git이 없으면 no-op).
add_git_diff turn_start 없음, 또는 ["full"] 기본 git diff --stat 추가. args: ["full"]로 전체 unified diff 방출. 출력은 4 KB로 제한.
add_directory_listing session_start 없음 cwd의 최상위 항목 알파벳 목록 추가(. 파일 건너뜀, 100개 캡 + "... and N more").
add_user_info session_start 없음 현재 OS 사용자(사용자 이름과 전체 이름)와 호스트 이름 추가.
add_recent_commits session_start 없음, 또는 ["<N>"] git log --oneline -n N 추가. N은 기본 10; 양의 정수로 재정의.
max_iterations before_llm_call ["<N>"] (필수) N번의 모델 호출 후 에이전트를 하드 스톱. 무상태: 런타임이 매 디스패치마다 반복 카운터를 공급.
snapshot session_start, turn_start, turn_end, pre_tool_use, post_tool_use, session_end 없음 Docker Agent 데이터 디렉토리 아래 shadow git 저장소에 파일시스템 스냅샷 기록. git 저장소 밖 no-op. 소스 저장소의 무시 규칙을 존중하고 2 MiB보다 큰 새로 추가된 파일 건너뜀.
redact_secrets pre_tool_use, before_llm_call, tool_response_transform 없음 감지된 시크릿(API 키, 토큰, 개인 키, …)을 도구 호출 인자, 나가는 채팅 콘텐츠, 도구 출력에서 털어내요. 같은 내장이 세 이벤트를 모두 처리하고 이벤트 이름으로 디스패치. 에이전트의 redact_secrets: true로 세 이벤트 모두에 자동 등록 — 수동 배선은 examples/redact_secrets_hooks.yaml 참조.
limit_large_tool_results tool_response_transform, session_end 없음 항상 켜진 안전 훅 — 런타임이 자동 주입, 구성 불필요. filesystem, shell, mcp, a2a 카테고리에서 온 도구 결과가 2,000줄 또는 50 KiB를 초과하면 전체 페이로드를 세션별 임시 파일에 쓰고 대화에서 안내문 + 제한된 발췌(2,000줄, 최대 50 KiB)로 교체해요: 대부분 도구는 꼬리, 내장 filesystem의 read_file은 머리 — 그 안내문은 line/limit로 후속 호출을 제안해요. session_end 다리는 임시 디렉토리를 삭제. 내부 toolset(memory, plan, tasks, think, …)은 영향받지 않아요.
safer_shell pre_tool_use 없음 지원 중단된 호환성 심. 런타임이 이제 모든 셸 명령을 네이티브로 분류하고(safe / destructive / unknown) 세션 안전 모드를 통해 게이트하므로 이 내장은 더 이상 판정을 방출하지 않아요. 고정된 항목은 호출에 분류 메타데이터(safety_label, blast_radius, category, reason)를 붙이는 순수 라벨러로 계속 동작. 내부적으로 도구 이름으로 필터(비 셸 호출 no-op).
unload on_agent_switch 없음 이전 에이전트의 각 DMR 모델 엔드포인트(/_unload 기본, 모델별 unload_api로 재정의 가능)에 {"model": "<id>"}를 POST해 방금 떠나는 모델이 쥐고 있던 GPU/RAM을 해제. 순수 HTTP — on_agent_switch에 런타임이 실어 보내는 모델 스냅샷을 읽고 provider별 런타임 상태에 의존하지 않아요. 비 DMR provider(OpenAI, Anthropic, …)는 조용히 건너뛰어져 교차 provider 체인이 안전해요. 오류는 로그되고 삼켜지며, 에이전트 전환은 느리거나 도달 불가 엔진에서 절대 블록하지 않아요(각 호출 10s 시간 초과). examples/unload_on_switch.yaml 참조.

참고 — 턴당 vs 세션당: turn_start 내장은 매 턴 재계산되고 세션에 영속되지 않는 일시적 컨텍스트를 기여해요 — 날짜나 현재 git 상태 같은 빠르게 움직이는 신호에 완벽. session_start 내장은 세션당 한 번 실행되고 그 컨텍스트는 턴과 재개를 넘어 영속돼요 — OS 사용자나 초기 디렉토리 목록 같은 안정적인 컨텍스트에 선택.

참고 — 자동 주입 내장: 에이전트 플래그 add_date: true, add_environment_info: true, add_prompt_files: [...], redact_secrets: true는 일치하는 내장 훅을 자동 등록하는 축약형이에요. hooks: 아래에 반복할 필요 없어요 — 플래그나 훅 항목(들) 중 하나만 설정하세요. redact_secrets: true는 pre_tool_use, before_llm_call, tool_response_transform 세 곳 모두에 같은 내장을 자동 등록해요. 도구별 매처, 다른 재작성자와의 순서 등 더 세밀한 제어를 위해 그 중 어떤 부분집합이든 손으로 배선할 수도 있어요.

limit_large_tool_results는 런타임이 무조건 주입해요 — 항상 활성이고 구성에서 제거할 수 없어요.

최소 스냅샷 배선은 이렇게 생겼어요:

hooks:
  turn_start:
  - type: builtin
    command: snapshot
  turn_end:
  - type: builtin
    command: snapshot
  session_end:
  - type: builtin
    command: snapshot

shadow 저장소는 트리 객체만 저장해요. 커밋은 절대 쓰지 않고 소스 저장소의 .git 디렉토리를 건드리지 않아요. 소스 저장소의 .gitignore와 info/exclude 규칙은 각 캡처 전에 미러링되어 무시된 파일이 스냅샷에 나타나지 않아요. 내장은 파일이 바뀌었을 때만 undo 체크포인트를 기록하므로, 최종 no-op 모델 응답이 마지막으로 변경된 스냅샷을 숨기지 않아요.

사용자 구성으로 모든 에이전트에 스냅샷을 전역 활성화할 수도 있어요:

settings:
  snapshot: true

snapshot을 생략하거나 false로 설정하면 자동 스냅샷을 끈 채로 두고, 수동으로 구성된 스냅샷 훅은 계속 실행돼요.

완전한 스냅샷 훅 구성은 examples/snapshot_hooks.yaml 참조. 스냅샷 기능과 /undo / /snapshots 명령 개요는 Snapshots 참조.

경고 — 두 가지 형태의 max_iterations: max_iterations 에이전트 필드는 자체 UX가 있어요(일시 중지하고 사용자에게 한도 너머 재개를 묻음). max_iterations 내장 훅은 재개 없는 하드 스톱이에요 — 카운터가 걸리면 에이전트가 차단 결정으로 종료돼요. 대화형 세션에는 에이전트 필드를, 무인 실행에서 협상 불가한 상한을 강제하려면 내장 훅을 사용하세요.

매처 패턴 (Matcher Patterns)

matcher 필드는 정규식 패턴으로 도구 이름을 매칭해요:

패턴 매치
* 모든 도구
shell shell 도구만
shell|edit_file shell 또는 edit_file
mcp:.* 모든 MCP 도구(regex)

훅 입력 (Hook Input)

훅은 stdin으로 이벤트에 대한 컨텍스트가 있는 JSON 입력을 받아요:

{
  "session_id": "abc123",
  "cwd": "/path/to/project",
  "hook_event_name": "pre_tool_use",
  "tool_name": "shell",
  "tool_use_id": "call_xyz",
  "tool_input": {
    "cmd": "rm -rf /tmp/cache",
    "cwd": "."
  }
}

공통 필드 (Common Fields)

모든 훅 이벤트가 담는 것:

필드 설명
session_id 현재 세션의 ID.
cwd 런타임의 작업 디렉토리.
hook_event_name 이벤트 이름(예: pre_tool_use).

이벤트별 추가 필드 (Per-Event Extra Fields)

공통 필드 외에 각 이벤트는 자체 페이로드를 실어 보내요:

이벤트 추가 필드
pre_tool_use agent_name, tool_name, tool_use_id, tool_input
tool_response_transform tool_name, tool_use_id, tool_input, tool_response
post_tool_use agent_name, tool_name, tool_use_id, tool_input, tool_response, tool_error
permission_request agent_name, tool_name, tool_use_id, tool_input
session_start source — startup, resume, clear, compact 중 하나
user_prompt_submit prompt — 방금 제출한 텍스트
user_steering_messages_submit steering_messages — 배출된 steering 메시지, 제출 순서
user_followup_submit prompt — 디큐된 후속 메시지의 텍스트
turn_start 없음(공통 필드만)
turn_end agent_name, reason — normal, continue, steered, error, canceled, hook_blocked, loop_detected 중 하나
before_llm_call iteration — 1 기반 run-loop 반복 카운터(이 훅이 게이팅하는 모델 호출), model_id
after_llm_call agent_name, stop_response, last_user_message, model_id, usage, cost
session_end reason — clear, logout, prompt_input_exit, other 중 하나
pre_compact source — manual, auto, overflow, tool_overflow 중 하나
before_compaction input_tokens, output_tokens, context_limit, compaction_reason(threshold / overflow / manual 중 하나)
after_compaction input_tokens, output_tokens, context_limit, compaction_reason, summary
subagent_stop agent_name(하위 에이전트), parent_session_id, stop_response
on_user_input 없음
stop agent_name, stop_response, last_user_message
notification notification_level(error 또는 warning), notification_message
on_error notification_level(항상 error), notification_message
on_max_iterations notification_level(항상 warning), notification_message
on_agent_switch from_agent, to_agent, agent_switch_kind(transfer_task, transfer_task_return, handoff, force_handoff)
on_session_resume previous_max_iterations, new_max_iterations
on_tool_approval_decision tool_name, tool_use_id, tool_input, approval_decision, approval_source
worktree_create worktree_path, worktree_branch, worktree_source_dir(cwd도 새 worktree로 설정)

참고:

  • post_tool_use의 tool_response는 도구의 결과를 담고, tool_error는 도구가 실패했을 때 true(실패 세부 사항은 tool_response 안에 표면화).
  • pre_tool_use, post_tool_use, permission_request의 agent_name은 도구 호출을 발행한 에이전트를 식별 — 멀티 에이전트 설정에서 이는 활성 하위 에이전트를 따르고 항상 루트 에이전트는 아니에요.
  • prompt는 user_prompt_submit에서만 채워져요. 하위 세션(전송된 작업, 백그라운드 에이전트, 스킬)은 시작 메시지가 사용자가 쓴 게 아니라 런타임이 합성하므로 이 이벤트를 발화하지 않아요.
  • steering_messages는 user_steering_messages_submit에서만 채워져요. 런타임이 steering 큐에서 막 배출한 사용자 메시지 — 에이전트가 이미 작업 중일 때 제출된 메시지(턴 중, 모델이 멈춘 후, 첫 모델 호출 전 유휴 동안).
  • prompt는 user_followup_submit에서도 채워져, 디큐된 후속 메시지의 텍스트를 담아요(턴 끝 처리를 위해 FollowUp API / 큐로 큐잉된 사용자 메시지 — 턴 중 steering과 반대).
  • stop_response는 stop, after_llm_call, subagent_stop에 모델의 최종 어시스턴트 텍스트를 담아요. last_user_message는 디스패치 시점에 가장 최근 사용자 메시지를 담아요.
  • model_id는 after_llm_call(및 before_llm_call)에 표준 <provider>/<model> 형태(예: anthropic/claude-sonnet-4-5)로 채워져요. harness 에이전트의 경우 model_id는 표준 모델 이름이 아닌 harness 라벨(예: claude-code) — Coding Harnesses 참조.
  • usage와 cost는 after_llm_call에서만 채워져요. usage는 호출별 토큰 사용 객체(input_tokens, output_tokens, cached_input_tokens, cached_write_tokens, reasoning_tokens — 마지막은 비 추론 모델에서 생략)이고, 전체 객체는 provider가 사용을 보고하지 않으면 없어요. cost는 그 한 번의 모델 응답의 USD 가격이에요. 네이티브 모델 호출에서는 usage와 모델의 가격표에서 계산된 가격이고 세션이 턴에 기록하는 비용과 같아요: 응답이 무가격(가격 데이터 없음, 또는 사용 없음)이면 없고, 유료지만 무료인 호출에서는 명시적 0이에요 — 따라서 있는 cost는 권위적이고 없는 것은 "unpriced"를 뜻하며 usage를 교차 확인할 필요 없어요. 카탈로그가 가격을 매기지 않는 모델(커스텀 엔드포인트, 로컬 모델)은 모델 수준 cost 구성으로 명시적 가격을 매길 수 있어요.(harness 에이전트의 경우 의미가 다름 — 다음 참고 참조) 따라서 비용 원장은 런타임 이벤트 채널을 구독하지 않고 페이로드만으로 호출별 지출을 기록할 수 있어요.
  • harness 에이전트에서 cost는 계산된 가격이 아니라 harness가 보고한 호출 총액이고, harness가 0이 아닌 비용을 보고할 때만 있어요(일부 harness — 예: codex — 는 토큰 수를 보고하지만 비용은 보고 안 함 — 그런 턴은 기록된 메시지가 0을 저장해도 cost 없이 usage를 담아요).
  • after_llm_call은 모든 모델 호출에서 발화해요. 하위 세션(전송된 작업, 백그라운드 에이전트, 스킬) 내부의 호출 포함. 그런 경우 session_id는 하위 세션의 id예요. 따라서 after_llm_call 이벤트에 걸쳐 cost를 합하면 하위 세션(비용이 영속되기 전에 오류를 낸 하위 세션까지)을 포함한 모든 지출을 잡아요. 별도로 조회한 세션 비용 총액을 그 위에 더하지 마세요: 런타임 자신의 총액은 이미 완료된 하위 세션 지출로 재귀해 포함하므로 둘을 합치면 이중 계산이 돼요. 합산된 훅 비용(권위 있는 원장)을 단일 소스로 고르세요.
  • context_limit는 모델 정의를 사용할 수 없을 때 0이에요(0을 "알 수 없음"으로 취급, 진짜 한도로 아님).
  • approval_decision는 allow, deny, canceled 중 하나. approval_source는 어느 단계가 결정했는지의 안정적 분류자(예: yolo, session_permissions_allow, session_permissions_deny, team_permissions_allow, team_permissions_deny, pre_tool_use_hook_allow, pre_tool_use_hook_deny, readonly_hint, user_approved, user_approved_session, user_approved_safe, user_approved_tool, user_rejected, context_canceled).

훅 출력 (Hook Output)

훅은 stdout에 JSON 출력으로 다시 통신해요:

{
  "continue": true,
  "stop_reason": "Optional message when continue=false",
  "suppress_output": false,
  "system_message": "Warning message to show user",
  "decision": "block",
  "reason": "Explanation for the decision",
  "hook_specific_output": {
    "hook_event_name": "pre_tool_use",
    "permission_decision": "allow",
    "permission_decision_reason": "Command is safe",
    "updated_input": { "cmd": "modified command" }
  }
}

모든 필드는 선택적이에요. {}를 반환하면(또는 출력 없음) "아무것도 하지 말고 정상 계속"을 뜻해요.

출력 필드 (Output Fields)

필드 타입 설명
continue boolean 실행 계속 여부(기본: true)
stop_reason string continue=false일 때 표시할 메시지
suppress_output boolean 트랜스크립트에서 stdout 숨기기
system_message string 사용자에게 표시할 경고 메시지
decision string 차단용: 작업 방지를 위해 block
reason string 결정에 대한 설명

Pre-Tool-Use / Permission-Request 전용 출력

pre_tool_use(및 permission_request)의 hook_specific_output은 다음을 지원해요:

필드 타입 설명
permission_decision string allow, deny, ask
permission_decision_reason string 결정에 대한 설명
updated_input object 수정된 도구 입력(원본 교체)
metadata object (preempt_yolo: true가 있는 permission_request 및 pre_tool_use 항목만) 도구 호출 확인 프롬프트에 병합되는 문자열 키/값 주석 — 아래 참조

pre_tool_use에서 자동 승인 선점 (Preempting auto-approval from pre_tool_use)

pre_tool_use 항목은 기본적으로 결정적 승인 파이프라인(커스텀 allow 규칙 / 안전 모드) 이후에 발화하므로, 자동 승인된 호출은 완전히 건너뛰어요. 안전 모드(자율 --yolo 포함)와 무관하게 반드시 모든 호출에서 실행해야 하는 보안 중요 검사에는 매처 항목에 preempt_yolo: true를 설정하세요:

hooks:
  pre_tool_use:
  - matcher: "*"
    preempt_yolo: true
    hooks:
    - type: command
      command: ./security-check.sh

그 항목은 Decide() 전의 전용 stage 0에서 발화해요:

  • deny는 호출을 outright 거부해요. 사용자는 프롬프트되지 않아요.
  • ask는 사용자 확인을 강제해요. 기본 pre_tool_use 레인과 permission_request는 이 경로에서 건너뛰어져 정책 수준 allow가 보안 판정을 재정의할 수 없어요. 유일한 예외는 세션 범위 allow 부여(대화형 "이 도구 항상 허용" 결정) — 이 정확한 프롬프트에 응답해 만든 정보를 가진 사용자 옵트인.
  • allow는 조언적이에요 — 파이프라인은 여전히 Decide()와 pre_tool_use의 나머지를 실행해요. 기본 레인의 정규 allow와 같은 모양, 더 일찍 관찰될 뿐.
  • 판정 없음(빈 permission_decision)은 통과(fall through)돼요.

preempt_yolo: true 항목의 훅 충돌은 기본 pre_tool_use 자세와 일치하게 fail-closed(deny)돼요.

선점 항목은 hook_specific_output.metadata(map[string]string)로 구조화 컨텍스트를 붙일 수 있어요. 런타임은 그것을 자체 안전 분류(safety_label, blast_radius, category, reason)에서 이미 파생한 메타데이터 위에 도구 호출 확인 이벤트로 병합해요. TUI 확인 프롬프트에서 특수 렌더링이 있는 키 규칙:

  • safety_label — 세 값 분류: safe, destructive, unknown.
  • blast_radius — safe, low, medium, high, unknown 중 하나. 색상 심각도 배지로 렌더링(녹색 / 노랑 / 빨강 / 흐림).
  • category — 분류 태그(예: fs-delete, dk-volume-del).

그리고 대화상자가 지원 컨텍스트로 보여주는 자유 형식 reason 키. 다른 키는 평문으로 렌더링돼요. 훅 간 키 충돌 시 마지막 쓰기가 이기고, 선점 항목이 런타임 자신의 분류를 이겨요.

Tool-Response-Transform 전용 출력

tool_response_transform의 hook_specific_output은 다음을 지원해요:

필드 타입 설명
updated_tool_response string 재작성된 도구 출력(원본 교체)

이것은 pre_tool_use의 updated_input의 대칭 대응물로, 도구 인자가 아닌 도구 결과에 적용돼요. 재작성은 모든 다운스트림 소비자 — 이벤트 구독자, 영속 세션 파일, post_tool_use 훅 입력, 다음 LLM 호출 — 에 도달해요. 과도한 출력 잘라내기, PII 털기, 도구 방언 정규화에 사용하세요. 내장 redact_secrets는 이 이벤트에 세 번째 다리로 등록돼요.

컨텍스트 기여 이벤트 (Context-Contributing Events)

session_start, user_prompt_submit, user_steering_messages_submit, user_followup_submit, turn_start, post_tool_use, pre_compact, stop의 경우 훅이 hook_specific_output.additional_context를 설정해 대화에 텍스트를 주입할 수 있어요. turn_start 컨텍스트는 일시적(매 턴 재계산, 절대 영속 안 됨). session_start 컨텍스트는 세션 수명 동안 영속돼요. user_steering_messages_submit과 user_followup_submit 컨텍스트는 user_prompt_submit처럼 일시적이에요 — steer/follow-up 턴에만 접합되고 절대 영속되지 않아요.(worktree_create도 stdout을 표면화하지만 대화가 아니라 CLI 사용자에게 — 세션이 아직 존재하지 않음.)

Before-Compaction 전용 출력

before_compaction의 경우 hook_specific_output.summary 필드가 비어 있지 않으면 LLM 생성 컴팩션 요약을 대체해요. 런타임이 문자열을 그대로 적용하고 모델 호출을 건너뛰어요.

{
  "hook_specific_output": {
    "hook_event_name": "before_compaction",
    "summary": "User asked to refactor pkg/foo. Done in commit abc123."
  }
}

decision: "block"(또는 exit code 2)을 반환하면 컴팩션을 완전히 거부해요. compaction_reason이 overflow일 때 거부에 주의하세요: 런타임이 컨텍스트 오버플로 오류에서 복구 중이고 거부하면 세션이 진행을 할 수 없게 돼요.

평문 텍스트 출력 (Plain Text Output)

session_start, user_prompt_submit, user_steering_messages_submit, user_followup_submit, turn_start, post_tool_use, pre_compact, stop 훅의 경우 stdout에 쓴 평문(즉, 유효한 JSON이 아닌 출력)은 에이전트를 위한 추가 컨텍스트로 캡처돼요. pre_compact에서는 컴팩션 프롬프트에 추가되고, 다른 것에서는 이벤트에 따라 대화에 (일시적 또는 영속) 시스템 메시지로 접합돼요.

종료 코드 (Exit Codes)

훅 종료 코드는 특별한 의미가 있어요:

종료 코드 의미
0 성공 — 정상 계속
2 차단 오류 — 작업 중단
기타 오류 — 로그되지만 실행은 계속

훅별 옵션 (Per-hook options)

훅의 기본 시간 초과는 60초예요. 훅에 이름을 주고, 환경 변수를 추가하고, 작업 디렉토리를 선택하고, 비보안 훅 실패 동작을 제어할 수도 있어요:

hooks:
  post_tool_use:
  - matcher: "shell"
    hooks:
    - name: "summarize shell output"
      type: command
      command: "./summarize.sh"
      timeout: 120 # 2 minutes
      working_dir: ./hooks
      env:
        PROFILE: dev
      on_error: warn # warn | ignore | block

pre_tool_use는 안전을 위해 fail-closed예요: 실패한 pre-tool 훅은 on_error와 무관하게 도구 호출을 차단해요.

working_dir과 env는 command와 builtin 훅에 적용돼요. builtin 훅의 경우 working_dir은 command 훅과 같은 로직으로 해석돼요(절대 경로 우선; 상대 경로는 실행자 디렉토리에 결합). working_dir은 ~, $VAR, ${VAR}, ${env.VAR}를 받아들이고, env 값은 순수 ${env.VAR} 형태만 확장(OS 프로세스 환경에서 해석)하며 다른 $는 리터럴로 유지돼요(Variable Expansion in Config Fields 참조). 빈 문자열로 확장되는 working_dir(예: 설정되지 않은 변수)은 경고와 함께 실행자 디렉토리로 폴백해요. model 훅의 경우 두 필드 모두 스키마에서 받아들여지지만 효과가 없어요: model 훅은 프롬프트 템플릿을 렌더링하고 LLM API를 직접 호출해요 — 하위 프로세스가 스폰되지 않고 파일 I/O가 수행되지 않으므로 작업 디렉토리와 환경 변수에 적용 가능한 의미가 없어요.

경고 — 성능: 훅은 동기적으로 실행되고 에이전트 실행을 느리게 할 수 있어요. 훅 스크립트를 빠르고 효율적으로 유지하세요. 로깅 훅에는 노이즈를 줄이려고 suppress_output: true를 고려하세요.

참고 — 세션 종료와 취소: session_end 훅은 세션이 중단될 때(예: Ctrl+C)도 실행되도록 설계됐어요. 여전히 구성된 시간 초과의 적용을 받아요.

예제 (Examples)

검증 스크립트 (Validation Script)

위험한 셸 명령을 차단하는 간단한 pre-tool-use 훅:

#!/bin/bash
# scripts/validate-command.sh

# Read JSON input from stdin
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
CMD=$(echo "$INPUT" | jq -r '.tool_input.cmd // empty')

# Block dangerous commands
if [[ "$TOOL_NAME" == "shell" ]]; then
  if [[ "$CMD" =~ ^sudo ]] || [[ "$CMD" =~ rm.*-rf ]]; then
    echo '{"decision": "block", "reason": "Dangerous command blocked by policy"}'
    exit 2
  fi
fi

# Allow everything else (returning {} means "do nothing, continue normally")
echo '{}'
exit 0

감사 로깅 (Audit Logging)

모든 도구 호출을 로깅하는 post-tool-use 훅:

#!/bin/bash
# scripts/log-tool-call.sh

INPUT=$(cat)
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id')

# Append to audit log
echo "$TIMESTAMP | $SESSION_ID | $TOOL_NAME" >> ./audit.log

# Don't block execution
echo '{"continue": true}'
exit 0

세션 라이프사이클 (Session Lifecycle)

환경 설정·정리를 위한 세션 시작·종료 훅:

hooks:
  session_start:
  - type: command
    timeout: 10
    command: |
      INPUT=$(cat)
      SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
      echo "Session $SESSION_ID started at $(date)" >> /tmp/agent-session.log
      echo '{"hook_specific_output":{"additional_context":"Session initialized."}}'

  session_end:
  - type: command
    timeout: 10
    command: |
      INPUT=$(cat)
      SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
      REASON=$(echo "$INPUT" | jq -r '.reason // "unknown"')
      echo "Session $SESSION_ID ended ($REASON) at $(date)" >> /tmp/agent-session.log

Stop 훅으로 응답 로깅 (Response Logging with Stop Hook)

분석 또는 규정 준수를 위해 모든 모델 응답 로깅:

hooks:
  stop:
  - type: command
    timeout: 10
    command: |
      INPUT=$(cat)
      SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
      RESPONSE_LENGTH=$(echo "$INPUT" | jq -r '.stop_response // ""' | wc -c | tr -d ' ')
      echo "[$(date)] Session $SESSION_ID - Response: $RESPONSE_LENGTH chars" >> /tmp/agent-responses.log

stop 훅은 다음에 유용해요:

  • 응답 품질 검사 — 반환 전에 응답이 기준을 충족하는지 검증
  • 분석 — 응답 길이, 패턴, 콘텐츠 추적
  • 규정 준수 로깅 — 감사를 위한 모든 에이전트 출력 기록

오류 알림 (Error Notifications)

에이전트가 오류를 만날 때 경고 전송:

hooks:
  notification:
  - type: command
    timeout: 10
    command: |
      INPUT=$(cat)
      LEVEL=$(echo "$INPUT" | jq -r '.notification_level // "unknown"')
      MESSAGE=$(echo "$INPUT" | jq -r '.notification_message // "no message"')
      echo "[$(date)] [$LEVEL] $MESSAGE" >> /tmp/agent-notifications.log

notification 훅은 다음 때 발화해요:

  • 모델이 오류를 반환(모든 모델 실패) — on_error도 발화
  • 변질된 도구 호출 루프 감지 — on_error도 발화
  • 최대 반복 한도 도달 — on_max_iterations도 발화

notification_level을 파싱하지 않고 이 조건 중 하나에 대한 구조화 핸들러를 원하면 notification 대신 on_error와 on_max_iterations를 사용하세요.

턴 시작: 턴당 컨텍스트 (Turn-Start)

turn_start는 모든 에이전트 턴 시작 시(모델 호출마다) 발화해요. additional_context(또는 평문 stdout)로 기여하는 무엇이든 그 턴에만 임시 시스템 메시지로 추가돼요 — 세션에 영속되지 않아요. 날짜, 현재 git 상태, 턴당 프롬프트 파일 같은 빠르게 움직이는 신호에 사용하세요. 내장 훅 add_date, add_prompt_files, add_git_status, add_git_diff는 모두 이 이벤트를 대상으로 해요.

턴 끝: 턴당 파이널라이저 (Turn-End)

turn_end는 turn_start의 대칭 대응물이에요. 반복이 끝나면 턴마다 한 번 — 이유와 무관하게 — 발화해요. 런타임은 모든 종료 경로(정상 중단, 오류, 훅 주도 종료, 루프 검출기, 컨텍스트 취소까지)에서 디스패치를 보장하고, Ctrl+C에서 핸들러가 완료까지 실행되도록 내부적으로 context.WithoutCancel을 사용해요.

reason 필드는 종료를 분류해요:

reason 때
normal 모델이 추가 호출 없이 깨끗하게 완료
continue 더 많은 반복이 올 것(예: 도구 호출, 후속 메시지)
steered 배출된 steer 메시지가 재진입을 촉구
error 모델 호출 실패(handleStreamError가 루프에서 종료)
canceled 컨텍스트 취소(예: Ctrl+C)
hook_blocked before_llm_call 또는 post_tool_use가 호출 거부
loop_detected 연속 도구 호출 루프 검출기가 턴 종료

turn_end는 관찰적이에요 — 결과는 무시돼요. 턴 시간 측정, 턴당 메트릭(토큰 사용, 도구 수) 누적, turn_start와 대칭으로 외부 관측 파이프라인에 알리는 데 사용하세요.

Before/After-LLM-Call: 예산 가드와 모델 감사

before_llm_call은 모든 모델 호출 직전(turn_start가 메시지를 조립한 후)에 발화해요. 컨텍스트를 기여할 수 없지만(turn_start 사용) decision: block(또는 exit code 2)을 반환해 run을 멈출 수 있어요. 내장 max_iterations 훅은 이 이벤트 위에 하드 캡을 구현해요.

after_llm_call은 각 성공적인 모델 호출 직후, 응답이 세션에 기록되고 도구 호출이 디스패치되기 전에 발화해요. 어시스턴트 텍스트는 stop_response에 있고, 호출의 usage와 cost는 턴당 토큰 사용과 계산된 USD 지출을 담아요(위 필드 참고). 응답 감사, 서툴게(logging redaction), 품질 메트릭, 런타임 이벤트 채널을 구독하지 않고 호출별 지출을 기록하는 사이드카 비용 원장에 사용하세요. 실패한 모델 호출은 대신 on_error를 발화해요.

Before/After-Compaction: 구조화 컴팩션 제어

before_compaction은 컴팩션 직전에 발화해요. pre_compact와 달리 구조화된 토큰 압박 데이터를 담아요: input_tokens, output_tokens, context_limit, compaction_reason(threshold, overflow, manual). 훅은:

  • decision: block을 반환해 컴팩션을 거부(런타임이 컴팩션을 완전히 건너뜀), 또는
  • hook_specific_output.summary를 반환해 LLM 생성 요약을 교체(런타임이 그 요약을 그대로 적용하고 모델 호출 건너뜀).

after_compaction은 성공적인 컴팩션 후에 발화해요. 생성된 요약과 컴팩션 전 input_tokens / output_tokens을 담아 관측 핸들러가 "X에서 Y로 컴팩션됨"을 자연스럽게 표현할 수 있어요. after_compaction은 순수 관찰적이에요. 출력은 무시돼요.

에이전트 전환과 세션 재개: 멀티 에이전트와 장수 실행 관측

on_agent_switch는 런타임이 활성 에이전트를 새 것으로 옮길 때마다 발화해요 — transfer_task, handoff, force_handoff, 또는 전송된 작업 완료 후의 반환. 원인은 agent_switch_kind에, 소스·대상은 from_agent·to_agent에 있어요. 어느 에이전트가 어떤 도구를 실행했는지 추적하는 감사·트랜스크립트·메트릭 파이프라인에 사용하세요.

내장 unload는 이 이벤트에 훅되어 이전 에이전트 모델이 보유한 리소스를 해제해요. 한 번에 하나만 수용할 수 있는 GPU에서 두 개의 무거운 로컬 모델을 실행하는 표준 방법이에요:

agents:
  coder:
    model: qwen3-large
    handoffs: [ reviewer ]
    hooks:
      on_agent_switch:
      - type: builtin
        command: unload
  reviewer:
    model: qwen3-coder
    handoffs: [ coder ]
    hooks:
      on_agent_switch:
      - type: builtin
        command: unload

models:
  qwen3-large:
    provider: dmr
    model: ai/qwen3-large
  qwen3-coder:
    provider: dmr
    model: ai/qwen3-coder

모든 전송에서 런타임은 on_agent_switch 훅 입력에 이전 에이전트의 모델 엔드포인트 스냅샷을 실어 보내고, unload 내장은 평문 HTTP로 각 DMR 엔드포인트의 /_unload URL에 {"model": "<id>"}를 POST해요. 클라우드 provider(OpenAI, Anthropic, …)에서는 HTTP unload 엔드포인트를 노출하지 않으므로 훅은 조용한 no-op이에요. 교차 provider 체인은 안전해요 — DMR 엔드포인트만 건드려져요. 전체 파일은 examples/unload_on_switch.yaml 참조.

on_session_resume은 사용자가 구성된 max_iterations 한도를 넘어 계속 진행하도록 명시적으로 승인할 때 발화해요. previous_max_iterations는 도달한 상한을, new_max_iterations는 승인 후 새 상한을 담아요. 확장 런타임 세션에 대한 알림 또는 재개를 측정하는 청구/쿼터 파이프라인에 유용해요.

도구 승인 결정: 무엇을 누가 승인했는지 감사 추적 (Tool-Approval-Decision)

on_tool_approval_decision은 런타임의 도구 승인 체인(permissions / yolo / readonly / pre_tool_use 훅 / 대화형 프롬프트)이 도구 호출에 대한 판정을 해석한 후에 발화해요. approval_decision는 allow, deny, canceled이고 approval_source는 어느 단계가 판정을 냈는지의 안정적 분류자예요. 관찰 전용이에요 — 체인을 다시 구현하지 않고 감사 파이프라인에 단일 구조화된 "누가 무엇을 승인했는지" 기록을 줘요.

Worktree-Create: 격리된 체크아웃 준비

worktree_create는 docker agent run --worktree[=name]가 새 git worktree를 만든 직후, 세션 시작 전에 한 번 발화해요. 각 훅은 새 worktree 안에서 실행돼요 — 그 작업 디렉토리(및 입력의 cwd)는 새 체크아웃이므로 설정 명령이 원본이 아닌 새 트리에서 동작해요. worktree 경로와 브랜치는 worktree_path·worktree_branch에, 분기된 저장소 루트는 worktree_source_dir에 있어요.

에이전트가 시작하기 전에 체크아웃을 준비하는 데 사용하세요: git이 안 가져갈 추적되지 않은 파일(.env, 로컬 구성) 복사, 의존성 설치, 캐시 워밍. worktree가 체크아웃 옆이 아니라 Docker Agent 데이터 디렉토리 아래 살므로, 상대 경로가 아닌 worktree_source_dir로 원본 파일을 해결하세요. 훅은 decision: block / {"continue": false} / exit code 2를 반환해 run을 중단할 수 있어요(예: 설정 단계 실패). 평문 stdout은 추가 컨텍스트로 표면화돼요.

hooks:
  worktree_create:
  # Copy untracked dotfiles git won't bring into the new worktree.
  - name: seed local env
    type: command
    command: |
      INPUT=$(cat)
      SRC=$(echo "$INPUT" | jq -r '.worktree_source_dir // ""')
      [ -n "$SRC" ] && [ -f "$SRC/.env" ] && [ ! -f .env ] && cp "$SRC/.env" .env
      echo "Prepared worktree"
  # Install dependencies, aborting the run on failure.
  - name: install dependencies
    type: command
    timeout: 600
    command: |
      if [ -f package.json ]; then
        npm install || { echo '{"continue": false, "system_message": "npm install failed"}'; exit 2; }
      fi

대부분의 이벤트와 달리 worktree_create는 run 루프가 아니라 CLI에서 디스패치돼요. worktree(그리고 런타임·세션·도구·스냅샷 메커니즘이 모두 캡처하는 작업 디렉토리)가 런타임·세션이 존재하기 전에 확정돼야 하기 때문이에요. 전체 파일은 examples/worktree_create_hook.yaml 참조.

Pre-Compact: 요약 steer

pre_compact는 런타임이 세션 트랜스크립트를 컴팩션하기 직전에 발화해요. source 필드가 컴팩션이 왜 트리거됐는지 말해줘요:

  • manual — 사용자가 /compact 호출
  • auto — 구성된 임계값에서 사전 컴팩션
  • overflow — 컨텍스트 오버플로 오류 후 응급 컴팩션
  • tool_overflow — 도구 결과가 추정 컨텍스트를 임계값 너머로 밀어 트리거된 사전 컴팩션

additional_context(또는 평문 stdout)를 반환해 에이전트 지시를 수정하지 않고 컴팩션 프롬프트에 지침을 추가해요. 이벤트를 차단(decision: block / exit code 2)하면 컴팩션을 취소해요 — 잘림을 직접 처리하고 싶을 때 유용.

User-Prompt-Submit: 모든 사용자 메시지 게이트 또는 풍부화

user_prompt_submit은 사용자 메시지당 한 번, 프롬프트가 세션에 기록된 후 첫 모델 호출 전에 발화해요. 제출된 텍스트는 prompt에 있어요. 다음에 사용하세요:

  • 정책을 위반하는 프롬프트 차단(decision: block / exit code 2),
  • 프롬프트별 컨텍스트 주입(additional_context가 그 턴의 임시 시스템 메시지로 접합),
  • 사용자 프롬프트를 로그로 감사.

하위 세션(전송된 작업, 백그라운드 에이전트, 스킬 하위 세션)에는 시작 메시지가 런타임에 합성되므로 발화하지 않아요.

User-Steering-Messages-Submit: 도중 steering 게이트 또는 풍부화

user_steering_messages_submit은 user_prompt_submit의 steering 큐 대응물이에요. 런타임이 steering 큐를 배출할 때마다 — 에이전트가 이미 작업 중일 때 사용자가 제출한 메시지: 턴 중(도구 호출 배치 후), 모델이 멈춘 후, 첫 모델 호출 전 유휴 동안 — 발화해요. 배출된 메시지는 steering_messages에 JSON 배열로 도착해요. 다음에 사용하세요:

  • steering이 정책을 위반하면 run 차단(decision: block / exit code 2),
  • steering에 응답해 컨텍스트 주입(additional_context가 steered 턴의 임시 시스템 메시지로 접합 — user_prompt_submit과 똑같이 절대 영속 안 됨),
  • steering 메시지를 로그로 감사.

reason: steered의 turn_end가 턴 중·중단 후 배출만 관찰하는 것과 달리, 이 이벤트는 모든 배출에서 발화해요 — 첫 모델 호출 전 유휴 동안 적용된 steering 포함.

hooks:
  user_steering_messages_submit:
  - type: command
    timeout: 5
    command: |
      INPUT=$(cat)
      COUNT=$(echo "$INPUT" | jq -r '.steering_messages | length')
      echo "$INPUT" | jq -r '.steering_messages[]' >> /tmp/agent-steering.log
      if [ "$COUNT" -gt 0 ]; then
        echo '{"hook_specific_output":{"additional_context":"The user sent new instructions while you were working — re-read the latest user messages and adjust course before continuing."}}'
      fi

User-Followup-Submit: 큐잉된 후속 게이트 또는 풍부화

user_followup_submit은 user_prompt_submit의 후속 큐 대응물이에요. 런타임이 턴 끝에 후속 메시지를 디큐하고 그를 위해 새 턴을 시작할 때마다 발화해요. 후속은 턴 끝 처리용으로 큐잉된 사용자 메시지(FollowUp API / 큐)예요 — 턴 중 steering과 구별돼요: 모델은 후속을 중단이 아닌 새 입력으로 보고, 각 후속은 온전히 나뉘지 않은 턴을 얻어요. 후속 텍스트는 prompt에 있어요. 다음에 사용하세요:

  • 정책을 위반하는 큐잉된 후속 차단(decision: block / exit code 2),
  • 후속별 컨텍스트 주입(additional_context가 후속 턴의 임시 시스템 메시지로 접합 — user_prompt_submit과 똑같이 절대 영속 안 됨),
  • 후속 메시지를 로그로 감사.

이것은 user_prompt_submit이 남긴 틈을 닫아요 — 첫 대화형 프롬프트에서만 발화하고 큐잉된 후속에는 절대 발화하지 않거든요.

hooks:
  user_followup_submit:
  - type: command
    timeout: 5
    command: |
      INPUT=$(cat)
      echo "$INPUT" | jq -r '.prompt' >> /tmp/agent-followups.log

Subagent-Stop: 핸드오프 완료 관찰

subagent_stop은 하위 에이전트가 끝날 때마다 발화해요 — transfer_task 반환, 백그라운드 에이전트 완료, 또는 스킬 하위 세션 종료. 부모 에이전트의 훅 실행자에 대해 실행되므로 오케스트레이터에 구성된 핸들러가 모든 자식 완료를 한 곳에서 봐요. 하위 에이전트 이름은 agent_name에, 부모 세션 ID는 parent_session_id에, 자식의 최종 어시스턴트 메시지는 stop_response에 있어요.

Permission-Request: 프로그래매틱 도구 승인

permission_request는 런타임이 사용자에게 도구 호출 승인을 요청하기 직전에 발화해요(즉 안전 모드나 권한 규칙이 결정을 단락시키지 않았을 때). pre_tool_use와 같은 hook_specific_output.permission_decision 모양을 사용해 호출을 자동 승인·자동 거부해요:

hooks:
  permission_request:
  - matcher: "shell"
    hooks:
    - type: command
      command: |
        INPUT=$(cat)
        CMD=$(echo "$INPUT" | jq -r '.tool_input.cmd // ""')
        if echo "$CMD" | grep -qE '^(ls|pwd|cat) '; then
          echo '{"hook_specific_output":{"permission_decision":"allow","permission_decision_reason":"safe read-only command"}}'
        fi

아무것도 반환하지 않으면 일반 대화형 확인으로 통과(fall through)해요.

훅이 통과할 때( permission_decision을 반환하지 않을 때)도 런타임이 사용자에게 보여주는 확인 프롬프트에 키/값 메타데이터를 붙일 수 있어요. 런타임은 그것을 toolset이 도구에 붙인 정적 메타데이터에 병합하고(충돌 시 훅 키가 이김) 도구 호출 확인 메시지로 방출하므로 클라이언트(TUI, HTTP)가 추가 호출별 컨텍스트를 렌더링할 수 있어요. 여러 일치 훅의 키는 병합되고, 구성 순서상 마지막 훅이 충돌에서 이겨요.

hooks:
  permission_request:
  - matcher: "shell"
    hooks:
    - type: command
      command: |
        INPUT=$(cat)
        CMD=$(echo "$INPUT" | jq -r '.tool_input.cmd // ""')
        if echo "$CMD" | grep -qE '\brm\b'; then
          echo '{"hook_specific_output":{"metadata":{"risk":"high","note":"deletes files"}}}'
        fi

LLM as a Judge (도구 호출 자동 승인)

model 훅 유형은 LLM에게 묻고 그 답을 훅의 네이티브 출력으로 번역해요 — Go 코드 없음, 셸 접착 없음, 당신 쪽 JSON 파싱 없음. 잘 알려진 pre_tool_use_decision 스키마와 결합하면 도구 호출당 allow / ask / deny를 결정하는 완전히 구성 가능한 LLM 판사가 돼요.

hooks:
  pre_tool_use:
  - matcher: "shell|edit_file|mcp:.*"
    hooks:
    - type: model
      model: openai/gpt-4o-mini
      timeout: 15
      schema: pre_tool_use_decision
      prompt: |
        You are a security judge for an autonomous agent.
        Decide whether this tool call is safe to auto-approve.

        Tool: {{ .ToolName }}
        Args: {{ .ToolInput | toJSON }}

        Project rules:
        - Reads under the working directory are safe.
        - Writes to ~/.ssh / ~/.aws / ~/.docker are deny.
필드 필수 설명
model 예 모델 스펙(provider/model, 예: openai/gpt-4o-mini). 판사 모델 — 작고/싼 것이 권장.
prompt 예 Go text/template 본문. 훅 Input을 데이터로 보고 toJSON과 truncate <n> 헬퍼도 봐요.
schema 아니오 잘 알려진 응답 해석. pre_tool_use_decision은 permission_decision 판정을 만들어요. 생략하면 additional_context로 주입되는 자유 형식 텍스트.
timeout 아니오(기본 60s) 호출당 시간 초과. 시간 초과는 다른 설정과 무관하게 pre_tool_use에서 fail-closed(deny)돼요. 판사 모델의 일반적인 대기 시간 + 약간의 버퍼에 맞추세요.

pre_tool_use_decision 스키마는 판사가 엄격한 {decision, reason} JSON으로 답하도록 제약해요. 구조화 출력을 준수하는 provider(OpenAI, ...)는 그 모양을 직접 방출하도록 요청받고, 무시하는 provider에서는 프레임워크가 여전히 관대한 JSON-in-text를 파싱해요. 파싱 불가능한 것은 훅 오류로 전파되고 실행자는 pre_tool_use에서 fail-closed(deny)돼요.

판사가 오도돼도 파괴적 호출(예: sudo, rm -rf)이 차단되도록 결정적 권한 규칙과 짝지으세요. 명백한 읽기 전용 호출은 완전히 LLM을 우회해요. 완전한 구성은 examples/llm_judge.yaml 참조.

보안 고려사항:

  • 민감 데이터: 도구 인자(파일 경로, 명령 인자, 다른 파라미터 포함)가 판사 LLM에 전송돼요. 시크릿을 다루는 도구에는 판사를 사용하지 말거나, 판사 모델이 셀프 호스팅인지 확인하세요.
  • 심층 방어: 판사가 유일한 보안 계층이 아니어야 해요. 예제 구성처럼 판사가 보기 전에 명백히 위험한 작업(예: sudo, rm -rf)을 차단하는 결정적 권한 규칙을 사용하세요.

CLI 플래그 (CLI Flags)

에이전트의 YAML 파일을 수정하지 않고 명령줄에서 훅을 추가할 수 있어요. 일회성 디버깅, 감사 로깅, 기존 에이전트에 훅을 겹쳐 씌우는 데 유용해요.

플래그 설명
--hook-pre-tool-use 모든 도구 호출 전에 명령 실행
--hook-post-tool-use 모든 도구 호출 후에 명령 실행
--hook-session-start 세션 시작 시 명령 실행
--hook-session-end 세션 종료 시 명령 실행
--hook-on-user-input 입력을 기다릴 때 명령 실행
--hook-stop 모델이 응답을 끝낼 때 명령 실행

모든 플래그는 반복 가능해요 — 여러 개를 전달해 여러 훅을 등록하세요.

# Add a session-start hook
$ docker agent run agent.yaml --hook-session-start "./scripts/setup-env.sh"

# Combine multiple hooks
$ docker agent run agent.yaml \
  --hook-pre-tool-use "./scripts/validate.sh" \
  --hook-post-tool-use "./scripts/log.sh"

# Add hooks to an agent from a registry
$ docker agent run myorg/coder \
  --hook-pre-tool-use "./audit.sh"

참고 — 병합 동작: 에이전트 구성, 전역, 드롭인, CLI 훅은 가산적이에요. 각 이벤트에 대해 훅은 이 순서로 실행돼요: 에이전트 구성 훅 먼저, 그다음 settings.hooks의 전역 훅, 그다음 hooks.d/의 훅 드롭인, 그다음 CLI 훅. 어떤 소스도 다른 것을 대체하지 않고, 개별 에이전트가 전역 훅을 옵트아웃할 수 없어요.

더 알아보기 (Learn more)