훅(Hooks)

훅(Hooks)

훅을 사용하면 에이전트가 원격 샌드박스 안에서 코드를 실행하거나 파일을 수정하기 직전 또는 직후에 커스텀 스크립트나 외부 HTTP 요청을 실행할 수 있어요. 훅으로 에이전트 루프를 자동화된 가드레일과 백그라운드 워크플로로 확장할 수 있어요. 예:

  • 안전 및 접근 가드레일 강제: 고위험 쉘 명령이나 제한된 파일 읽기가 실행되기 전에
  • 데이터 파이프라인 변환 자동화: 에이전트가 파일을 만들거나 수정한 직후에
  • 도구 실행 후 외부 모니터링 시스템으로 기업 감사 텔레메트리 스트리밍
import json
from google import genai

client = genai.Client()

hooks_config = {
    "security-gate": {
        "pre_tool_execution": [
            {
                "matcher": "code_execution",
                "hooks": [
                    {
                        "type": "command",
                        "command": "python3 /.agents/hooks-scripts/gate.py",
                        "timeout": 10,
                    }
                ],
            }
        ]
    }
}

gate_script = """#!/usr/bin/env python3
import sys, json
data = json.load(sys.stdin)
cmd = str(data.get("tool_call", {}).get("args", {}))
if "rm -rf" in cmd:
    print(json.dumps({"decision": "deny", "reason": "Destructive command blocked by security gate."}))
else:
    print(json.dumps({"decision": "allow"}))
"""

interaction = client.interactions.create(
    agent="antigravity-preview-09-2026",
    input="Run `rm -rf /tmp/forbidden` using code_execution.",
    tools=[{"type": "code_execution"}],
    environment={
        "type": "remote",
        "sources": [
            {
                "type": "inline",
                "target": ".agents/hooks.json",
                "content": json.dumps(hooks_config, indent=2),
            },
            {
                "type": "inline",
                "target": ".agents/hooks-scripts/gate.py",
                "content": gate_script,
            },
        ],
    },
)
print(interaction.output_text)
import { GoogleGenAI } from "@google/genai";

const client = new GoogleGenAI({});

const hooksConfig = {
    "security-gate": {
        pre_tool_execution: [
            {
                matcher: "code_execution",
                hooks: [
                    {
                        type: "command",
                        command: "python3 /.agents/hooks-scripts/gate.py",
                        timeout: 10,
                    },
                ],
            },
        ],
    },
};

const gateScript = `#!/usr/bin/env python3
import sys, json
data = json.load(sys.stdin)
cmd = str(data.get("tool_call", {}).get("args", {}))
if "rm -rf" in cmd:
    print(json.dumps({"decision": "deny", "reason": "Destructive command blocked by security gate."}))
else:
    print(json.dumps({"decision": "allow"}))
`;

const interaction = await client.interactions.create({
    agent: "antigravity-preview-09-2026",
    input: "Run `rm -rf /tmp/forbidden` using code_execution.",
    tools: [{ type: "code_execution" }],
    environment: {
        type: "remote",
        sources: [
            {
                type: "inline",
                target: ".agents/hooks.json",
                content: JSON.stringify(hooksConfig, null, 2),
            },
            {
                type: "inline",
                target: ".agents/hooks-scripts/gate.py",
                content: gateScript,
            },
        ],
    },
});
console.log(interaction.output_text);
curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
  -H "Content-Type: application/json" \
  -H "x-goog-api-key: *** \
  -d '{
      "agent": "antigravity-preview-09-2026",
      "input": [{"type": "text", "text": "Run `rm -rf /tmp/forbidden` using code_execution."}],
      "tools": [{"type": "code_execution"}],
      "environment": {
          "type": "remote",
          "sources": [
              {
                  "type": "inline",
                  "target": ".agents/hooks.json",
                  "content": "{\"security-gate\": {\"pre_tool_execution\": [{\"matcher\": \"code_execution\", \"hooks\": [{\"type\": \"command\", \"command\": \"python3 /.agents/hooks-scripts/gate.py\", \"timeout\": 10}]}]}}"
              },
              {
                  "type": "inline",
                  "target": ".agents/hooks-scripts/gate.py",
                  "content": "#!/usr/bin/env python3\nimport sys, json\ndata = json.load(sys.stdin)\ncmd = str(data.get(\"tool_call\", {}).get(\"args\", {}))\nif \"rm -rf\" in cmd:\n    print(json.dumps({\"decision\": \"deny\", \"reason\": \"Destructive command blocked by security gate.\"}))\nelse:\n    print(json.dumps({\"decision\": \"allow\"}))\n"
              }
          ]
      }
  }'

출처: 원문

본문

지원 라이프사이클 이벤트

훅은 샌드박스 내부에서 2가지 이벤트를 지원해요.

이벤트 발생 시점 기능
pre_tool_execution 도구가 실행되기 직전 도구가 실행되기 전에 승인(allow)하거나 차단(deny)할 수 있어요. 차단되면 모델이 거부 사유를 보고 적응해요.
post_tool_execution 도구가 끝난 직후 코드 서식, 단위 테스트 실행, 텔레메트리 로깅 같은 후속 작업을 실행해요. 완료된 작업을 차단하거나 되돌릴 수 없어요.

pre_tool_execution

도구가 실행되기 직전에 발생해요. 스크립트는 stdin에서 도구 호출 세부사항을 읽고 결정 JSON(allow 또는 deny)을 stdout으로 출력해요.

입력 페이로드(stdin):

{
  "tool_call": {
    "name": "code_execution",
    "args": {
      "code": "rm -rf /tmp/forbidden",
      "language": "bash"
    }
  },
  "environment_id": "env_xyz789"
}

출력 응답(stdout):

도구 호출을 승인하려면:

{
  "decision": "allow"
}

도구 호출을 차단하고 모델에 피드백을 반환하려면:

{
  "decision": "deny",
  "reason": "Destructive command blocked by security gate."
}

훅이 명령을 거부하면 도구 호출이 즉시 건너뛰어져요. 에이전트는 현재 턴 안에서 거부 사유를 담은 오류 결과를 봐요. 모델은 대체 명령을 선택하거나 사용자에게 차단을 설명하는 방식으로 자기 수정할 수 있어요.

스크립트가 인식할 수 없는 JSON, 일반 텍스트, 또는 {"decision": "deny"} 외의 것을 출력하면 런타임은 그 응답을 승인(allow)으로 취급해요.

post_tool_execution

도구가 완료된 직후에 발생해요. 스크립트는 실행 세부사항과 오류 상태를 stdin에서 읽어요.

입력 페이로드(stdin):

{
  "tool_call": {
    "name": "code_execution",
    "args": {
      "code": "python3 /workspace/app.py",
      "language": "bash"
    }
  },
  "environment_id": "env_xyz789"
}

쉘 명령이 표준 오류(stderr)로 오류를 출력하거나 파일 시스템 작업이 실패하면 페이로드에 오류 텍스트를 포함하는 "error" 필드가 포함돼요. 명령이 오류 없이 성공하면 "error" 필드는 완전히 생략돼요.

출력 응답(stdout):

{}

post-tool 훅은 코드 서식이나 로깅 같은 백그라운드 작업을 위해 엄격히 실행되므로, 런타임은 stdout에서 반환되는 결정 값을 무시해요.

구성 탐색

런타임은 샌드박스 환경 내부의 .agents/hooks.json 또는 /.agents/hooks.json에서 훅 정의를 자동으로 발견해요. 지원되는 환경 소스를 사용해 커스텀 스크립트와 함께 hooks.json을 제공할 수 있어요.

  • 저장소 마운트: AGENTS.md와 함께 .agents/hooks.json을 포함하는 Git 저장소
  • Cloud Storage(gcs): 환경에 복사된 hooks.json을 포함하는 GCS 버킷
  • 인라인 소스: client.interactions.create 호출 시 environment.sources에 전달된 원시 JSON 문자열과 스크립트 콘텐츠

hooks.json 스키마

hooks.json 파일은 이벤트 정의(pre_tool_execution 또는 post_tool_execution)를 커스텀 이름 아래에 그룹화해요. 각 그룹을 독립적으로 활성화하거나 비활성화할 수 있어요.

{
  "security-gate": {
    "enabled": true,
    "pre_tool_execution": [
      {
        "matcher": "code_execution",
        "hooks": [
          {
            "type": "command",
            "command": "python3 /.agents/hooks-scripts/gate.py",
            "timeout": 10
          }
        ]
      }
    ]
  },
  "auto-format": {
    "post_tool_execution": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "python3 /.agents/hooks-scripts/auto_lint.py",
            "timeout": 15
          }
        ]
      }
    ]
  }
}

매처 구문과 규칙

hooks.json의 각 규칙 그룹은 matcher와 hooks 속성으로 핸들러가 언제, 어떻게 실행되는지 정의해요.

필드 유형 설명
enabled boolean 선택 사항. false로 설정하면 그룹을 비활성화(기본 true).
matcher string 컨테이너 내부의 대상 도구 이름과 일치하는 정규식 패턴
hooks array 핸들러 정의의 순서 있는 목록(command 또는 http). 핸들러는 선언 순서대로 순차 실행.
정규식 평가 방식

에이전트가 샌드박스 내부에서 도구를 호출하면 런타임은 표준 RE2 정규식을 사용해 도구의 컨테이너 이름을 matcher 패턴과 대조해요. 정규식이 도구 이름과 일치하면 hooks 배열의 모든 핸들러가 순서대로 실행돼요. 여러 규칙 그룹이 같은 도구와 일치하면 해당 핸들러 배열이 모두 실행돼요.

내장 컨테이너 도구 이름을 대상으로 할 수 있어요: 코드 실행(code_execution) 또는 파일 시스템 작업(view_file, write_to_file, replace_file_content, list_dir, delete_file).

일반적인 매처 표현식
  • "code_execution": 쉘 명령과 스크립트 실행에 대한 정확한 문자열 일치
  • "write_to_file": 파일 시스템 파일 생성 및 디스크 쓰기에 대한 정확한 일치
  • "view_file|write_to_file": 파이프 구분으로 단일 규칙에서 여러 특정 도구 이름 일치
  • ".*_file": _file로 끝나는 모든 도구(예: view_file, write_to_file, delete_file)와 일치하는 정규식 와일드카드. 이는 파일 시스템 도구 세트의 일부만 포함하며, replace_file_content와 list_dir는 _file로 끝나지 않으므로 필요할 때 명시적으로 이름을 지정하세요. 표준 RE2 정규식은 .*을 요구해요. *_file 같은 단순 쉘 글로브는 유효하지 않은 정규식 구문이며 일치에 실패해요.
  • ".*" 또는 "*" 또는 "": 컨테이너 내부의 모든 도구 호출을 가로채는 캐치올 패턴

핸들러 유형

명령 훅

명령 훅은 샌드박스 내부에서 쉘 명령이나 스크립트를 실행해요. 스크립트는 stdin에서 이벤트 JSON을 받고 stdout에서 결정 JSON을 출력해요.

필드 유형 설명
type string 반드시 "command"여야 해요.
command string 샌드박스 내부에서 실행할 명령줄(예: python3 /.agents/hooks-scripts/gate.py)
timeout integer 타임아웃(초). 기본값: 30

HTTP 훅

HTTP 훅은 샌드박스 네트워크 내부에서 직접 외부 HTTPS URL로 이벤트 JSON을 POST 요청으로 보내요. 대상 서버는 정확히 동일한 JSON 형식({"decision": "allow"} 또는 {"decision": "deny", "reason": "..."})을 사용해 HTTP 응답 본문에서 결정을 반환해요.

필드 유형 설명
type string 반드시 "http"여야 해요.
url string 이벤트 페이로드를 POST할 외부 HTTPS 엔드포인트
headers object 비민감 커스텀 헤더에 대한 선택적 키-값 쌍(예: {"X-Event-Source": "agent-sandbox"}). 인증에는 네트워크 허용 목록의 자격증명을 대신 사용하세요.
timeout integer 타임아웃(초). 기본값: 30
이그레스 프록시와 토큰 변환

HTTP 훅은 샌드박스 네트워크 네임스페이스 내부에서 직접 실행되므로 나가는 요청은 투명한 이그레스 프록시를 통과해요. 이 아키텍처는 두 가지 중요한 보안 이점을 제공해요.

  • 네트워크 허용 목록: 대상 엔드포인트는 환경의 network.allowlist에서 명시적으로 허용되어야 해요. 루프백 트래픽(127.0.0.1 또는 localhost)은 프록시에 의해 차단되므로 항상 허용된 외부 엔드포인트를 대상으로 하세요.
  • 자격증명 주입: .agents/hooks.json 내부에 API 키나 비밀 bearer 토큰을 저장하거나 컨테이너에 마운트할 필요가 없어요. 비밀을 한 번 자격증명으로 저장하고 환경의 network.allowlist에 ID로 참조하세요. 이그레스 프록시가 나가는 HTTP 훅 트래픽을 자동으로 가로채고 샌드박스를 떠나기 전에 실제 인증 헤더를 와이어에 주입해요. 인라인 transform 규칙도 같은 방식으로 와이어에 헤더를 설정하지만, 프로젝트 전체에서 비밀을 재사용하고 한 곳에서 회전시키려면 자격증명을 사용하는 것이 좋아요. 자세한 내용은 네트워크 구성을 참조하세요.

런타임이 결정과 실패를 처리하는 방법

  • 동기 대기: 에이전트는 계속하기 전에 훅이 끝나기를 일시 중지하고 기다려요.
  • 도구 실행 차단: pre-tool 훅이 {"decision": "deny", "reason": "<your reason>"}을 반환하면 런타임이 도구 호출을 즉시 취소해요. 모델은 대화 히스토리에서 거부 사유를 보고 안전한 대안을 선택하거나 사용자에게 차단을 설명하며 적응해요.
  • 스크립트 충돌, HTTP 오류, 타임아웃 처리: 명령 스크립트가 충돌하면(0이 아닌 종료 상태), HTTP 훅이 2xx가 아닌 상태 코드를 반환하거나(예: 4xx 또는 5xx 서버 오류), 작업이 타임아웃되거나 인식할 수 없는 JSON을 반환하면 런타임은 그것을 승인(allow)으로 취급해요. 중단된 스크립트나 연결할 수 없는 텔레메트리 서버가 애플리케이션을 교착상태에 빠뜨리지 않도록 도구 실행이 정상적으로 계속돼요.

일반적인 사용 사례

데이터 개인정보 및 규정 준수를 위한 다중 턴 복구

훅이 PII(개인 식별 정보)를 포함하는 디렉토리나 기밀 금융 기록 같은 제한된 리소스에 대한 접근을 차단하면, 다음 호출에 previous_interaction_id를 전달해 같은 환경에서 턴을 계속할 수 있어요. 에이전트는 거부 설명을 읽고 승인된 공개 테이블을 대신 쿼리하며 자동으로 복구해요.

import json
from google import genai

client = genai.Client()

hooks_config = {
    "privacy-gate": {
        "pre_tool_execution": [
            {
                "matcher": "view_file",
                "hooks": [
                    {
                        "type": "command",
                        "command": "python3 /.agents/hooks-scripts/check_privacy.py",
                        "timeout": 5,
                    }
                ],
            }
        ]
    }
}

check_privacy_script = """#!/usr/bin/env python3
import sys, json
data = json.load(sys.stdin)
path = str(data.get("tool_call", {}).get("args", {}).get("path", ""))

if "/private/" in path:
    resp = {
        "decision": "deny",
        "reason": "Access to confidential `/private/` records is blocked by PII compliance policy. Query approved `/public/` summary tables instead."
    }
else:
    resp = {"decision": "allow"}

print(json.dumps(resp))
"""

# Step 1: Agent attempts to read confidential PII records and is intercepted
int_1 = client.interactions.create(
    agent="antigravity-preview-09-2026",
    input="Use your filesystem tool to read `/workspace/private/employees.json` and summarize the employee details.",
    environment={
        "type": "remote",
        "sources": [
            {
                "type": "inline",
                "target": ".agents/hooks.json",
                "content": json.dumps(hooks_config, indent=2),
            },
            {
                "type": "inline",
                "target": ".agents/hooks-scripts/check_privacy.py",
                "content": check_privacy_script,
            },
            {
                "type": "inline",
                "target": "workspace/private/employees.json",
                "content": '{"employees": [{"id": 1, "salary": 150000, "ssn": "000-00-0000"}]}',
            },
            {
                "type": "inline",
                "target": "workspace/public/summary.json",
                "content": '{"department": "Engineering", "team_size": 42, "status": "active"}',
            },
        ],
    },
)
print(int_1.output_text)

# Step 2: Continue in the same environment using previous_interaction_id; agent recovers with public tables
int_2 = client.interactions.create(
    agent="antigravity-preview-09-2026",
    input="Understood. Please read the approved `/workspace/public/summary.json` file instead and provide the summary.",
    environment=int_1.environment_id,
    previous_interaction_id=int_1.id,
)
print(int_2.output_text)
import { GoogleGenAI } from "@google/genai";

const client = new GoogleGenAI({});

const hooksConfig = {
    "privacy-gate": {
        pre_tool_execution: [
            {
                matcher: "view_file",
                hooks: [
                    {
                        type: "command",
                        command: "python3 /.agents/hooks-scripts/check_privacy.py",
                        timeout: 5,
                    },
                ],
            },
        ],
    },
};

const checkPrivacyScript = `#!/usr/bin/env python3
import sys, json
data = json.load(sys.stdin)
path = str(data.get("tool_call", {}).get("args", {}).get("path", ""))

if "/private/" in path:
    resp = {
        "decision": "deny",
        "reason": "Access to confidential \`/private/\` records is blocked by PII compliance policy. Query approved \`/public/\` summary tables instead."
    }
else:
    resp = {"decision": "allow"}

print(json.dumps(resp))
`;

const int1 = await client.interactions.create({
    agent: "antigravity-preview-09-2026",
    input: "Use your filesystem tool to read `/workspace/private/employees.json` and summarize the employee details.",
    environment: {
        type: "remote",
        sources: [
            {
                type: "inline",
                target: ".agents/hooks.json",
                content: JSON.stringify(hooksConfig, null, 2),
            },
            {
                type: "inline",
                target: ".agents/hooks-scripts/check_privacy.py",
                content: checkPrivacyScript,
            },
            {
                type: "inline",
                target: "workspace/private/employees.json",
                content: '{"employees": [{"id": 1, "salary": 150000, "ssn": "000-00-0000"}]}',
            },
            {
                type: "inline",
                target: "workspace/public/summary.json",
                content: '{"department": "Engineering", "team_size": 42, "status": "active"}',
            },
        ],
    },
});
console.log(int1.output_text);

const int2 = await client.interactions.create({
    agent: "antigravity-preview-09-2026",
    input: "Understood. Please read the approved `/workspace/public/summary.json` file instead and provide the summary.",
    environment: int1.environment_id,
    previous_interaction_id: int1.id,
});
console.log(int2.output_text);
# Step 1: Attempt to access restricted PII directory (blocked by hook)
curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
  -H "Content-Type: application/json" \
  -H "x-goog-api-key: *** \
  -d '{
      "agent": "antigravity-preview-09-2026",
      "input": [{"type": "text", "text": "Use your filesystem tool to read /workspace/private/employees.json and summarize the employee details."}],
      "environment": {
          "type": "remote",
          "sources": [
              {
                  "type": "inline",
                  "target": ".agents/hooks.json",
                  "content": "{\"privacy-gate\": {\"pre_tool_execution\": [{\"matcher\": \"view_file\", \"hooks\": [{\"type\": \"command\", \"command\": \"python3 /.agents/hooks-scripts/check_privacy.py\", \"timeout\": 5}]}]}}"
              },
              {
                  "type": "inline",
                  "target": ".agents/hooks-scripts/check_privacy.py",
                  "content": "#!/usr/bin/env python3\nimport sys, json\ndata = json.load(sys.stdin)\npath = str(data.get(\"tool_call\", {}).get(\"args\", {}).get(\"path\", \"\"))\nif \"/private/\" in path:\n    resp = {\"decision\": \"deny\", \"reason\": \"Access to confidential `/private/` records is blocked by PII compliance policy. Query approved `/public/` summary tables instead.\"}\nelse:\n    resp = {\"decision\": \"allow\"}\nprint(json.dumps(resp))\n"
              },
              {
                  "type": "inline",
                  "target": "workspace/private/employees.json",
                  "content": "{\"employees\": [{\"id\": 1, \"salary\": 150000, \"ssn\": \"000-00-0000\"}]}"
              },
              {
                  "type": "inline",
                  "target": "workspace/public/summary.json",
                  "content": "{\"department\": \"Engineering\", \"team_size\": 42, \"status\": \"active\"}"
              }
          ]
      }
  }'

# Step 2: Continue in the same environment using $ENV_ID and $INTERACTION_ID from the previous response
# curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
#   -H "Content-Type: application/json" \
#   -H "x-goog-api-key: *** \
#   -d '{
#       "agent": "antigravity-preview-09-2026",
#       "input": [{"type": "text", "text": "Understood. Please read the approved /workspace/public/summary.json file instead and provide the summary."}],
#       "environment": "'"$ENV_ID"'",
#       "previous_interaction_id": "'"$INTERACTION_ID"'"
#   }'

외부 감사 로깅 및 텔레메트리

파일을 읽거나 수정할 때마다 샌드박스 내부에서 외부 모니터링 서버로 실시간 감사 이벤트를 보내요.

  • 여러 도구 일치: 매처는 표준 정규식을 사용하므로 파이프(view_file|write_to_file|replace_file_content)나 와일드카드(.*_file)로 단일 규칙에서 여러 도구를 결합할 수 있어요.
  • 구성에서 비밀 제외: 인증 토큰을 자격증명으로 저장하고 환경의 네트워크 구성(network.allowlist.credential)에서 ID로 참조하세요. 이그레스 프록시가 나가는 요청에 실제 bearer 토큰을 주입해요. 이 예시는 대신 transform으로 헤더를 인라인으로 설정하는데, 같은 프록시로 보호되고 토큰이 이 하나의 구성에 속할 때 적합해요.
import json
from google import genai

client = genai.Client()

# Define hook without secrets; the egress proxy injects headers dynamically
hooks_config = {
    "audit-logging": {
        "post_tool_execution": [
            {
                "matcher": "view_file|write_to_file|replace_file_content",
                "hooks": [
                    {
                        "type": "http",
                        "url": "https://telemetry.example.com/api/v1/agent-events",
                        "timeout": 10,
                    }
                ],
            }
        ]
    }
}

interaction = client.interactions.create(
    agent="antigravity-preview-09-2026",
    input="Use your filesystem tool to create `/workspace/audit.log` containing 'event 1', then immediately read it back using your filesystem read tool.",
    environment={
        "type": "remote",
        "sources": [
            {
                "type": "inline",
                "target": ".agents/hooks.json",
                "content": json.dumps(hooks_config, indent=2),
            }
        ],
        "network": {
            "allowlist": [
                {
                    "domain": "telemetry.example.com",
                    "transform": {
                        "Authorization": "Bearer telemetry_secret_token_123",
                    },
                },
                {"domain": "*"},
            ]
        },
    },
)
print(interaction.output_text)
import { GoogleGenAI } from "@google/genai";

const client = new GoogleGenAI({});

// Define hook without secrets; the egress proxy injects headers dynamically
const hooksConfig = {
    "audit-logging": {
        post_tool_execution: [
            {
                matcher: "view_file|write_to_file|replace_file_content",
                hooks: [
                    {
                        type: "http",
                        url: "https://telemetry.example.com/api/v1/agent-events",
                        timeout: 10,
                    },
                ],
            },
        ],
    },
};

const interaction = await client.interactions.create({
    agent: "antigravity-preview-09-2026",
    input: "Use your filesystem tool to create `/workspace/audit.log` containing 'event 1', then immediately read it back using your filesystem read tool.",
    environment: {
        type: "remote",
        sources: [
            {
                type: "inline",
                target: ".agents/hooks.json",
                content: JSON.stringify(hooksConfig, null, 2),
            },
        ],
        network: {
            allowlist: [
                {
                    domain: "telemetry.example.com",
                    transform: {
                        Authorization: "Bearer telemetry_secret_token_123",
                    },
                },
                { domain: "*" },
            ],
        },
    },
});
console.log(interaction.output_text);
curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
  -H "Content-Type: application/json" \
  -H "x-goog-api-key: *** \
  -d '{
      "agent": "antigravity-preview-09-2026",
      "input": [{"type": "text", "text": "Use your filesystem tool to create /workspace/audit.log containing event 1, then immediately read it back using your filesystem read tool."}],
      "environment": {
          "type": "remote",
          "sources": [
              {
                  "type": "inline",
                  "target": ".agents/hooks.json",
                  "content": "{\"audit-logging\": {\"post_tool_execution\": [{\"matcher\": \"view_file|write_to_file|replace_file_content\", \"hooks\": [{\"type\": \"http\", \"url\": \"https://telemetry.example.com/api/v1/agent-events\", \"timeout\": 10}]}]}}"
              }
          ],
          "network": {
              "allowlist": [
                  {
                      "domain": "telemetry.example.com",
                      "transform": {
                          "Authorization": "Bearer telemetry_secret_token_123"
                      }
                  },
                  {"domain": "*"}
              ]
          }
      }
  }'

제한 사항

  • 샌드박스 도구 범위: 훅은 샌드박스 내부의 내장 도구를 가로채요: 코드 실행(code_execution)과 파일 시스템 작업(view_file, write_to_file, replace_file_content, list_dir, delete_file). 컨테이너 밖에서 처리되는 커스텀 함수 호출(function)이나 외부 Model Context Protocol(mcp_server) 도구에는 발생하지 않아요.
  • 네트워크 허용 목록: HTTP 훅은 컨테이너 네트워크 내부에서 실행돼요. 환경의 network.allowlist에서 대상 URL을 명시적으로 허용해야 해요. 루프백 주소(localhost, 127.0.0.1)는 프록시에 의해 차단돼요.
  • 오류 시 자동 승인: 훅 스크립트가 충돌하거나(0이 아닌 종료 상태), 타임아웃되거나, 실패하면 런타임은 실패를 기록하고 도구 호출이 계속되도록 허용해요. 이는 중단된 린터 스크립트나 중단된 프로세스가 애플리케이션을 영원히 교착상태에 빠뜨리지 않도록 보장해요.
  • 샌드박스 구성 보호: 훅은 컨테이너 샌드박스 내부에서 실행되므로, 파일 시스템 쓰기 도구나 쉘 코드 실행 권한이 있는 에이전트는 쓰기 가능한 워크스페이스 내부의 로컬 .agents/hooks.json이나 스크립트를 수정할 수 있어요. 컨테이너 훅을 자동화된 정책 지침과 운영 가드레일로 사용하세요. 신뢰할 수 없는 모델 실행에 대한 엄격한 변조 방지가 필요하다면 읽기 전용 저장소에서 구성 소스를 마운트하세요.

다음 단계

더 알아보기 (Learn more)