SPB Git

Repository Public

agent-hooks.md 39.4 KB 920 lines markdown

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.
Files main
Menu

Theme