Repository Public
1Hooks let you run custom scripts or external HTTP requests right before or after the agent executes code or modifies files inside its remote sandbox. Use hooks to extend the agent loop with automated guardrails and background workflows, such as:23- **Enforcing safety and access guardrails** before high-risk shell commands or restricted file reads execute.4- **Automating data pipeline transformations** right after an agent creates or modifies files.5- **Streaming enterprise audit telemetry** to external monitoring systems after tool execution.67### Python89 import json10 from google import genai1112 client = genai.Client()1314 hooks_config = {15 "security-gate": {16 "pre_tool_execution": [17 {18 "matcher": "code_execution",19 "hooks": [20 {21 "type": "command",22 "command": "python3 /.agents/hooks-scripts/gate.py",23 "timeout": 10,24 }25 ],26 }27 ]28 }29 }3031 gate_script = """#!/usr/bin/env python332 import sys, json33 data = json.load(sys.stdin)34 cmd = str(data.get("tool_call", {}).get("args", {}))35 if "rm -rf" in cmd:36 print(json.dumps({"decision": "deny", "reason": "Destructive command blocked by security gate."}))37 else:38 print(json.dumps({"decision": "allow"}))39 """4041 interaction = client.interactions.create(42 agent="antigravity-preview-09-2026",43 input="Run `rm -rf /tmp/forbidden` using code_execution.",44 tools=[{"type": "code_execution"}],45 environment={46 "type": "remote",47 "sources": [48 {49 "type": "inline",50 "target": ".agents/hooks.json",51 "content": json.dumps(hooks_config, indent=2),52 },53 {54 "type": "inline",55 "target": ".agents/hooks-scripts/gate.py",56 "content": gate_script,57 },58 ],59 },60 )61 print(interaction.output_text)6263### JavaScript6465 import { GoogleGenAI } from "@google/genai";6667 const client = new GoogleGenAI({});6869 const hooksConfig = {70 "security-gate": {71 pre_tool_execution: [72 {73 matcher: "code_execution",74 hooks: [75 {76 type: "command",77 command: "python3 /.agents/hooks-scripts/gate.py",78 timeout: 10,79 },80 ],81 },82 ],83 },84 };8586 const gateScript = `#!/usr/bin/env python387 import sys, json88 data = json.load(sys.stdin)89 cmd = str(data.get("tool_call", {}).get("args", {}))90 if "rm -rf" in cmd:91 print(json.dumps({"decision": "deny", "reason": "Destructive command blocked by security gate."}))92 else:93 print(json.dumps({"decision": "allow"}))94 `;9596 const interaction = await client.interactions.create({97 agent: "antigravity-preview-09-2026",98 input: "Run `rm -rf /tmp/forbidden` using code_execution.",99 tools: [{ type: "code_execution" }],100 environment: {101 type: "remote",102 sources: [103 {104 type: "inline",105 target: ".agents/hooks.json",106 content: JSON.stringify(hooksConfig, null, 2),107 },108 {109 type: "inline",110 target: ".agents/hooks-scripts/gate.py",111 content: gateScript,112 },113 ],114 },115 });116 console.log(interaction.output_text);117118### Java119120 import com.google.genai.Client;121 import com.google.genai.gaos.models.interactions.AgentOption;122 import com.google.genai.gaos.models.interactions.CodeExecution;123 import com.google.genai.gaos.models.interactions.CreateAgentInteraction;124 import com.google.genai.gaos.models.interactions.CreateAgentInteractionEnvironment;125 import com.google.genai.gaos.models.interactions.Environment;126 import com.google.genai.gaos.models.interactions.Interaction;127 import com.google.genai.gaos.models.interactions.InteractionsInput;128 import com.google.genai.gaos.models.interactions.Source;129 import com.google.genai.gaos.models.interactions.SourceType;130 import com.google.genai.gaos.models.operations.CreateInteractionRequestBody;131 import java.util.List;132133 Client client = new Client();134135 String hooksConfig = """136 {137 "security-gate": {138 "pre_tool_execution": [139 {140 "matcher": "code_execution",141 "hooks": [142 {143 "type": "command",144 "command": "python3 /.agents/hooks-scripts/gate.py",145 "timeout": 10146 }147 ]148 }149 ]150 }151 }152 """;153154 String gateScript = "#!/usr/bin/env python3\n"155 + "import sys, json\n"156 + "data = json.load(sys.stdin)\n"157 + "cmd = str(data.get(\"tool_call\", {}).get(\"args\", {}))\n"158 + "if \"rm -rf\" in cmd:\n"159 + " print(json.dumps({\"decision\": \"deny\", \"reason\": \"Destructive command blocked by security gate.\"}))\n"160 + "else:\n"161 + " print(json.dumps({\"decision\": \"allow\"}))\n";162163 Environment env = Environment.builder()164 .sources(List.of(165 Source.builder()166 .type(SourceType.INLINE)167 .target(".agents/hooks.json")168 .content(hooksConfig)169 .build(),170 Source.builder()171 .type(SourceType.INLINE)172 .target(".agents/hooks-scripts/gate.py")173 .content(gateScript)174 .build()175 ))176 .build();177178 CreateAgentInteraction params = CreateAgentInteraction.builder()179 .agent(AgentOption.of("antigravity-preview-09-2026"))180 .input(InteractionsInput.of("Run `rm -rf /tmp/forbidden` using code_execution."))181 .tools(List.of(CodeExecution.builder().build()))182 .environment(CreateAgentInteractionEnvironment.of(env))183 .build();184185 Interaction interaction = client.interactions.create(CreateInteractionRequestBody.of(params)).interaction().get();186 System.out.println(interaction.outputText().orElse(""));187188### REST189190 curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \191 -H "Content-Type: application/json" \192 -H "x-goog-api-key: $GEMINI_API_KEY" \193 -d '{194 "agent": "antigravity-preview-09-2026",195 "input": [{"type": "text", "text": "Run `rm -rf /tmp/forbidden` using code_execution."}],196 "tools": [{"type": "code_execution"}],197 "environment": {198 "type": "remote",199 "sources": [200 {201 "type": "inline",202 "target": ".agents/hooks.json",203 "content": "{\"security-gate\": {\"pre_tool_execution\": [{\"matcher\": \"code_execution\", \"hooks\": [{\"type\": \"command\", \"command\": \"python3 /.agents/hooks-scripts/gate.py\", \"timeout\": 10}]}]}}"204 },205 {206 "type": "inline",207 "target": ".agents/hooks-scripts/gate.py",208 "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"209 }210 ]211 }212 }'213214## Supported lifecycle events215216Hooks support 2 events inside the sandbox:217218| Event | When it fires | What it does |219|---|---|---|220| `pre_tool_execution` | Right before a tool runs | Can approve (`allow`) or block (`deny`) the tool before it executes. When blocked, the model sees your rejection reason and adapts. |221| `post_tool_execution` | Right after a tool finishes | Runs follow-up tasks like formatting code, running unit tests, or logging telemetry. Cannot block or undo completed actions. |222223### `pre_tool_execution`224225Fires right before a tool executes. Your script reads the tool call details from `stdin` and outputs its decision JSON (`allow` or `deny`) to `stdout`.226227**Input payload (`stdin`):**228229 {230 "tool_call": {231 "name": "code_execution",232 "args": {233 "code": "rm -rf /tmp/forbidden",234 "language": "bash"235 }236 },237 "environment_id": "env_xyz789"238 }239240**Output response (`stdout`):**241242To approve the tool call:243244 {245 "decision": "allow"246 }247248To block the tool call and return feedback to the model:249250 {251 "decision": "deny",252 "reason": "Destructive command blocked by security gate."253 }254255When a hook denies a command, the tool call is skipped immediately. The agent sees an error result containing your rejection reason right inside its current turn. The model can then self-correct by choosing an alternative command or explaining the block to the user.256257If your script outputs unrecognized JSON, plain text, or anything other than `{"decision": "deny"}`, the runtime treats the response as an approval (`allow`).258259### `post_tool_execution`260261Fires right after a tool completes. Your script reads the execution details and any error status from `stdin`.262263**Input payload (`stdin`):**264265 {266 "tool_call": {267 "name": "code_execution",268 "args": {269 "code": "python3 /workspace/app.py",270 "language": "bash"271 }272 },273 "environment_id": "env_xyz789"274 }275276If a shell command prints errors to standard error (`stderr`) or a filesystem operation fails, an `"error"` field containing the error text is included in the payload. When the command succeeds without errors, the `"error"` field is omitted entirely.277278**Output response (`stdout`):**279280 {}281282Because post-tool hooks run strictly for background tasks like code formatting or logging, the runtime ignores any decision values returned on `stdout`.283284## Configuration discovery285286The runtime automatically discovers hook definitions from `.agents/hooks.json` or `/.agents/hooks.json` inside the sandbox environment. You can provide `hooks.json` alongside your custom scripts using any supported [environment source](https://ai.google.dev/gemini-api/docs/agent-environment#mount_from_a_source):287288- **Repository mount** : A Git repository containing `.agents/hooks.json` alongside `AGENTS.md`.289- **Cloud Storage (`gcs`)** : A GCS bucket containing `hooks.json` copied into the environment.290- **Inline sources** : Raw JSON string and script contents passed in `environment.sources` when calling `client.interactions.create`.291292### `hooks.json` schema293294A `hooks.json` file groups event definitions (`pre_tool_execution` or `post_tool_execution`) under custom names. You can enable or disable each group independently:295296 {297 "security-gate": {298 "enabled": true,299 "pre_tool_execution": [300 {301 "matcher": "code_execution",302 "hooks": [303 {304 "type": "command",305 "command": "python3 /.agents/hooks-scripts/gate.py",306 "timeout": 10307 }308 ]309 }310 ]311 },312 "auto-format": {313 "post_tool_execution": [314 {315 "matcher": "*",316 "hooks": [317 {318 "type": "command",319 "command": "python3 /.agents/hooks-scripts/auto_lint.py",320 "timeout": 15321 }322 ]323 }324 ]325 }326 }327328### Matcher syntax and rules329330Each rule group in `hooks.json` defines when and how handlers fire using the `matcher` and `hooks` properties:331332| Field | Type | Description |333|---|---|---|334| `enabled` | `boolean` | Optional. Set to `false` to disable the group (`true` by default). |335| `matcher` | `string` | Regular expression pattern matching target tool names inside the container. |336| `hooks` | `array` | Ordered list of handler definitions (`command` or `http`). Handlers run sequentially in declaration order. |337338#### How regex evaluation works339340When the agent invokes a tool inside the sandbox, the runtime evaluates the tool's container name against your `matcher` pattern using standard RE2 regular expressions. If the regex matches the tool name, all handlers in the `hooks` array execute in order. If multiple rule groups match the same tool, all corresponding handler arrays run.341342You can target any built-in container tool name: code execution (`code_execution`) or filesystem operations (`view_file`, `write_to_file`, `replace_file_content`, `list_dir`, and `delete_file`).343344#### Common matcher expressions345346- `"code_execution"`: Exact string match for shell commands and script executions.347- `"write_to_file"`: Exact match for filesystem file creation and disk writes.348- `"view_file|write_to_file"`: Pipe separation matches multiple specific tool names in a single rule.349- `".*_file"`: Regex wildcard matching any tool ending in `_file` (such as `view_file`, `write_to_file`, or `delete_file`). This covers only part of the filesystem toolset, `replace_file_content` and `list_dir` do not end in `_file`, so name them explicitly when you need them. Standard RE2 regular expressions require `.*`; simple shell globs like `*_file` are invalid regex syntax and will fail to match.350- `".*"` or `"*"` or `""`: Catch-all pattern that intercepts every single tool call inside the container.351352## Handler types353354### Command hooks355356Command hooks execute a shell command or script inside the sandbox. The script receives the event JSON on `stdin` and outputs its decision JSON on `stdout`.357358| Field | Type | Description |359|---|---|---|360| `type` | `string` | Must be `"command"`. |361| `command` | `string` | Command line to run inside the sandbox (for example, `python3 /.agents/hooks-scripts/gate.py`). |362| `timeout` | `integer` | Timeout in seconds. Default: `30`. |363364### HTTP hooks365366HTTP hooks send the event JSON as a POST request to an external HTTPS URL directly from inside the sandbox network. The target server returns its decision in the HTTP response body using the exact same JSON format (`{"decision": "allow"}` or `{"decision": "deny", "reason": "..."}`).367368| Field | Type | Description |369|---|---|---|370| `type` | `string` | Must be `"http"`. |371| `url` | `string` | External HTTPS endpoint to POST the event payload to. |372| `headers` | `object` | Optional key-value pairs for non-sensitive custom headers (such as `{"X-Event-Source": "agent-sandbox"}`). For authentication, use a [credential](https://ai.google.dev/gemini-api/docs/agent-credentials) on the network allowlist instead. |373| `timeout` | `integer` | Timeout in seconds. Default: `30`. |374375#### Egress proxy and token transformation376377Because HTTP hooks execute directly from inside the sandbox network namespace, outgoing requests pass through the transparent egress proxy. This architecture gives you 2 critical security advantages:378379- **Network allowlisting:** Target endpoints must be explicitly permitted in your environment's `network.allowlist`. Loopback traffic (`127.0.0.1` or `localhost`) is blocked by the proxy; always target allowlisted external endpoints.380- **Credential injection:** You do not need to store API keys or secret bearer tokens inside `.agents/hooks.json` or mount them into the container. Store the secret once as a [credential](https://ai.google.dev/gemini-api/docs/agent-credentials) and reference it by ID from your environment's `network.allowlist`. The egress proxy automatically intercepts outgoing HTTP hook traffic and injects the real authentication header on the wire before leaving the sandbox. Inline `transform` rules set headers the same way on the wire, a credential is the one to use when you want to reuse the secret across the project and rotate it in one place. See [network configuration](https://ai.google.dev/gemini-api/docs/agent-environment#network-configuration).381382## How the runtime handles decisions and failures383384- **Synchronous waiting:** The agent pauses and waits for your hooks to finish before continuing.385- **Blocking tool execution:** If your pre-tool hook returns `{"decision": "deny", "reason": "<your reason>"}`, the runtime immediately cancels the tool call. The model sees your rejection reason in its conversation history and adapts by choosing a safe alternative or explaining the block to the user.386- **Handling script crashes, HTTP errors, and timeouts:** If a command script crashes (non-zero exit status), an HTTP hook returns a non-2xx status code (such as a 4xx or 5xx server error), or an operation times out or returns unrecognized JSON, the runtime treats it as an approval (`allow`). Tool execution continues normally so a broken script or unreachable telemetry server never deadlocks your application.387388## Common use cases389390### Multi-turn recovery for data privacy and compliance391392When a hook blocks access to restricted resources---such as directories containing Personally Identifiable Information (PII) or confidential financial records---you can pass `previous_interaction_id` on the next call to continue the turn in the same environment. The agent reads the denial explanation and automatically recovers by querying approved public tables instead.393394### Python395396 import json397 from google import genai398399 client = genai.Client()400401 hooks_config = {402 "privacy-gate": {403 "pre_tool_execution": [404 {405 "matcher": "view_file",406 "hooks": [407 {408 "type": "command",409 "command": "python3 /.agents/hooks-scripts/check_privacy.py",410 "timeout": 5,411 }412 ],413 }414 ]415 }416 }417418 check_privacy_script = """#!/usr/bin/env python3419 import sys, json420 data = json.load(sys.stdin)421 path = str(data.get("tool_call", {}).get("args", {}).get("path", ""))422423 if "/private/" in path:424 resp = {425 "decision": "deny",426 "reason": "Access to confidential `/private/` records is blocked by PII compliance policy. Query approved `/public/` summary tables instead."427 }428 else:429 resp = {"decision": "allow"}430431 print(json.dumps(resp))432 """433434 # Step 1: Agent attempts to read confidential PII records and is intercepted435 int_1 = client.interactions.create(436 agent="antigravity-preview-09-2026",437 input="Use your filesystem tool to read `/workspace/private/employees.json` and summarize the employee details.",438 environment={439 "type": "remote",440 "sources": [441 {442 "type": "inline",443 "target": ".agents/hooks.json",444 "content": json.dumps(hooks_config, indent=2),445 },446 {447 "type": "inline",448 "target": ".agents/hooks-scripts/check_privacy.py",449 "content": check_privacy_script,450 },451 {452 "type": "inline",453 "target": "workspace/private/employees.json",454 "content": '{"employees": [{"id": 1, "salary": 150000, "ssn": "000-00-0000"}]}',455 },456 {457 "type": "inline",458 "target": "workspace/public/summary.json",459 "content": '{"department": "Engineering", "team_size": 42, "status": "active"}',460 },461 ],462 },463 )464 print(int_1.output_text)465466 # Step 2: Continue in the same environment using previous_interaction_id; agent recovers with public tables467 int_2 = client.interactions.create(468 agent="antigravity-preview-09-2026",469 input="Understood. Please read the approved `/workspace/public/summary.json` file instead and provide the summary.",470 environment=int_1.environment_id,471 previous_interaction_id=int_1.id,472 )473 print(int_2.output_text)474475### JavaScript476477 import { GoogleGenAI } from "@google/genai";478479 const client = new GoogleGenAI({});480481 const hooksConfig = {482 "privacy-gate": {483 pre_tool_execution: [484 {485 matcher: "view_file",486 hooks: [487 {488 type: "command",489 command: "python3 /.agents/hooks-scripts/check_privacy.py",490 timeout: 5,491 },492 ],493 },494 ],495 },496 };497498 const checkPrivacyScript = `#!/usr/bin/env python3499 import sys, json500 data = json.load(sys.stdin)501 path = str(data.get("tool_call", {}).get("args", {}).get("path", ""))502503 if "/private/" in path:504 resp = {505 "decision": "deny",506 "reason": "Access to confidential \`/private/\` records is blocked by PII compliance policy. Query approved \`/public/\` summary tables instead."507 }508 else:509 resp = {"decision": "allow"}510511 print(json.dumps(resp))512 `;513514 const int1 = await client.interactions.create({515 agent: "antigravity-preview-09-2026",516 input: "Use your filesystem tool to read `/workspace/private/employees.json` and summarize the employee details.",517 environment: {518 type: "remote",519 sources: [520 {521 type: "inline",522 "target": ".agents/hooks.json",523 content: JSON.stringify(hooksConfig, null, 2),524 },525 {526 type: "inline",527 "target": ".agents/hooks-scripts/check_privacy.py",528 content: checkPrivacyScript,529 },530 {531 type: "inline",532 "target": "workspace/private/employees.json",533 content: '{"employees": [{"id": 1, "salary": 150000, "ssn": "000-00-0000"}]}',534 },535 {536 type: "inline",537 "target": "workspace/public/summary.json",538 content: '{"department": "Engineering", "team_size": 42, "status": "active"}',539 },540 ],541 },542 });543 console.log(int1.output_text);544545 const int2 = await client.interactions.create({546 agent: "antigravity-preview-09-2026",547 input: "Understood. Please read the approved `/workspace/public/summary.json` file instead and provide the summary.",548 environment: int1.environment_id,549 previous_interaction_id: int1.id,550 });551 console.log(int2.output_text);552553### Java554555 import com.google.genai.Client;556 import com.google.genai.gaos.models.interactions.AgentOption;557 import com.google.genai.gaos.models.interactions.CreateAgentInteraction;558 import com.google.genai.gaos.models.interactions.CreateAgentInteractionEnvironment;559 import com.google.genai.gaos.models.interactions.Environment;560 import com.google.genai.gaos.models.interactions.Interaction;561 import com.google.genai.gaos.models.interactions.InteractionsInput;562 import com.google.genai.gaos.models.interactions.Source;563 import com.google.genai.gaos.models.interactions.SourceType;564 import com.google.genai.gaos.models.operations.CreateInteractionRequestBody;565 import java.util.List;566567 Client client = new Client();568569 String hooksConfig = """570 {571 "privacy-gate": {572 "pre_tool_execution": [573 {574 "matcher": "read_file",575 "hooks": [576 {577 "type": "command",578 "command": "python3 /.agents/hooks-scripts/check_privacy.py",579 "timeout": 5580 }581 ]582 }583 ]584 }585 }586 """;587588 String checkPrivacyScript = "#!/usr/bin/env python3\n"589 + "import sys, json\n"590 + "data = json.load(sys.stdin)\n"591 + "path = str(data.get(\"tool_call\", {}).get(\"args\", {}).get(\"path\", \"\"))\n"592 + "if \"/private/\" in path:\n"593 + " resp = {\n"594 + " \"decision\": \"deny\",\n"595 + " \"reason\": \"Access to confidential `/private/` records is blocked by PII compliance policy. Query approved `/public/` summary tables instead.\"\n"596 + " }\n"597 + "else:\n"598 + " resp = {\"decision\": \"allow\"}\n"599 + "print(json.dumps(resp))\n";600601 Environment env = Environment.builder()602 .sources(List.of(603 Source.builder()604 .type(SourceType.INLINE)605 .target(".agents/hooks.json")606 .content(hooksConfig)607 .build(),608 Source.builder()609 .type(SourceType.INLINE)610 .target(".agents/hooks-scripts/check_privacy.py")611 .content(checkPrivacyScript)612 .build(),613 Source.builder()614 .type(SourceType.INLINE)615 .target("workspace/private/employees.json")616 .content("{\"employees\": [{\"id\": 1, \"salary\": 150000, \"ssn\": \"000-00-0000\"}]}")617 .build(),618 Source.builder()619 .type(SourceType.INLINE)620 .target("workspace/public/summary.json")621 .content("{\"department\": \"Engineering\", \"team_size\": 42, \"status\": \"active\"}")622 .build()623 ))624 .build();625626 // Step 1: Agent attempts to read confidential PII records and is intercepted627 CreateAgentInteraction params1 = CreateAgentInteraction.builder()628 .agent(AgentOption.of("antigravity-preview-09-2026"))629 .input(InteractionsInput.of("Use your filesystem tool to read `/workspace/private/employees.json` and summarize the employee details."))630 .environment(CreateAgentInteractionEnvironment.of(env))631 .build();632633 Interaction int1 = client.interactions.create(CreateInteractionRequestBody.of(params1)).interaction().get();634 System.out.println(int1.outputText().orElse(""));635636 // Step 2: Continue in the same environment using previous_interaction_id; agent recovers with public tables637 CreateAgentInteraction params2 = CreateAgentInteraction.builder()638 .agent(AgentOption.of("antigravity-preview-09-2026"))639 .input(InteractionsInput.of("Understood. Please read the approved `/workspace/public/summary.json` file instead and provide the summary."))640 .environment(CreateAgentInteractionEnvironment.of(int1.environmentId().orElse("")))641 .previousInteractionId(int1.id().orElse(""))642 .build();643644 Interaction int2 = client.interactions.create(CreateInteractionRequestBody.of(params2)).interaction().get();645 System.out.println(int2.outputText().orElse(""));646647### REST648649 # Step 1: Attempt to access restricted PII directory (blocked by hook)650 curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \651 -H "Content-Type: application/json" \652 -H "x-goog-api-key: $GEMINI_API_KEY" \653 -d '{654 "agent": "antigravity-preview-09-2026",655 "input": [{"type": "text", "text": "Use your filesystem tool to read /workspace/private/employees.json and summarize the employee details."}],656 "environment": {657 "type": "remote",658 "sources": [659 {660 "type": "inline",661 "target": ".agents/hooks.json",662 "content": "{\"privacy-gate\": {\"pre_tool_execution\": [{\"matcher\": \"view_file\", \"hooks\": [{\"type\": \"command\", \"command\": \"python3 /.agents/hooks-scripts/check_privacy.py\", \"timeout\": 5}]}]}}"663 },664 {665 "type": "inline",666 "target": ".agents/hooks-scripts/check_privacy.py",667 "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"668 },669 {670 "type": "inline",671 "target": "workspace/private/employees.json",672 "content": "{\"employees\": [{\"id\": 1, \"salary\": 150000, \"ssn\": \"000-00-0000\"}]}"673 },674 {675 "type": "inline",676 "target": "workspace/public/summary.json",677 "content": "{\"department\": \"Engineering\", \"team_size\": 42, \"status\": \"active\"}"678 }679 ]680 }681 }'682683 # Step 2: Continue in the same environment using $ENV_ID and $INTERACTION_ID from the previous response684 # curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \685 # -H "Content-Type: application/json" \686 # -H "x-goog-api-key: $GEMINI_API_KEY" \687 # -d '{688 # "agent": "antigravity-preview-09-2026",689 # "input": [{"type": "text", "text": "Understood. Please read the approved /workspace/public/summary.json file instead and provide the summary."}],690 # "environment": "'"$ENV_ID"'",691 # "previous_interaction_id": "'"$INTERACTION_ID"'"692 # }'693694### External audit logging and telemetry695696Send real-time audit events from inside the sandbox to an external monitoring server whenever files are read or modified.697698- **Match multiple tools:** Because matchers use standard regex, you can combine multiple tools in a single rule using pipes (`view_file|write_to_file|replace_file_content`) or wildcards (`.*_file`).699- **Keep secrets out of your configuration:** Store the authentication token as a [credential](https://ai.google.dev/gemini-api/docs/agent-credentials) and reference it by ID from your environment's [network configuration](https://ai.google.dev/gemini-api/docs/agent-environment#network-configuration) (`network.allowlist.credential`). The egress proxy injects the real bearer token on outgoing requests. This example sets the header inline with `transform` instead, which is protected by the same proxy and fits when the token belongs to this one configuration.700701### Python702703 import json704 from google import genai705706 client = genai.Client()707708 # Define hook without secrets; the egress proxy injects headers dynamically709 hooks_config = {710 "audit-logging": {711 "post_tool_execution": [712 {713 "matcher": "view_file|write_to_file|replace_file_content",714 "hooks": [715 {716 "type": "http",717 "url": "https://telemetry.example.com/api/v1/agent-events",718 "timeout": 10,719 }720 ],721 }722 ]723 }724 }725726 interaction = client.interactions.create(727 agent="antigravity-preview-09-2026",728 input="Use your filesystem tool to create `/workspace/audit.log` containing 'event 1', then immediately read it back using your filesystem read tool.",729 environment={730 "type": "remote",731 "sources": [732 {733 "type": "inline",734 "target": ".agents/hooks.json",735 "content": json.dumps(hooks_config, indent=2),736 }737 ],738 "network": {739 "allowlist": [740 {741 "domain": "telemetry.example.com",742 "transform": {743 "Authorization": "Bearer telemetry_secret_token_123",744 },745 },746 {"domain": "*"},747 ]748 },749 },750 )751 print(interaction.output_text)752753### JavaScript754755 import { GoogleGenAI } from "@google/genai";756757 const client = new GoogleGenAI({});758759 // Define hook without secrets; the egress proxy injects headers dynamically760 const hooksConfig = {761 "audit-logging": {762 post_tool_execution: [763 {764 matcher: "view_file|write_to_file|replace_file_content",765 hooks: [766 {767 type: "http",768 url: "https://telemetry.example.com/api/v1/agent-events",769 timeout: 10,770 },771 ],772 },773 ],774 },775 };776777 const interaction = await client.interactions.create({778 agent: "antigravity-preview-09-2026",779 input: "Use your filesystem tool to create `/workspace/audit.log` containing 'event 1', then immediately read it back using your filesystem read tool.",780 environment: {781 type: "remote",782 sources: [783 {784 type: "inline",785 target: ".agents/hooks.json",786 content: JSON.stringify(hooksConfig, null, 2),787 },788 ],789 network: {790 allowlist: [791 {792 domain: "telemetry.example.com",793 transform: {794 Authorization: "Bearer telemetry_secret_token_123",795 },796 },797 { domain: "*" },798 ],799 },800 },801 });802 console.log(interaction.output_text);803804### Java805806 import com.google.genai.Client;807 import com.google.genai.gaos.models.interactions.AgentOption;808 import com.google.genai.gaos.models.interactions.Allowlist;809 import com.google.genai.gaos.models.interactions.AllowlistEntry;810 import com.google.genai.gaos.models.interactions.CreateAgentInteraction;811 import com.google.genai.gaos.models.interactions.CreateAgentInteractionEnvironment;812 import com.google.genai.gaos.models.interactions.Environment;813 import com.google.genai.gaos.models.interactions.EnvironmentNetworkEgressAllowlist;814 import com.google.genai.gaos.models.interactions.Interaction;815 import com.google.genai.gaos.models.interactions.InteractionsInput;816 import com.google.genai.gaos.models.interactions.Network;817 import com.google.genai.gaos.models.interactions.Source;818 import com.google.genai.gaos.models.interactions.SourceType;819 import com.google.genai.gaos.models.interactions.Transform;820 import com.google.genai.gaos.models.operations.CreateInteractionRequestBody;821 import java.util.List;822 import java.util.Map;823824 Client client = new Client();825826 // Define hook without secrets; the egress proxy injects headers dynamically827 String hooksConfig = """828 {829 "audit-logging": {830 "post_tool_execution": [831 {832 "matcher": "read_file|write_file",833 "hooks": [834 {835 "type": "http",836 "url": "https://telemetry.example.com/api/v1/agent-events",837 "timeout": 10838 }839 ]840 }841 ]842 }843 }844 """;845846 Environment env = Environment.builder()847 .sources(List.of(848 Source.builder()849 .type(SourceType.INLINE)850 .target(".agents/hooks.json")851 .content(hooksConfig)852 .build()853 ))854 .network(Network.of(EnvironmentNetworkEgressAllowlist.of(855 Allowlist.builder()856 .allowlist(List.of(857 AllowlistEntry.builder()858 .domain("telemetry.example.com")859 .transform(Transform.of(Map.of(860 "Authorization", "Bearer telemetry_secret_token_123"861 )))862 .build(),863 AllowlistEntry.builder().domain("*").build()864 ))865 .build()866 )))867 .build();868869 CreateAgentInteraction params = CreateAgentInteraction.builder()870 .agent(AgentOption.of("antigravity-preview-09-2026"))871 .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."))872 .environment(CreateAgentInteractionEnvironment.of(env))873 .build();874875 Interaction interaction = client.interactions.create(CreateInteractionRequestBody.of(params)).interaction().get();876 System.out.println(interaction.outputText().orElse(""));877878### REST879880 curl -X POST "https://generativelanguage.googleapis.com/v1beta/interactions" \881 -H "Content-Type: application/json" \882 -H "x-goog-api-key: $GEMINI_API_KEY" \883 -d '{884 "agent": "antigravity-preview-09-2026",885 "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."}],886 "environment": {887 "type": "remote",888 "sources": [889 {890 "type": "inline",891 "target": ".agents/hooks.json",892 "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}]}]}}"893 }894 ],895 "network": {896 "allowlist": [897 {898 "domain": "telemetry.example.com",899 "transform": {900 "Authorization": "Bearer telemetry_secret_token_123"901 }902 },903 {"domain": "*"}904 ]905 }906 }907 }'908909## Limitations910911- **Sandbox tool scope:** Hooks intercept built-in tools inside the sandbox: code execution (`code_execution`) and filesystem operations (`view_file`, `write_to_file`, `replace_file_content`, `list_dir`, and `delete_file`). They do not fire for custom function calling (`function`) or external Model Context Protocol (`mcp_server`) tools handled outside the container.912- **Network allowlists:** HTTP hooks run inside the container network. You must explicitly allow target URLs in your environment's `network.allowlist`. Loopback addresses (`localhost`, `127.0.0.1`) are blocked by the proxy.913- **Automatic approval on errors:** If a hook script crashes (non-zero exit status), times out, or fails, the runtime logs the failure and allows the tool call to continue. This ensures broken linter scripts or hanging processes never deadlock your applications.914- **Sandbox configuration protection:** Because hooks execute inside the container sandbox, agents with filesystem write tools or shell code execution permissions can modify local `.agents/hooks.json` or scripts within writable workspaces. Use container hooks as automated policy guidance and operational guardrails; if strict tamper resistance is required against untrusted model executions, mount configuration sources from read-only repositories.915916## What's next917918- Learn how to configure persistent [remote sandboxes and environments](https://ai.google.dev/gemini-api/docs/agent-environment).919- Explore the capabilities and built-in tools of the [Antigravity agent](https://ai.google.dev/gemini-api/docs/antigravity-agent).920- Review the [Interactions API overview](https://ai.google.dev/gemini-api/docs/interactions-overview) for multi-turn sessions and streaming.