훅(Hooks)

훅(Hooks)

이 항목에서는 훅을 사용해 에이전트 수명 주기의 주요 지점에서 사용자 정의 코드를 실행하는 방법을 설명해요. 훅을 사용하면 도구 호출을 가로채고, 에이전트 동작을 감사하고, 정책을 강제하고, 컨텍스트를 주입하고, 실행 흐름을 제어할 수 있어요.

출처: Hooks

본문

훅 작동 방식

훅은 4단계 과정을 따르며: 이벤트가 발생하면 SDK가 매처(matcher)가 적용되는지 확인하고, 콜백을 호출하며, 콜백이 다음에 무엇이 일어날지 제어하는 결정을 반환해요.

  • 에이전트가 이벤트를 트리거(예: 도구를 호출하려 함).
  • SDK가 해당 이벤트 유형에 등록된 각 훅 매처를 확인.
  • 매처가 일치하면(또는 매처 패턴이 없어 모든 것을 매치하면) SDK가 연결된 콜백 함수를 호출.
  • 각 콜백은 작업을 허용, 차단 또는 수정할 수 있는 출력 객체를 반환.

훅 이벤트

사용 가능한 이벤트는 다음과 같아요.

이벤트 발생 시점
PreToolUse 도구 실행 전. 도구를 차단하거나 입력을 수정할 수 있음.
PostToolUse 도구가 성공적으로 실행된 후. 추가 컨텍스트를 주입할 수 있음.
PostToolUseFailure 도구 실행이 실패한 후.
UserPromptSubmit 사용자가 프롬프트를 보낼 때. 추가 컨텍스트를 주입할 수 있음.
Stop 에이전트가 중지하려 할 때. 컨텍스트를 주입하거나 세션을 중단할 수 있음.
SubagentStart 하위 에이전트가 시작될 때.
SubagentStop 하위 에이전트가 중지하려 할 때.
PreCompact 대화가 압축되기 전(컨텍스트 창에 맞게 요약).
Notification 에이전트가 알림을 발생시킬 때.
PermissionRequest 도구 권한 확인이 발생할 때.

훅 구성

세션을 만들 때 hooks 옵션으로 훅을 전달해요. 각 훅 이벤트는 매처 목록에 매핑되고, 각 매처는 콜백 함수 목록을 포함해요.

import { createCortexCodeSession } from "cortex-code-agent-sdk";

const session = await createCortexCodeSession({
  cwd: process.cwd(),
  hooks: {
    PreToolUse: [
      {
        matcher: "Bash",
        hooks: [
          async (input, toolUseId, context) => {
            console.log("Bash command:", input.tool_input);
            return {};
          },
        ],
      },
    ],
  },
});
from cortex_code_agent_sdk import CortexCodeSDKClient, CortexCodeAgentOptions, HookMatcher

async def log_bash(input, tool_use_id, context):
    print("Bash command:", input["tool_input"])
    return {}

async with CortexCodeSDKClient(CortexCodeAgentOptions(
    hooks={
        "PreToolUse": [
            HookMatcher(matcher="Bash", hooks=[log_bash]),
        ],
    },
)) as client:
    await client.query("List files in the current directory")
    async for msg in client.receive_response():
        pass

매처

각 훅 매처에는 세 개의 필드가 있어요.

필드 유형 설명
matcher string(선택) 매칭을 지원하는 이벤트가 사용하는 정규식 패턴. PreToolUse, PostToolUse, PermissionRequest는 도구 이름으로 매치하고, Notification은 알림 유형으로, PreCompact는 트리거 값("auto" 또는 "manual")으로 매치해요. 생략하면 해당 이벤트의 모든 값에 대해 훅이 발생해요.
hooks 콜백 목록 매처가 일치할 때 실행할 하나 이상의 콜백 함수.
timeout number(선택) 이 매처의 모든 콜백에 대한 최대 시간(초). 기본값 60.

matcher 필드는 유효한 정규식 패턴을 허용해요. 예를 들어:

  • "Bash" – Bash 도구만 매치
  • "Write|Edit" – Write 또는 Edit 매치
  • ".*" – 모든 도구 매치(매처 생략과 동일)

콜백 입력

모든 콜백은 세 개의 인자를 받아요.

인자 설명
input 이벤트별 데이터를 담은 객체. 모든 이벤트는 session_id, transcript_path, cwd, permission_mode, hook_event_name을 포함해요. 도구 이벤트는 tool_name, tool_input, tool_use_id도 포함해요.
toolUseId / tool_use_id 도구 사용 ID(도구 관련 이벤트의 경우) 또는 null.
context 컨텍스트 객체. 향후 사용을 위해 예약(예: abort 신호).

입력 필드는 이벤트에 따라 달라져요.

이벤트 추가 입력 필드
PreToolUse tool_name, tool_input, tool_use_id
PostToolUse tool_name, tool_input, tool_response, tool_use_id
PostToolUseFailure tool_name, tool_input, tool_use_id, error, 선택 is_interrupt
UserPromptSubmit prompt
Stop stop_hook_active
SubagentStart agent_id, agent_type
SubagentStop stop_hook_active, agent_id, agent_transcript_path, agent_type
PreCompact trigger("manual" 또는 "auto"), custom_instructions
Notification message, notification_type, 선택 title
PermissionRequest tool_name, tool_input, 선택 permission_suggestions

도구 수명 주기 훅과 PermissionRequest는 하위 에이전트에서 발생할 때 선택적 agent_id 및 agent_type 필드를 포함할 수도 있어요.

콜백 출력

콜백은 실행을 제어하는 출력 객체를 반환해요. 다음 필드를 사용할 수 있어요.

필드 유형 설명
continue / continue_ boolean 처리를 계속할지 여부. 현재 훅 사이트나 턴의 추가 처리를 중지하려면 false로 설정. 기본값: true.
stopReason string continue가 false일 때 표시되는 메시지.
decision "block" 현재 작업을 차단하려면 "block"으로 설정.
reason string 에이전트에게 전달하는 결정에 대한 피드백 메시지.
systemMessage string 사용자에게 표시되는 경고 메시지.
hookSpecificOutput object 이벤트별 제어(아래 참조).

참고: Python SDK는 Python 키워드 충돌을 피하기 위해 continue 대신 continue_(밑줄이 붙음)를 사용해요. SDK는 CLI와 통신할 때 이를 자동으로 continue로 변환해요.

훅별 출력

hookSpecificOutput 필드는 이벤트별 제어를 받아들여요.

PreToolUse

필드 설명
permissionDecision "allow", "deny" 또는 "ask". 도구가 실행될 수 있는지 제어.
permissionDecisionReason 권한 결정의 이유.
updatedInput 원본 대신 사용할 수정된 도구 입력.

PostToolUse

필드 설명
additionalContext 도구 실행 후 대화에 주입되는 추가 컨텍스트.

UserPromptSubmit

필드 설명
additionalContext 대화에 주입되는 추가 컨텍스트.

PermissionRequest

필드 설명
decision.behavior "allow" 또는 "deny". 권한 결과 제어.
decision.message 권한 요청을 거부할 때 표시되는 메시지.

예시

위험한 도구 차단

에이전트가 특정 bash 명령을 실행하지 못하게 막아요.

import { createCortexCodeSession } from "cortex-code-agent-sdk";

const session = await createCortexCodeSession({
  cwd: process.cwd(),
  hooks: {
    PreToolUse: [
      {
        matcher: "Bash",
        hooks: [
          async (input) => {
            const command = (input.tool_input as any)?.command ?? "";
            if (command.includes("rm -rf") || command.includes("DROP TABLE")) {
              return {
                decision: "block",
                reason: "Destructive commands are not allowed",
              };
            }
            return {};
          },
        ],
      },
    ],
  },
});
from cortex_code_agent_sdk import CortexCodeSDKClient, CortexCodeAgentOptions, HookMatcher

async def block_dangerous(input, tool_use_id, context):
    command = input.get("tool_input", {}).get("command", "")
    if "rm -rf" in command or "DROP TABLE" in command:
        return {
            "decision": "block",
            "reason": "Destructive commands are not allowed",
        }
    return {}

async with CortexCodeSDKClient(CortexCodeAgentOptions(
    hooks={
        "PreToolUse": [
            HookMatcher(matcher="Bash", hooks=[block_dangerous]),
        ],
    },
)) as client:
    ...

읽기 전용 권한 요청 자동 허용

데이터만 읽는 도구의 권한 요청을 허용해요.

hooks: {
  PermissionRequest: [
    {
      matcher: "Read%Glob%Grep",
      hooks: [
        async (input) => {
          return {
            hookSpecificOutput: {
              hookEventName: "PermissionRequest",
              decision: { behavior: "allow" },
            },
          };
        },
      ],
    },
  ],
}
async def auto_approve_reads(input, tool_use_id, context):
    return {
        "hookSpecificOutput": {
            "hookEventName": "PermissionRequest",
            "decision": {"behavior": "allow"},
        },
    }

hooks = {
    "PermissionRequest": [
        HookMatcher(matcher="Read%Glob%Grep", hooks=[auto_approve_reads]),
    ],
}

도구 입력 수정

실행 전에 모든 bash 명령에 타임아웃을 추가해요.

hooks: {
  PreToolUse: [
    {
      matcher: "Bash",
      hooks: [
        async (input) => {
          const originalCommand = (input.tool_input as any)?.command ?? "";
          return {
            hookSpecificOutput: {
              hookEventName: "PreToolUse",
              updatedInput: { command: `timeout 30 ${originalCommand}` },
            },
          };
        },
      ],
    },
  ],
}
async def add_timeout(input, tool_use_id, context):
    original = input.get("tool_input", {}).get("command", "")
    return {
        "hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "updatedInput": {"command": f"timeout 30 {original}"},
        },
    }

hooks = {
    "PreToolUse": [
        HookMatcher(matcher="Bash", hooks=[add_timeout]),
    ],
}

감사 로깅

실행에 영향을 주지 않고 감사 목적으로 모든 도구 호출을 기록해요.

import { createCortexCodeSession } from "cortex-code-agent-sdk";
import { appendFileSync } from "node:fs";

const session = await createCortexCodeSession({
  cwd: process.cwd(),
  hooks: {
    PostToolUse: [
      {
        hooks: [
          async (input) => {
            const entry = {
              timestamp: new Date().toISOString(),
              tool: input.tool_name,
              input: input.tool_input,
              sessionId: input.session_id,
            };
            appendFileSync("audit.log", JSON.stringify(entry) + "\n");
            return {};
          },
        ],
      },
    ],
  },
});
import json
from datetime import datetime, timezone
from cortex_code_agent_sdk import CortexCodeSDKClient, CortexCodeAgentOptions, HookMatcher

async def audit_log(input, tool_use_id, context):
    entry = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "tool": input.get("tool_name"),
        "input": input.get("tool_input"),
        "session_id": input.get("session_id"),
    }
    with open("audit.log", "a") as f:
        f.write(json.dumps(entry) + "\n")
    return {}

async with CortexCodeSDKClient(CortexCodeAgentOptions(
    hooks={
        "PostToolUse": [
            HookMatcher(hooks=[audit_log]),
        ],
    },
)) as client:
    ...

훅과 canUseTool 비교

훅과 canUseTool 콜백 모두 도구 호출을 가로챌 수 있지만 목적이 달라요.

기능 canUseTool 훅
범위 실행 전 권한 확인만 여러 수명 주기 이벤트(도구 수명 주기, 프롬프트 제출, 중지, 하위 에이전트 수명 주기, 알림, 압축)
이벤트 하나: 권한 요청 열 가지: PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, UserPromptSubmit, Stop, SubagentStart, SubagentStop, Notification, PreCompact
패턴 매칭 없음 있음(도구 이름, 알림 유형, 압축 트리거 기준)
도구 입력 수정 가능(updatedInput) 가능(hookSpecificOutput.updatedInput)
컨텍스트 주입 불가 가능 (PostToolUse, UserPromptSubmit의 additionalContext)
주요 목적 권한 제어 관찰, 정책, 컨텍스트 주입, 흐름 제어

더 알아보기