Hooks

Les hooks vous permettent d'exécuter des scripts personnalisés ou des requêtes HTTP externes juste avant ou après que l'agent exécute du code ou modifie des fichiers dans son bac à sable distant. Utilisez des hooks pour étendre la boucle de l'agent avec des garde-fous automatisés et des workflows en arrière-plan, par exemple :

  • Application de mesures de sécurité et de protection de l'accès avant l'exécution de commandes shell à risque élevé ou de lectures de fichiers restreintes.
  • Automatiser les transformations de pipeline de données immédiatement après la création ou la modification de fichiers par un agent.
  • Diffuser la télémétrie d'audit Enterprise vers des systèmes de surveillance externes après l'exécution de l'outil.

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

Événements de cycle de vie compatibles

Les hooks sont compatibles avec deux événements dans le bac à sable :

Événement Quand il se déclenche Description
pre_tool_execution Juste avant l'exécution d'un outil Peut approuver (allow) ou bloquer (deny) l'outil avant son exécution. Lorsqu'il est bloqué, le modèle voit la raison de votre refus et s'adapte.
post_tool_execution Juste après la fin d'un outil Exécute des tâches de suivi telles que la mise en forme du code, l'exécution de tests unitaires ou la journalisation de la télémétrie. Vous ne pouvez pas bloquer ni annuler les actions effectuées.

pre_tool_execution

Se déclenche juste avant l'exécution d'un outil. Votre script lit les détails de l'appel d'outil à partir de stdin et génère sa décision JSON (allow ou deny) dans stdout.

Charge utile d'entrée (stdin) :

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

Réponse de sortie (stdout) :

Pour approuver l'appel d'outil :

{
  "decision": "allow"
}

Pour bloquer l'appel d'outil et renvoyer des commentaires au modèle :

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

Lorsqu'un hook refuse une commande, l'appel d'outil est immédiatement ignoré. L'agent voit un résultat d'erreur contenant le motif de refus directement dans son tour actuel. Le modèle peut ensuite s'autocorriger en choisissant une autre commande ou en expliquant le blocage à l'utilisateur.

Si votre script génère un code JSON non reconnu, du texte brut ou tout autre élément que {"decision": "deny"}, le runtime considère la réponse comme une approbation (allow).

post_tool_execution

Se déclenche juste après la fin d'un outil. Votre script lit les détails de l'exécution et l'état des erreurs à partir de stdin.

Charge utile d'entrée (stdin) :

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

Si une commande shell affiche des erreurs sur l'erreur standard (stderr) ou si une opération sur le système de fichiers échoue, un champ "error" contenant le texte de l'erreur est inclus dans la charge utile. Lorsque la commande aboutit sans erreur, le champ "error" est entièrement omis.

Réponse de sortie (stdout) :

{}

Étant donné que les hooks post-outil s'exécutent strictement pour les tâches en arrière-plan telles que la mise en forme du code ou la journalisation, le runtime ignore toutes les valeurs de décision renvoyées sur stdout.

Découverte de la configuration

L'environnement d'exécution détecte automatiquement les définitions de crochet à partir de .agents/hooks.json ou /.agents/hooks.json dans l'environnement de bac à sable. Vous pouvez fournir hooks.json avec vos scripts personnalisés en utilisant n'importe quelle source d'environnement compatible :

  • Montage du dépôt : dépôt Git contenant .agents/hooks.json et AGENTS.md.
  • Cloud Storage (gcs) : bucket GCS contenant hooks.json copié dans l'environnement.
  • Sources intégrées : chaîne JSON brute et contenu du script transmis dans environment.sources lors de l'appel de client.interactions.create.

hooks.json schéma

Un fichier hooks.json regroupe les définitions d'événements (pre_tool_execution ou post_tool_execution) sous des noms personnalisés. Vous pouvez activer ou désactiver chaque groupe indépendamment :

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

Syntaxe et règles des correspondances

Chaque groupe de règles dans hooks.json définit quand et comment les gestionnaires se déclenchent à l'aide des propriétés matcher et hooks :

Champ Type Description
enabled boolean Facultatif. Définissez la valeur sur false pour désactiver le groupe (true par défaut).
matcher string Modèle d'expression régulière correspondant aux noms des outils cibles dans le conteneur.
hooks array Liste ordonnée des définitions de gestionnaire (command ou http). Les gestionnaires s'exécutent de manière séquentielle dans l'ordre de déclaration.

Fonctionnement de l'évaluation des expressions régulières

Lorsque l'agent appelle un outil dans le bac à sable, le runtime évalue le nom du conteneur de l'outil par rapport à votre modèle matcher à l'aide d'expressions régulières RE2 standards. Si l'expression régulière correspond au nom de l'outil, tous les gestionnaires du tableau hooks s'exécutent dans l'ordre. Si plusieurs groupes de règles correspondent au même outil, tous les tableaux de gestionnaires correspondants sont exécutés.

Vous pouvez cibler n'importe quel nom d'outil de conteneur intégré : exécution de code (code_execution) ou opérations sur le système de fichiers (view_file, write_to_file, replace_file_content, list_dir et delete_file).

Expressions de correspondance courantes

  • "code_execution" : correspondance exacte de la chaîne pour les commandes shell et les exécutions de scripts.
  • "write_to_file" : correspondance exacte pour la création de fichiers du système de fichiers et les écritures sur le disque.
  • "view_file|write_to_file" : la séparation par un canal correspond à plusieurs noms d'outils spécifiques dans une même règle.
  • ".*_file" : expression régulière générique correspondant à tout outil se terminant par _file (comme view_file, write_to_file ou delete_file). Cela ne couvre qu'une partie de l'ensemble d'outils du système de fichiers. replace_file_content et list_dir ne se terminent pas par _file. Vous devez donc les nommer explicitement lorsque vous en avez besoin. Les expressions régulières RE2 standards nécessitent .*. Les globs shell simples tels que *_file ne sont pas valides en tant que syntaxe d'expression régulière et ne correspondront pas.
  • ".*", "*" ou "" : modèle générique qui intercepte chaque appel d'outil dans le conteneur.

Types de gestionnaires

Hooks de commande

Les hooks de commande exécutent une commande ou un script shell dans le bac à sable. Le script reçoit le JSON de l'événement sur stdin et génère le JSON de sa décision sur stdout.

Champ Type Description
type string doit être "command"
command string Ligne de commande à exécuter dans le bac à sable (par exemple, python3 /.agents/hooks-scripts/gate.py).
timeout integer Délai avant expiration en secondes. Valeur par défaut : 30

Hooks HTTP

Les hooks HTTP envoient le JSON de l'événement en tant que requête POST à une URL HTTPS externe directement depuis le réseau du bac à sable. Le serveur cible renvoie sa décision dans le corps de la réponse HTTP en utilisant exactement le même format JSON ({"decision": "allow"} ou {"decision": "deny", "reason": "..."}).

Champ Type Description
type string doit être "http"
url string Point de terminaison HTTPS externe auquel envoyer le payload de l'événement.
headers object Paires clé-valeur facultatives pour les en-têtes personnalisés non sensibles (tels que {"X-Event-Source": "agent-sandbox"}). Pour l'authentification, utilisez plutôt un identifiant sur la liste d'autorisation du réseau.
timeout integer Délai avant expiration en secondes. Valeur par défaut : 30

Proxy de sortie et transformation des jetons

Étant donné que les hooks HTTP s'exécutent directement à l'intérieur de l'espace de noms réseau du bac à sable, les requêtes sortantes transitent par le proxy de sortie transparent. Cette architecture vous offre deux avantages essentiels en termes de sécurité :

  • Ajout à la liste d'autorisation du réseau : les points de terminaison cibles doivent être explicitement autorisés dans le network.allowlist de votre environnement. Le trafic de bouclage (127.0.0.1 ou localhost) est bloqué par le proxy. Ciblez toujours les points de terminaison externes autorisés.
  • Injection d'identifiants : vous n'avez pas besoin de stocker les clés API ni les jetons secrets du porteur dans .agents/hooks.json ni de les monter dans le conteneur. Stockez le secret une seule fois en tant qu'identifiant et référencez-le par ID à partir du network.allowlist de votre environnement. Le proxy de sortie intercepte automatiquement le trafic de hook HTTP sortant et injecte le véritable en-tête d'authentification sur le réseau avant de quitter le bac à sable. Les règles transform intégrées définissent les en-têtes de la même manière sur le réseau. Un identifiant est celui à utiliser lorsque vous souhaitez réutiliser le secret dans le projet et le faire pivoter à un seul endroit. Consultez la section Configuration réseau.

Comment le runtime gère les décisions et les échecs

  • Attente synchrone : l'agent s'interrompt et attend que vos hooks se terminent avant de continuer.
  • Bloquer l'exécution de l'outil : si votre hook pré-outil renvoie {"decision": "deny", "reason": "<your reason>"}, le runtime annule immédiatement l'appel d'outil. Le modèle voit le motif de votre refus dans son historique de conversation et s'adapte en choisissant une alternative sûre ou en expliquant le blocage à l'utilisateur.
  • Gestion des plantages de script, des erreurs HTTP et des délais d'attente : si un script de commande plante (code d'état non nul), qu'un hook HTTP renvoie un code d'état non 2xx (tel qu'une erreur de serveur 4xx ou 5xx), ou qu'une opération expire ou renvoie un JSON non reconnu, le runtime le traite comme une approbation (allow). L'exécution de l'outil se poursuit normalement. Ainsi, un script défectueux ou un serveur de télémétrie inaccessible ne bloquent jamais votre application.

Cas d'utilisation courants

Récupération multitours pour la confidentialité et la conformité des données

Lorsqu'un crochet bloque l'accès à des ressources restreintes, telles que des répertoires contenant des informations permettant d'identifier personnellement l'utilisateur (PII) ou des dossiers financiers confidentiels, vous pouvez transmettre previous_interaction_id lors du prochain appel pour poursuivre le tour dans le même environnement. L'agent lit l'explication du refus et récupère automatiquement les données en interrogeant des tables publiques approuvées.

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

Journalisation et télémétrie des audits externes

Envoyez des événements d'audit en temps réel depuis le bac à sable vers un serveur de surveillance externe chaque fois que des fichiers sont lus ou modifiés.

  • Faire correspondre plusieurs outils : comme les outils de correspondance utilisent des expressions régulières standards, vous pouvez combiner plusieurs outils dans une même règle à l'aide de barres verticales (view_file|write_to_file|replace_file_content) ou de caractères génériques (.*_file).
  • Ne pas inclure de secrets dans votre configuration : stockez le jeton d'authentification en tant qu'identifiant et référencez-le par ID à partir de la configuration réseau de votre environnement (network.allowlist.credential). Le proxy de sortie injecte le jeton de support réel dans les requêtes sortantes. Cet exemple définit l'en-tête intégré à transform, qui est protégé par le même proxy et s'adapte lorsque le jeton appartient à cette configuration.

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

Limites

  • Champ d'application de l'outil Bac à sable : les hooks interceptent les outils intégrés dans le bac à sable : exécution de code (code_execution) et opérations sur le système de fichiers (view_file, write_to_file, replace_file_content, list_dir et delete_file). Ils ne se déclenchent pas pour l'appel de fonction personnalisé (function) ni pour les outils externes Model Context Protocol (mcp_server) gérés en dehors du conteneur.
  • Listes d'autorisation réseau : les hooks HTTP s'exécutent dans le réseau de conteneurs. Vous devez autoriser explicitement les URL cibles dans le network.allowlist de votre environnement. Les adresses de rebouclage (localhost, 127.0.0.1) sont bloquées par le proxy.
  • Approbation automatique en cas d'erreur : si un script de hook plante (état de sortie différent de zéro), expire ou échoue, le runtime enregistre l'échec et permet à l'appel d'outil de se poursuivre. Ainsi, les scripts Linter défaillants ou les processus suspendus ne bloquent jamais vos applications.
  • Protection de la configuration du bac à sable : étant donné que les hooks s'exécutent dans le bac à sable du conteneur, les agents disposant d'outils d'écriture dans le système de fichiers ou d'autorisations d'exécution de code shell peuvent modifier les .agents/hooks.json locaux ou les scripts dans les espaces de travail accessibles en écriture. Utilisez des hooks de conteneur comme conseils automatisés sur les règles et comme garde-fous opérationnels. Si une résistance stricte à la falsification est requise contre les exécutions de modèles non fiables, montez les sources de configuration à partir de dépôts en lecture seule.

Étape suivante