후크를 사용하면 에이전트가 원격 샌드박스 내에서 코드를 실행하거나 파일을 수정하기 직전 또는 직후에 맞춤 스크립트나 외부 HTTP 요청을 실행할 수 있습니다. 후크를 사용하여 다음과 같은 자동 가이드라인 및 백그라운드 워크플로로 에이전트 루프를 확장합니다.
- 위험도가 높은 셸 명령어 또는 제한된 파일 읽기가 실행되기 전에 안전 및 액세스 가이드라인을 적용합니다.
- 에이전트가 파일을 생성하거나 수정한 직후 데이터 파이프라인 변환 자동화
도구 실행 후 외부 모니터링 시스템으로 엔터프라이즈 감사 텔레메트리 스트리밍
Python
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);
자바
import com.google.genai.Client;
import com.google.genai.gaos.models.interactions.AgentOption;
import com.google.genai.gaos.models.interactions.CodeExecution;
import com.google.genai.gaos.models.interactions.CreateAgentInteraction;
import com.google.genai.gaos.models.interactions.CreateAgentInteractionEnvironment;
import com.google.genai.gaos.models.interactions.Environment;
import com.google.genai.gaos.models.interactions.Interaction;
import com.google.genai.gaos.models.interactions.InteractionsInput;
import com.google.genai.gaos.models.interactions.Source;
import com.google.genai.gaos.models.interactions.SourceType;
import com.google.genai.gaos.models.operations.CreateInteractionRequestBody;
import java.util.List;
Client client = new Client();
String hooksConfig = """
{
"security-gate": {
"pre_tool_execution": [
{
"matcher": "code_execution",
"hooks": [
{
"type": "command",
"command": "python3 /.agents/hooks-scripts/gate.py",
"timeout": 10
}
]
}
]
}
}
""";
String gateScript = "#!/usr/bin/env python3\n"
+ "import sys, json\n"
+ "data = json.load(sys.stdin)\n"
+ "cmd = str(data.get(\"tool_call\", {}).get(\"args\", {}))\n"
+ "if \"rm -rf\" in cmd:\n"
+ " print(json.dumps({\"decision\": \"deny\", \"reason\": \"Destructive command blocked by security gate.\"}))\n"
+ "else:\n"
+ " print(json.dumps({\"decision\": \"allow\"}))\n";
Environment env = Environment.builder()
.sources(List.of(
Source.builder()
.type(SourceType.INLINE)
.target(".agents/hooks.json")
.content(hooksConfig)
.build(),
Source.builder()
.type(SourceType.INLINE)
.target(".agents/hooks-scripts/gate.py")
.content(gateScript)
.build()
))
.build();
CreateAgentInteraction params = CreateAgentInteraction.builder()
.agent(AgentOption.of("antigravity-preview-09-2026"))
.input(InteractionsInput.of("Run `rm -rf /tmp/forbidden` using code_execution."))
.tools(List.of(CodeExecution.builder().build()))
.environment(CreateAgentInteractionEnvironment.of(env))
.build();
Interaction interaction = client.interactions.create(CreateInteractionRequestBody.of(params)).interaction().get();
System.out.println(interaction.outputText().orElse(""));
REST
curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
-H "Content-Type: application/json" \
-H "x-goog-api-key: $GEMINI_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"
}
]
}
}'
지원되는 수명 주기 이벤트
후크는 샌드박스 내에서 다음 두 이벤트를 지원합니다.
| 이벤트 | 실행 시점 | 기능 |
|---|---|---|
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):
{}
도구 후 훅은 코드 형식 지정이나 로깅과 같은 백그라운드 작업에 대해서만 엄격하게 실행되므로 런타임은 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과 같은 간단한 셸 glob은 잘못된 정규 표현식 구문이므로 일치하지 않습니다.".*"또는"*"또는"": 컨테이너 내의 모든 도구 호출을 가로채는 포괄적인 패턴입니다.
핸들러 유형
명령어 후크
명령어 후크는 샌드박스 내에서 셸 명령어 또는 스크립트를 실행합니다. 스크립트는 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 |
이벤트 페이로드를 게시할 외부 HTTPS 엔드포인트입니다. |
headers |
object |
민감하지 않은 맞춤 헤더 (예: {"X-Event-Source": "agent-sandbox"})의 선택적 키-값 쌍입니다. 인증에는 네트워크 허용 목록의 사용자 인증 정보를 대신 사용하세요. |
timeout |
integer |
제한 시간(초)입니다. 기본값: 30 |
이그레스 프록시 및 토큰 변환
HTTP 후크는 샌드박스 네트워크 네임스페이스 내부에서 직접 실행되므로 아웃바운드 요청은 투명 이그레스 프록시를 통과합니다. 이 아키텍처는 다음과 같은 두 가지 중요한 보안 이점을 제공합니다.
- 네트워크 허용 목록: 대상 엔드포인트는 환경의
network.allowlist에서 명시적으로 허용되어야 합니다. 루프백 트래픽 (127.0.0.1또는localhost)은 프록시에 의해 차단되므로 항상 허용 목록에 추가된 외부 엔드포인트를 타겟팅하세요. - 사용자 인증 정보 삽입:
.agents/hooks.json내부에 API 키 또는 보안 비밀 보유자 토큰을 저장하거나 컨테이너에 마운트할 필요가 없습니다. 보안 비밀을 사용자 인증 정보로 한 번 저장하고 환경의network.allowlist에서 ID로 참조합니다. 이그레스 프록시는 샌드박스를 나가기 전에 나가는 HTTP 후크 트래픽을 자동으로 가로채고 실제 인증 헤더를 와이어에 삽입합니다. 인라인transform규칙은 와이어에서 동일한 방식으로 헤더를 설정하며, 프로젝트 전반에서 보안 비밀을 재사용하고 한 곳에서 순환시키려면 사용자 인증 정보를 사용해야 합니다. 네트워크 구성을 참고하세요.
런타임에서 결정과 실패를 처리하는 방법
- 동기식 대기: 상담사가 일시중지하고 후크가 완료될 때까지 기다린 후 계속 진행합니다.
- 도구 실행 차단: 사전 도구 후크가
{"decision": "deny", "reason": "<your reason>"}를 반환하면 런타임이 즉시 도구 호출을 취소합니다. 모델은 대화 기록에서 거부 이유를 확인하고 안전한 대안을 선택하거나 사용자에게 차단을 설명하여 적응합니다. - 스크립트 비정상 종료, HTTP 오류, 시간 제한 처리: 명령 스크립트가 비정상 종료되거나 (0이 아닌 종료 상태) HTTP 후크가 2xx가 아닌 상태 코드 (예: 4xx 또는 5xx 서버 오류)를 반환하거나 작업 시간이 초과되거나 인식할 수 없는 JSON을 반환하는 경우 런타임은 이를 승인 (
allow)으로 처리합니다. 도구 실행은 정상적으로 계속되므로 손상된 스크립트나 연결할 수 없는 원격 분석 서버로 인해 애플리케이션이 교착 상태에 빠지지 않습니다.
일반적인 사용 사례
데이터 개인 정보 보호 및 규정 준수를 위한 멀티턴 복구
후크가 개인 식별 정보 (PII) 또는 기밀 금융 기록이 포함된 디렉터리와 같은 제한된 리소스에 대한 액세스를 차단하는 경우 다음 호출에서 previous_interaction_id를 전달하여 동일한 환경에서 턴을 계속할 수 있습니다. 에이전트는 거부 설명을 읽고 승인된 공개 테이블을 대신 쿼리하여 자동으로 복구합니다.
Python
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);
자바
import com.google.genai.Client;
import com.google.genai.gaos.models.interactions.AgentOption;
import com.google.genai.gaos.models.interactions.CreateAgentInteraction;
import com.google.genai.gaos.models.interactions.CreateAgentInteractionEnvironment;
import com.google.genai.gaos.models.interactions.Environment;
import com.google.genai.gaos.models.interactions.Interaction;
import com.google.genai.gaos.models.interactions.InteractionsInput;
import com.google.genai.gaos.models.interactions.Source;
import com.google.genai.gaos.models.interactions.SourceType;
import com.google.genai.gaos.models.operations.CreateInteractionRequestBody;
import java.util.List;
Client client = new Client();
String hooksConfig = """
{
"privacy-gate": {
"pre_tool_execution": [
{
"matcher": "read_file",
"hooks": [
{
"type": "command",
"command": "python3 /.agents/hooks-scripts/check_privacy.py",
"timeout": 5
}
]
}
]
}
}
""";
String checkPrivacyScript = "#!/usr/bin/env python3\n"
+ "import sys, json\n"
+ "data = json.load(sys.stdin)\n"
+ "path = str(data.get(\"tool_call\", {}).get(\"args\", {}).get(\"path\", \"\"))\n"
+ "if \"/private/\" in path:\n"
+ " resp = {\n"
+ " \"decision\": \"deny\",\n"
+ " \"reason\": \"Access to confidential `/private/` records is blocked by PII compliance policy. Query approved `/public/` summary tables instead.\"\n"
+ " }\n"
+ "else:\n"
+ " resp = {\"decision\": \"allow\"}\n"
+ "print(json.dumps(resp))\n";
Environment env = Environment.builder()
.sources(List.of(
Source.builder()
.type(SourceType.INLINE)
.target(".agents/hooks.json")
.content(hooksConfig)
.build(),
Source.builder()
.type(SourceType.INLINE)
.target(".agents/hooks-scripts/check_privacy.py")
.content(checkPrivacyScript)
.build(),
Source.builder()
.type(SourceType.INLINE)
.target("workspace/private/employees.json")
.content("{\"employees\": [{\"id\": 1, \"salary\": 150000, \"ssn\": \"000-00-0000\"}]}")
.build(),
Source.builder()
.type(SourceType.INLINE)
.target("workspace/public/summary.json")
.content("{\"department\": \"Engineering\", \"team_size\": 42, \"status\": \"active\"}")
.build()
))
.build();
// Step 1: Agent attempts to read confidential PII records and is intercepted
CreateAgentInteraction params1 = CreateAgentInteraction.builder()
.agent(AgentOption.of("antigravity-preview-09-2026"))
.input(InteractionsInput.of("Use your filesystem tool to read `/workspace/private/employees.json` and summarize the employee details."))
.environment(CreateAgentInteractionEnvironment.of(env))
.build();
Interaction int1 = client.interactions.create(CreateInteractionRequestBody.of(params1)).interaction().get();
System.out.println(int1.outputText().orElse(""));
// Step 2: Continue in the same environment using previous_interaction_id; agent recovers with public tables
CreateAgentInteraction params2 = CreateAgentInteraction.builder()
.agent(AgentOption.of("antigravity-preview-09-2026"))
.input(InteractionsInput.of("Understood. Please read the approved `/workspace/public/summary.json` file instead and provide the summary."))
.environment(CreateAgentInteractionEnvironment.of(int1.environmentId().orElse("")))
.previousInteractionId(int1.id().orElse(""))
.build();
Interaction int2 = client.interactions.create(CreateInteractionRequestBody.of(params2)).interaction().get();
System.out.println(int2.outputText().orElse(""));
REST
# 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: $GEMINI_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: $GEMINI_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로 참조합니다. 이그레스 프록시는 나가는 요청에 실제 전달자 토큰을 삽입합니다. 이 예시에서는 동일한 프록시로 보호되고 토큰이 이 구성에 속하는 경우에 적합한transform를 사용하여 헤더를 인라인으로 설정합니다.
Python
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);
자바
import com.google.genai.Client;
import com.google.genai.gaos.models.interactions.AgentOption;
import com.google.genai.gaos.models.interactions.Allowlist;
import com.google.genai.gaos.models.interactions.AllowlistEntry;
import com.google.genai.gaos.models.interactions.CreateAgentInteraction;
import com.google.genai.gaos.models.interactions.CreateAgentInteractionEnvironment;
import com.google.genai.gaos.models.interactions.Environment;
import com.google.genai.gaos.models.interactions.EnvironmentNetworkEgressAllowlist;
import com.google.genai.gaos.models.interactions.Interaction;
import com.google.genai.gaos.models.interactions.InteractionsInput;
import com.google.genai.gaos.models.interactions.Network;
import com.google.genai.gaos.models.interactions.Source;
import com.google.genai.gaos.models.interactions.SourceType;
import com.google.genai.gaos.models.interactions.Transform;
import com.google.genai.gaos.models.operations.CreateInteractionRequestBody;
import java.util.List;
import java.util.Map;
Client client = new Client();
// Define hook without secrets; the egress proxy injects headers dynamically
String hooksConfig = """
{
"audit-logging": {
"post_tool_execution": [
{
"matcher": "read_file|write_file",
"hooks": [
{
"type": "http",
"url": "https://telemetry.example.com/api/v1/agent-events",
"timeout": 10
}
]
}
]
}
}
""";
Environment env = Environment.builder()
.sources(List.of(
Source.builder()
.type(SourceType.INLINE)
.target(".agents/hooks.json")
.content(hooksConfig)
.build()
))
.network(Network.of(EnvironmentNetworkEgressAllowlist.of(
Allowlist.builder()
.allowlist(List.of(
AllowlistEntry.builder()
.domain("telemetry.example.com")
.transform(Transform.of(Map.of(
"Authorization", "Bearer telemetry_secret_token_123"
)))
.build(),
AllowlistEntry.builder().domain("*").build()
))
.build()
)))
.build();
CreateAgentInteraction params = CreateAgentInteraction.builder()
.agent(AgentOption.of("antigravity-preview-09-2026"))
.input(InteractionsInput.of("Use your filesystem tool to create `/workspace/audit.log` containing 'event 1', then immediately read it back using your filesystem read tool."))
.environment(CreateAgentInteractionEnvironment.of(env))
.build();
Interaction interaction = client.interactions.create(CreateInteractionRequestBody.of(params)).interaction().get();
System.out.println(interaction.outputText().orElse(""));
REST
curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \
-H "Content-Type: application/json" \
-H "x-goog-api-key: $GEMINI_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) 또는 외부 모델 컨텍스트 프로토콜(mcp_server) 도구에는 트리거되지 않습니다. - 네트워크 허용 목록: HTTP 후크는 컨테이너 네트워크 내에서 실행됩니다. 환경의
network.allowlist에서 타겟 URL을 명시적으로 허용해야 합니다. 루프백 주소 (localhost,127.0.0.1)는 프록시에 의해 차단됩니다. - 오류 시 자동 승인: 후크 스크립트가 비정상 종료되거나 (0이 아닌 종료 상태) 시간 초과되거나 실패하면 런타임에서 실패를 로깅하고 도구 호출이 계속되도록 허용합니다. 이렇게 하면 린터 스크립트가 중단되거나 프로세스가 멈춰도 애플리케이션이 교착 상태에 빠지지 않습니다.
- 샌드박스 구성 보호: 후크는 컨테이너 샌드박스 내에서 실행되므로 파일 시스템 쓰기 도구나 셸 코드 실행 권한이 있는 에이전트는 쓰기 가능한 워크스페이스 내에서 로컬
.agents/hooks.json또는 스크립트를 수정할 수 있습니다. 컨테이너 후크를 자동 정책 안내 및 운영 가드레일로 사용합니다. 신뢰할 수 없는 모델 실행에 대해 엄격한 변조 방지가 필요한 경우 읽기 전용 저장소에서 구성 소스를 마운트합니다.
다음 단계
- 영구 원격 샌드박스 및 환경을 구성하는 방법을 알아보세요.
- Antigravity 에이전트의 기능과 내장 도구를 살펴봅니다.
- 다중 턴 세션 및 스트리밍에 관한 Interactions API 개요를 검토하세요.