ฮุก

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 เป็นคำแนะนำด้านนโยบายอัตโนมัติและแนวทางปฏิบัติงาน หากต้องการป้องกันการดัดแปลงอย่างเข้มงวดเมื่อมีการเรียกใช้โมเดลที่ไม่น่าเชื่อถือ ให้ติดตั้งแหล่งที่มาของการกำหนดค่าจากที่เก็บแบบอ่านอย่างเดียว

ขั้นตอนถัดไป