Hooks

Gli hook ti consentono di eseguire script personalizzati o richieste HTTP esterne immediatamente prima o dopo che l'agente esegue il codice o modifica i file all'interno del sandbox remoto. Utilizza gli hook per estendere il ciclo dell'agente con barriere protettive automatizzate e workflow in background, ad esempio:

  • Applicazione di misure di sicurezza e di controllo dell'accesso prima dell'esecuzione di comandi shell ad alto rischio o di letture di file con restrizioni.
  • Automatizzare le trasformazioni della pipeline di dati subito dopo che un agente crea o modifica i file.
  • Trasmettere in streaming la telemetria di controllo aziendale a sistemi di monitoraggio esterni dopo l'esecuzione dello strumento.

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"
              }
          ]
      }
  }'

Eventi del ciclo di vita supportati

Gli hook supportano due eventi all'interno della sandbox:

Evento Quando viene attivato Descrizione
pre_tool_execution Subito prima dell'esecuzione di uno strumento Può approvare (allow) o bloccare (deny) lo strumento prima dell'esecuzione. Quando viene bloccato, il modello visualizza il motivo del rifiuto e si adegua.
post_tool_execution Subito dopo il completamento di uno strumento Esegue attività di follow-up come la formattazione del codice, l'esecuzione di test delle unità o la registrazione della telemetria. Non è possibile bloccare o annullare le azioni completate.

pre_tool_execution

Viene attivato subito prima dell'esecuzione di uno strumento. Lo script legge i dettagli della chiamata allo strumento da stdin e restituisce il JSON della decisione (allow o deny) a stdout.

Payload di input (stdin):

{
  "tool_call": {
    "name": "code_execution",
    "args": {
      "code": "rm -rf /tmp/forbidden",
      "language": "bash"
    }
  },
  "environment_id": "env_xyz789"
}

Risposta di output (stdout):

Per approvare la chiamata allo strumento:

{
  "decision": "allow"
}

Per bloccare la chiamata allo strumento e restituire il feedback al modello:

{
  "decision": "deny",
  "reason": "Destructive command blocked by security gate."
}

Quando un hook nega un comando, la chiamata allo strumento viene ignorata immediatamente. L'agente visualizza un risultato di errore contenente il motivo del rifiuto all'interno del turno corrente. Il modello può quindi correggersi scegliendo un comando alternativo o spiegando il blocco all'utente.

Se lo script restituisce JSON non riconosciuto, testo normale o qualsiasi altro formato diverso da {"decision": "deny"}, il runtime considera la risposta come approvazione (allow).

post_tool_execution

Viene attivato subito dopo il completamento di uno strumento. Lo script legge i dettagli di esecuzione e lo stato di eventuali errori da stdin.

Payload di input (stdin):

{
  "tool_call": {
    "name": "code_execution",
    "args": {
      "code": "python3 /workspace/app.py",
      "language": "bash"
    }
  },
  "environment_id": "env_xyz789"
}

Se un comando shell stampa errori nell'errore standard (stderr) o un'operazione del file system non va a buon fine, nel payload viene incluso un campo "error" contenente il testo dell'errore. Quando il comando ha esito positivo senza errori, il campo "error" viene omesso completamente.

Risposta di output (stdout):

{}

Poiché gli hook post-strumento vengono eseguiti rigorosamente per attività in background come la formattazione del codice o la registrazione, il runtime ignora tutti i valori di decisione restituiti su stdout.

Rilevamento della configurazione

Il runtime rileva automaticamente le definizioni degli hook da .agents/hooks.json o /.agents/hooks.json all'interno dell'ambiente sandbox. Puoi fornire hooks.json insieme ai tuoi script personalizzati utilizzando qualsiasi origine dell'ambiente supportata:

  • Montaggio del repository: un repository Git contenente .agents/hooks.json insieme a AGENTS.md.
  • Cloud Storage (gcs): un bucket GCS contenente hooks.json copiato nell'ambiente.
  • Origini inline: stringa JSON non elaborata e contenuti dello script passati in environment.sources quando viene chiamato client.interactions.create.

hooks.json schema

Un file hooks.json raggruppa le definizioni degli eventi (pre_tool_execution o post_tool_execution) con nomi personalizzati. Puoi attivare o disattivare ogni gruppo in modo indipendente:

{
  "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
          }
        ]
      }
    ]
  }
}

Sintassi e regole del matcher

Ogni gruppo di regole in hooks.json definisce quando e come vengono attivati i gestori utilizzando le proprietà matcher e hooks:

Campo Tipo Descrizione
enabled boolean Facoltativo. Imposta su false per disattivare il gruppo (true per impostazione predefinita).
matcher string Espressione regolare per trovare corrispondenze con i nomi degli strumenti di targeting all'interno del contenitore.
hooks array Elenco ordinato di definizioni di gestori (command o http). I gestori vengono eseguiti in sequenza nell'ordine di dichiarazione.

Come funziona la valutazione delle espressioni regolari

Quando l'agente richiama uno strumento all'interno della sandbox, il runtime valuta il nome del contenitore dello strumento rispetto al pattern matcher utilizzando le espressioni regolari RE2 standard. Se l'espressione regolare corrisponde al nome dello strumento, tutti i gestori nell'array hooks vengono eseguiti in ordine. Se più gruppi di regole corrispondono allo stesso strumento, vengono eseguiti tutti gli array di gestori corrispondenti.

Puoi scegliere come target qualsiasi nome di strumento contenitore integrato: esecuzione del codice (code_execution) o operazioni sul file system (view_file, write_to_file, replace_file_content, list_dir e delete_file).

Espressioni di corrispondenza comuni

  • "code_execution": Corrispondenza esatta delle stringhe per i comandi della shell e le esecuzioni di script.
  • "write_to_file": Corrispondenza esatta per la creazione di file del file system e le scritture su disco.
  • "view_file|write_to_file": La separazione con la barra verticale corrisponde a più nomi di strumenti specifici in una singola regola.
  • ".*_file": corrispondenza con caratteri jolly regex per qualsiasi strumento che termina con _file (ad esempio view_file, write_to_file o delete_file). Questo copre solo una parte del set di strumenti del file system. replace_file_content e list_dir non terminano con _file, quindi denominali in modo esplicito quando ne hai bisogno. Le espressioni regolari RE2 standard richiedono .*; i caratteri jolly della shell semplici come *_file non sono una sintassi regex valida e non verranno trovate corrispondenze.
  • ".*" o "*" o "": pattern generico che intercetta ogni singola chiamata allo strumento all'interno del contenitore.

Tipi di gestori

Hook dei comandi

Gli hook di comando eseguono un comando shell o uno script all'interno del sandbox. Lo script riceve il JSON dell'evento su stdin e restituisce il JSON della decisione su stdout.

Campo Tipo Descrizione
type string Deve essere "command".
command string Riga di comando da eseguire all'interno della sandbox (ad esempio, python3 /.agents/hooks-scripts/gate.py).
timeout integer Timeout in secondi. Valore predefinito: 30.

Hook HTTP

Gli hook HTTP inviano il JSON dell'evento come richiesta POST a un URL HTTPS esterno direttamente dall'interno della rete sandbox. Il server di destinazione restituisce la sua decisione nel corpo della risposta HTTP utilizzando lo stesso formato JSON ({"decision": "allow"} o {"decision": "deny", "reason": "..."}).

Campo Tipo Descrizione
type string Deve essere "http".
url string Endpoint HTTPS esterno a cui inviare il payload dell'evento.
headers object Coppie chiave-valore facoltative per intestazioni personalizzate non sensibili (ad esempio {"X-Event-Source": "agent-sandbox"}). Per l'autenticazione, utilizza invece una credenziale nella lista consentita di rete.
timeout integer Timeout in secondi. Valore predefinito: 30.

Proxy in uscita e trasformazione dei token

Poiché gli hook HTTP vengono eseguiti direttamente dall'interno dello spazio dei nomi di rete sandbox, le richieste in uscita passano attraverso il proxy di uscita trasparente. Questa architettura offre due vantaggi di sicurezza fondamentali:

  • Elenco consentito di reti:gli endpoint di destinazione devono essere esplicitamente consentiti nel network.allowlist del tuo ambiente. Il traffico di loopback (127.0.0.1 o localhost) viene bloccato dal proxy; scegli sempre come target endpoint esterni consentiti.
  • Inserimento delle credenziali:non è necessario archiviare chiavi API o token di autenticazione segreti all'interno di .agents/hooks.json o montarli nel container. Memorizza il secret una sola volta come credenziale e fai riferimento a esso tramite ID da network.allowlist del tuo ambiente. Il proxy di uscita intercetta automaticamente il traffico HTTP hook in uscita e inserisce l'intestazione di autenticazione reale sul cavo prima di uscire dalla sandbox. Le regole transform inline impostano le intestazioni allo stesso modo sul cavo, una credenziale è quella da utilizzare quando vuoi riutilizzare il secret in tutto il progetto e ruotarlo in un'unica posizione. Consulta la sezione Configurazione di rete.

Come il runtime gestisce le decisioni e gli errori

  • Attesa sincrona: l'agente si mette in pausa e attende il completamento degli hook prima di continuare.
  • Esecuzione dello strumento di blocco:se l'hook pre-strumento restituisce {"decision": "deny", "reason": "<your reason>"}, il runtime annulla immediatamente la chiamata allo strumento. Il modello vede il motivo del rifiuto nella cronologia delle conversazioni e si adatta scegliendo un'alternativa sicura o spiegando il blocco all'utente.
  • Gestione di arresti anomali degli script, errori HTTP e timeout:se un comando script si arresta in modo anomalo (stato di uscita diverso da zero), un hook HTTP restituisce un codice di stato diverso da 2xx (ad esempio un errore del server 4xx o 5xx) oppure un'operazione va in timeout o restituisce JSON non riconosciuto, il runtime lo considera come un'approvazione (allow). L'esecuzione dello strumento continua normalmente, quindi uno script danneggiato o un server di telemetria irraggiungibile non blocca mai l'applicazione.

Casi d'uso comuni

Recupero in più passaggi per la privacy e la conformità dei dati

Quando un hook blocca l'accesso a risorse con limitazioni, come directory contenenti informazioni che consentono l'identificazione personale (PII) o registri finanziari riservati, puoi passare previous_interaction_id alla chiamata successiva per continuare il turno nello stesso ambiente. L'agente legge la spiegazione del rifiuto e recupera automaticamente eseguendo una query sulle tabelle pubbliche approvate.

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"'"
#   }'

Telemetria e logging di audit esterni

Invia eventi di controllo in tempo reale dall'interno della sandbox a un server di monitoraggio esterno ogni volta che i file vengono letti o modificati.

  • Corrispondenza di più strumenti:poiché i matcher utilizzano espressioni regolari standard, puoi combinare più strumenti in una singola regola utilizzando le barre verticali (view_file|write_to_file|replace_file_content) o i caratteri jolly (.*_file).
  • Non includere secret nella configurazione:memorizza il token di autenticazione come credenziale e fai riferimento a esso tramite ID dalla configurazione di rete del tuo ambiente (network.allowlist.credential). Il proxy di uscita inserisce il token di autenticazione effettivo nelle richieste in uscita. Questo esempio imposta l'intestazione in linea con transform, che è protetta dallo stesso proxy e si adatta quando il token appartiene a questa configurazione.

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": "*"}
              ]
          }
      }
  }'

Limitazioni

  • Ambito dello strumento sandbox:gli hook intercettano gli strumenti integrati all'interno della sandbox: esecuzione del codice (code_execution) e operazioni sul file system (view_file, write_to_file, replace_file_content, list_dir e delete_file). Non vengono attivati per la chiamata di funzioni personalizzate (function) o per strumenti esterni Model Context Protocol (mcp_server) gestiti al di fuori del container.
  • Liste consentite di rete:gli hook HTTP vengono eseguiti all'interno della rete del container. Devi consentire esplicitamente gli URL di destinazione nel network.allowlist del tuo ambiente. Gli indirizzi di loopback (localhost, 127.0.0.1) sono bloccati dal proxy.
  • Approvazione automatica in caso di errori: se uno script hook si arresta in modo anomalo (stato di uscita diverso da zero), si verifica un timeout o non va a buon fine, il runtime registra l'errore e consente alla chiamata allo strumento di continuare. In questo modo, gli script di controllo sintattico interrotti o i processi bloccati non bloccano mai le tue applicazioni.
  • Protezione della configurazione della sandbox:poiché gli hook vengono eseguiti all'interno della sandbox del container, gli agenti con strumenti di scrittura del file system o autorizzazioni di esecuzione del codice shell possono modificare .agents/hooks.json o gli script locali all'interno degli spazi di lavoro scrivibili. Utilizza gli hook dei container come indicazioni delle norme automatizzate e misure di salvaguardia operative. Se è richiesta una rigorosa resistenza alla manomissione contro l'esecuzione di modelli non attendibili, monta le origini di configurazione da repository di sola lettura.

Passaggi successivi