훅(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) |
| 주요 목적 | 권한 제어 | 관찰, 정책, 컨텍스트 주입, 흐름 제어 |