Hook ช่วยให้คุณเรียกใช้สคริปต์ที่กำหนดเองหรือคำขอ HTTP ภายนอกได้ทันทีก่อนหรือหลังจากที่เอเจนต์เรียกใช้โค้ดหรือแก้ไขไฟล์ภายในแซนด์บ็อกซ์ระยะไกล ใช้ Hook เพื่อขยายลูปของเอเจนต์ด้วยการป้องกันอัตโนมัติและเวิร์กโฟลว์เบื้องหลัง เช่น
- บังคับใช้แนวทางการรักษาความปลอดภัยและการเข้าถึงก่อนที่จะมีการเรียกใช้คำสั่งเชลล์ที่มีความเสี่ยงสูงหรือการอ่านไฟล์ที่ถูกจำกัด
- การเปลี่ยนรูปแบบไปป์ไลน์ข้อมูลโดยอัตโนมัติทันทีที่ตัวแทนสร้างหรือแก้ไขไฟล์
การสตรีมการวัดและส่งข้อมูลทางไกลของการตรวจสอบระดับองค์กรไปยังระบบตรวจสอบภายนอกหลังจากเรียกใช้เครื่องมือ
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)
JavaScript
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);
Java
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"
}
]
}
}'
เหตุการณ์ในวงจรที่รองรับ
Hooks รองรับ 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."
}
เมื่อ Hook ปฏิเสธคำสั่ง ระบบจะข้ามการเรียกใช้เครื่องมือทันที เอเจนต์จะเห็นผลลัพธ์ข้อผิดพลาดที่มีเหตุผลในการปฏิเสธของคุณในเทิร์นปัจจุบัน จากนั้นโมเดลจะแก้ไขตัวเองได้โดยเลือกคำสั่งอื่นหรืออธิบายการบล็อกให้ผู้ใช้ทราบ
หากสคริปต์แสดงผล 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
การค้นพบการกำหนดค่า
รันไทม์จะค้นหานิยาม Hook จาก .agents/hooks.json หรือ /.agents/hooks.json ภายในสภาพแวดล้อมแซนด์บ็อกซ์โดยอัตโนมัติ คุณระบุ hooks.json ควบคู่ไปกับสคริปต์ที่กำหนดเองได้โดยใช้แหล่งที่มาของสภาพแวดล้อมที่รองรับ
- การติดตั้งที่เก็บ: ที่เก็บ Git ที่มี
.agents/hooks.jsonอยู่ข้างAGENTS.md - Cloud Storage (
gcs): Bucket ของ GCS ที่มีhooks.jsonซึ่งคัดลอกลงในสภาพแวดล้อม - แหล่งที่มาแบบอินไลน์: สตริง JSON แบบดิบและเนื้อหาสคริปต์ที่ส่งใน
environment.sourcesเมื่อเรียกใช้client.interactions.create
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) ตัวแฮนเดิลจะทำงานตามลำดับตามลำดับการประกาศ |
วิธีการทำงานของการประเมินนิพจน์ทั่วไป
เมื่อ Agent เรียกใช้เครื่องมือภายในแซนด์บ็อกซ์ รันไทม์จะประเมินชื่อคอนเทนเนอร์ของเครื่องมือเทียบกับรูปแบบ matcher โดยใช้นิพจน์ทั่วไป RE2 มาตรฐาน หากนิพจน์ทั่วไปตรงกับชื่อเครื่องมือ ตัวแฮนเดิลทั้งหมดในอาร์เรย์ 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 มาตรฐานต้องมี.*ส่วน Glob ของเชลล์อย่างง่าย เช่น*_fileเป็นไวยากรณ์นิพจน์ทั่วไปที่ไม่ถูกต้องและจะจับคู่ไม่สำเร็จ".*"หรือ"*"หรือ"": รูปแบบ Catch-all ที่สกัดกั้นการเรียกใช้เครื่องมือทุกรายการภายในคอนเทนเนอร์
ประเภทตัวแฮนเดิล
Command Hooks
Command Hook จะเรียกใช้คำสั่ง Shell หรือสคริปต์ภายในแซนด์บ็อกซ์ สคริปต์จะรับ JSON ของเหตุการณ์ใน stdin และส่งออก JSON ของการตัดสินใจใน stdout
| ช่อง | ประเภท | คำอธิบาย |
|---|---|---|
type |
string |
ต้องเป็น "command" |
command |
string |
บรรทัดคำสั่งที่จะเรียกใช้ภายในแซนด์บ็อกซ์ (เช่น python3 /.agents/hooks-scripts/gate.py) |
timeout |
integer |
การหมดเวลาเป็นวินาที ค่าเริ่มต้น: 30 |
ฮุก HTTP
ฮุก HTTP จะส่ง JSON ของเหตุการณ์เป็นคำขอ POST ไปยัง URL HTTPS ภายนอกโดยตรงจากภายในเครือข่ายแซนด์บ็อกซ์ เซิร์ฟเวอร์เป้าหมายจะส่งคืนการตัดสินใจในเนื้อหาการตอบกลับ HTTP โดยใช้รูปแบบ JSON เดียวกัน ({"decision": "allow"} หรือ {"decision": "deny", "reason": "..."})
| ช่อง | ประเภท | คำอธิบาย |
|---|---|---|
type |
string |
ต้องเป็น "http" |
url |
string |
ปลายทาง HTTPS ภายนอกเพื่อ POST เพย์โหลดเหตุการณ์ |
headers |
object |
คู่คีย์-ค่าที่ไม่บังคับสำหรับส่วนหัวที่กำหนดเองซึ่งไม่มีความละเอียดอ่อน (เช่น {"X-Event-Source": "agent-sandbox"}) สำหรับการตรวจสอบสิทธิ์ ให้ใช้ข้อมูลเข้าสู่ระบบในรายการที่อนุญาตของเครือข่ายแทน |
timeout |
integer |
การหมดเวลาเป็นวินาที ค่าเริ่มต้น: 30 |
พร็อกซีขาออกและการแปลงโทเค็น
เนื่องจากฮุก HTTP จะทํางานจากภายในเนมสเปซเครือข่ายแซนด์บ็อกซ์โดยตรง คําขอขาออกจึงส่งผ่านพร็อกซีขาออกแบบโปร่งใส สถาปัตยกรรมนี้ช่วยให้คุณได้รับประโยชน์ด้านความปลอดภัยที่สำคัญ 2 ประการ ดังนี้
- การอนุญาตพิเศษเครือข่าย: ต้องอนุญาตปลายทางเป้าหมายอย่างชัดเจนใน
network.allowlistของสภาพแวดล้อม พร็อกซีจะบล็อกการรับส่งข้อมูลแบบวนรอบ (127.0.0.1หรือlocalhost) ให้กำหนดเป้าหมายเป็นปลายทางภายนอกที่อยู่ในรายการที่อนุญาตเสมอ - การแทรกข้อมูลเข้าสู่ระบบ: คุณไม่จำเป็นต้องจัดเก็บคีย์ API หรือโทเค็นผู้ถือสิทธิ์ลับไว้ภายใน
.agents/hooks.jsonหรือติดตั้งไว้ในคอนเทนเนอร์ จัดเก็บข้อมูลลับเพียงครั้งเดียวเป็นข้อมูลเข้าสู่ระบบ และอ้างอิงตามรหัสจากnetwork.allowlistของสภาพแวดล้อม พร็อกซีขาออกจะสกัดกั้นการเข้าชมของฮุก HTTP ขาออกโดยอัตโนมัติ และแทรกส่วนหัวการตรวจสอบสิทธิ์จริงในสายก่อนออกจากแซนด์บ็อกซ์ กฎแบบอินไลน์transformจะตั้งค่าส่วนหัวในลักษณะเดียวกันบนสาย และข้อมูลเข้าสู่ระบบคือข้อมูลที่ใช้เมื่อต้องการนำลับไปใช้ซ้ำในโปรเจ็กต์และหมุนเวียนในที่เดียว ดูการกำหนดค่าเครือข่าย
วิธีที่รันไทม์จัดการการตัดสินใจและความล้มเหลว
- การรอแบบซิงโครนัส: เอเจนต์จะหยุดชั่วคราวและรอให้ฮุกของคุณทำงานเสร็จก่อนจึงจะดำเนินการต่อ
- การบล็อกการดำเนินการเครื่องมือ: หาก Hook ก่อนเครื่องมือแสดงผลเป็น
{"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)
JavaScript
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);
Java
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) พร็อกซีขาออกจะแทรกโทเค็นผู้ถือสิทธิ์จริงในคำขอขาออก ตัวอย่างนี้ตั้งค่าส่วนหัวแบบอินไลน์ด้วย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)
JavaScript
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);
Java
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": "*"}
]
}
}
}'
ข้อจำกัด
- ขอบเขตเครื่องมือ Sandbox: Hooks จะสกัดกั้นเครื่องมือในตัวภายใน Sandbox ได้แก่ การเรียกใช้โค้ด (
code_execution) และการดำเนินการระบบไฟล์ (view_file,write_to_file,replace_file_content,list_dirและdelete_file) โดยจะไม่ทริกเกอร์สำหรับการเรียกใช้ฟังก์ชันที่กำหนดเอง (function) หรือเครื่องมือ Model Context Protocol (mcp_server) ภายนอกที่จัดการนอกคอนเทนเนอร์ - รายการที่อนุญาตของเครือข่าย: ฮุก HTTP จะทํางานภายในเครือข่ายคอนเทนเนอร์ คุณต้องอนุญาต URL เป้าหมายอย่างชัดเจนใน
network.allowlistของสภาพแวดล้อม พร็อกซีจะบล็อกที่อยู่ Loopback (localhost,127.0.0.1) - การอนุมัติอัตโนมัติเมื่อเกิดข้อผิดพลาด: หากสคริปต์ Hook ขัดข้อง (สถานะการออกที่ไม่ใช่ 0) หมดเวลา หรือล้มเหลว รันไทม์จะบันทึกความล้มเหลวและอนุญาตให้การเรียกใช้เครื่องมือดำเนินการต่อ ซึ่งจะช่วยให้มั่นใจได้ว่าสคริปต์ Linter ที่ใช้งานไม่ได้หรือกระบวนการที่ค้างจะไม่ทำให้แอปพลิเคชันของคุณหยุดทำงาน
- การป้องกันการกำหนดค่าแซนด์บ็อกซ์: เนื่องจาก Hook ทำงานภายในแซนด์บ็อกซ์ของคอนเทนเนอร์ เอเจนต์ที่มีเครื่องมือเขียนระบบไฟล์หรือสิทธิ์ในการดำเนินการโค้ดเชลล์จึงสามารถแก้ไข
.agents/hooks.jsonหรือสคริปต์ในพื้นที่ทำงานที่เขียนได้ ใช้ Container Hook เป็นคำแนะนำด้านนโยบายอัตโนมัติและแนวทางปฏิบัติงาน หากต้องการป้องกันการดัดแปลงอย่างเข้มงวดเมื่อมีการเรียกใช้โมเดลที่ไม่น่าเชื่อถือ ให้ติดตั้งแหล่งที่มาของการกำหนดค่าจากที่เก็บแบบอ่านอย่างเดียว
ขั้นตอนถัดไป
- ดูวิธีกำหนดค่าแซนด์บ็อกซ์และสภาพแวดล้อมระยะไกลแบบถาวร
- สำรวจความสามารถและเครื่องมือในตัวของเอเจนต์ Antigravity
- ดูภาพรวมของ Interactions API สำหรับเซสชันแบบการสนทนาไปมาและการสตรีม