Ganchos

Los hooks te permiten ejecutar secuencias de comandos personalizadas o solicitudes HTTP externas justo antes o después de que el agente ejecute código o modifique archivos dentro de su sandbox remoto. Usa hooks para extender el bucle del agente con protecciones automatizadas y flujos de trabajo en segundo plano, como los siguientes:

  • Aplicación de medidas de seguridad y protección de acceso antes de que se ejecuten comandos de shell de alto riesgo o lecturas de archivos restringidas
  • Automatizar las transformaciones de la canalización de datos inmediatamente después de que un agente cree o modifique archivos
  • Transmite telemetría de auditoría empresarial a sistemas de supervisión externos después de la ejecución de la herramienta.

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

Eventos de ciclo de vida admitidos

Los hooks admiten 2 eventos dentro de la zona de pruebas:

Evento Cuándo se activa Qué hace
pre_tool_execution Justo antes de que se ejecute una herramienta Puede aprobar (allow) o bloquear (deny) la herramienta antes de que se ejecute. Cuando se bloquea, el modelo ve el motivo del rechazo y se adapta.
post_tool_execution Inmediatamente después de que finaliza una herramienta Ejecuta tareas de seguimiento, como dar formato al código, ejecutar pruebas de unidades o registrar datos de telemetría. No se pueden bloquear ni deshacer las acciones completadas.

pre_tool_execution

Se activa justo antes de que se ejecute una herramienta. Tu secuencia de comandos lee los detalles de la llamada a la herramienta desde stdin y genera su JSON de decisión (allow o deny) en stdout.

Carga útil de entrada (stdin):

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

Respuesta de salida (stdout):

Para aprobar la llamada a la herramienta, haz lo siguiente:

{
  "decision": "allow"
}

Para bloquear la llamada a la herramienta y devolver comentarios al modelo, haz lo siguiente:

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

Cuando un gancho rechaza un comando, se omite la llamada a la herramienta de inmediato. El agente ve un resultado de error que contiene el motivo del rechazo en su turno actual. Luego, el modelo puede autocorregirse eligiendo un comando alternativo o explicándole el bloqueo al usuario.

Si tu secuencia de comandos genera JSON no reconocido, texto sin formato o cualquier otro elemento que no sea {"decision": "deny"}, el tiempo de ejecución tratará la respuesta como una aprobación (allow).

post_tool_execution

Se activa inmediatamente después de que se completa una herramienta. Tu secuencia de comandos lee los detalles de ejecución y cualquier estado de error de stdin.

Carga útil de entrada (stdin):

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

Si un comando de shell imprime errores en el error estándar (stderr) o falla una operación del sistema de archivos, se incluye en la carga útil un campo "error" que contiene el texto del error. Cuando el comando se ejecuta correctamente sin errores, se omite por completo el campo "error".

Respuesta de salida (stdout):

{}

Dado que los hooks posteriores a la herramienta se ejecutan estrictamente para tareas en segundo plano, como el registro o el formato de código, el tiempo de ejecución ignora cualquier valor de decisión que se devuelva en stdout.

Descubrimiento de la configuración

El tiempo de ejecución descubre automáticamente las definiciones de gancho de .agents/hooks.json o /.agents/hooks.json dentro del entorno de zona de pruebas. Puedes proporcionar hooks.json junto con tus secuencias de comandos personalizadas usando cualquier fuente de entorno compatible:

  • Repository mount: Es un repositorio de Git que contiene .agents/hooks.json junto con AGENTS.md.
  • Cloud Storage (gcs): Es un bucket de GCS que contiene hooks.json copiado en el entorno.
  • Fuentes intercaladas: Cadena JSON sin procesar y contenido de la secuencia de comandos que se pasan en environment.sources cuando se llama a client.interactions.create.

hooks.json esquema

Un archivo hooks.json agrupa las definiciones de eventos (pre_tool_execution o post_tool_execution) bajo nombres personalizados. Puedes habilitar o inhabilitar cada grupo de forma independiente:

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

Sintaxis y reglas del comparador

Cada grupo de reglas en hooks.json define cuándo y cómo se activan los controladores con las propiedades matcher y hooks:

Campo Tipo Descripción
enabled boolean Opcional. Se establece en false para inhabilitar el grupo (true de forma predeterminada).
matcher string Es el patrón de expresión regular que coincide con los nombres de las herramientas de destino dentro del contenedor.
hooks array Lista ordenada de definiciones de controladores (command o http). Los controladores se ejecutan de forma secuencial en el orden de declaración.

Cómo funciona la evaluación de regex

Cuando el agente invoca una herramienta dentro del entorno de pruebas, el tiempo de ejecución evalúa el nombre del contenedor de la herramienta en función de tu patrón matcher con expresiones regulares RE2 estándar. Si la regex coincide con el nombre de la herramienta, todos los controladores del array hooks se ejecutan en orden. Si varios grupos de reglas coinciden con la misma herramienta, se ejecutan todos los arrays de controladores correspondientes.

Puedes segmentar cualquier nombre de herramienta de contenedor integrada: ejecución de código (code_execution) o operaciones del sistema de archivos (view_file, write_to_file, replace_file_content, list_dir y delete_file).

Expresiones de coincidencia comunes

  • "code_execution": Coincidencia exacta de cadenas para comandos de shell y ejecuciones de secuencias de comandos.
  • "write_to_file": Coincidencia exacta para la creación de archivos del sistema de archivos y las escrituras en el disco.
  • "view_file|write_to_file": La separación con barras verticales coincide con varios nombres de herramientas específicos en una sola regla.
  • ".*_file": Comodín de regex que coincide con cualquier herramienta que termine en _file (como view_file, write_to_file o delete_file). Esto abarca solo una parte del conjunto de herramientas del sistema de archivos. replace_file_content y list_dir no terminan en _file, por lo que debes nombrarlos de forma explícita cuando los necesites. Las expresiones regulares RE2 estándar requieren .*; los comodines simples de shell, como *_file, no son sintaxis de regex válidas y no coincidirán.
  • ".*", "*" o "": Es un patrón general que intercepta cada llamada a la herramienta dentro del contenedor.

Tipos de controladores

Hooks de comandos

Los hooks de comandos ejecutan un comando o una secuencia de comandos de shell dentro de la zona de pruebas. La secuencia de comandos recibe el JSON del evento en stdin y genera su JSON de decisión en stdout.

Campo Tipo Descripción
type string Debe ser "command".
command string Línea de comandos para ejecutar dentro de la zona de pruebas (por ejemplo, python3 /.agents/hooks-scripts/gate.py).
timeout integer Tiempo de espera en segundos. Valor predeterminado: 30.

Hooks HTTP

Los hooks HTTP envían el JSON del evento como una solicitud POST a una URL HTTPS externa directamente desde la red de zona de pruebas. El servidor de destino devuelve su decisión en el cuerpo de la respuesta HTTP con el mismo formato JSON ({"decision": "allow"} o {"decision": "deny", "reason": "..."}).

Campo Tipo Descripción
type string Debe ser "http".
url string Es el extremo HTTPS externo al que se enviará el cuerpo del evento con POST.
headers object Pares clave-valor opcionales para encabezados personalizados no sensibles (como {"X-Event-Source": "agent-sandbox"}). Para la autenticación, usa una credencial en la lista de entidades permitidas de la red.
timeout integer Tiempo de espera en segundos. Valor predeterminado: 30.

Proxy de salida y transformación de tokens

Dado que los hooks HTTP se ejecutan directamente desde el espacio de nombres de la red de zona de pruebas, las solicitudes salientes pasan por el proxy de salida transparente. Esta arquitectura te brinda 2 ventajas de seguridad críticas:

  • Permitir listas de redes: Los endpoints de destino deben permitirse de forma explícita en el network.allowlist de tu entorno. El proxy bloquea el tráfico de bucle invertido (127.0.0.1 o localhost); siempre se deben segmentar los extremos externos incluidos en la lista de entidades permitidas.
  • Inyección de credenciales: No es necesario que almacenes claves de API ni tokens de portador secretos dentro de .agents/hooks.json ni que los montes en el contenedor. Almacena el secreto una vez como una credencial y haz referencia a él por ID desde el network.allowlist de tu entorno. El proxy de salida intercepta automáticamente el tráfico de hooks HTTP saliente y, luego, inserta el encabezado de autenticación real en el cable antes de salir del entorno de pruebas. Las reglas transform intercaladas configuran los encabezados de la misma manera en la conexión. Una credencial es la que se debe usar cuando deseas reutilizar el secreto en todo el proyecto y rotarlo en un solo lugar. Consulta Configuración de red.

Cómo el tiempo de ejecución controla las decisiones y los errores

  • Espera síncrona: El agente se pausa y espera a que finalicen tus hooks antes de continuar.
  • Bloqueo de la ejecución de la herramienta: Si tu gancho previo a la herramienta devuelve {"decision": "deny", "reason": "<your reason>"}, el tiempo de ejecución cancela de inmediato la llamada a la herramienta. El modelo ve el motivo de rechazo en su historial de conversaciones y se adapta eligiendo una alternativa segura o explicándole el bloqueo al usuario.
  • Control de fallas de secuencia de comandos, errores de HTTP y tiempos de espera: Si una secuencia de comandos de comandos falla (estado de salida distinto de cero), un gancho HTTP devuelve un código de estado que no es 2xx (como un error del servidor 4xx o 5xx), o bien si una operación agota el tiempo de espera o devuelve JSON no reconocido, el tiempo de ejecución lo trata como una aprobación (allow). La ejecución de la herramienta continúa con normalidad, por lo que una secuencia de comandos dañada o un servidor de telemetría inaccesible nunca bloquean tu aplicación.

Casos de uso habituales

Recuperación de varios turnos para la privacidad de los datos y el cumplimiento

Cuando un gancho bloquea el acceso a recursos restringidos, como directorios que contienen información de identificación personal (PII) o registros financieros confidenciales, puedes pasar previous_interaction_id en la siguiente llamada para continuar el turno en el mismo entorno. El agente lee la explicación del rechazo y se recupera automáticamente consultando tablas públicas aprobadas.

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

Telemetría y registros de auditoría externos

Envía eventos de auditoría en tiempo real desde la zona de pruebas a un servidor de supervisión externo cada vez que se lean o modifiquen archivos.

  • Coincidencia con varias herramientas: Debido a que los comparadores usan regex estándar, puedes combinar varias herramientas en una sola regla con barras verticales (view_file|write_to_file|replace_file_content) o comodines (.*_file).
  • Mantén los secretos fuera de tu configuración: Almacena el token de autenticación como una credencial y haz referencia a él por ID desde la configuración de red de tu entorno (network.allowlist.credential). El proxy de salida inyecta el token de portador real en las solicitudes salientes. En este ejemplo, se establece el encabezado intercalado con transform, que está protegido por el mismo proxy y se ajusta cuando el token pertenece a esta configuración.

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

Limitaciones

  • Alcance de la herramienta de zona de pruebas: Los hooks interceptan las herramientas integradas dentro de la zona de pruebas: ejecución de código (code_execution) y operaciones del sistema de archivos (view_file, write_to_file, replace_file_content, list_dir y delete_file). No se activan para las llamadas a funciones personalizadas (function) ni para las herramientas externas del Protocolo de contexto del modelo (mcp_server) que se controlan fuera del contenedor.
  • Listas de entidades permitidas de red: Los hooks HTTP se ejecutan dentro de la red del contenedor. Debes permitir de forma explícita las URLs de destino en el network.allowlist de tu entorno. El proxy bloquea las direcciones de bucle invertido (localhost, 127.0.0.1).
  • Aprobación automática en caso de errores: Si una secuencia de comandos de hook falla (estado de salida distinto de cero), se agota el tiempo de espera o falla, el tiempo de ejecución registra la falla y permite que continúe la llamada a la herramienta. Esto garantiza que las secuencias de comandos de linter dañadas o los procesos que se detienen nunca bloqueen tus aplicaciones.
  • Protección de la configuración de la zona de pruebas: Debido a que los hooks se ejecutan dentro de la zona de pruebas del contenedor, los agentes con herramientas de escritura del sistema de archivos o permisos de ejecución de código de shell pueden modificar .agents/hooks.json locales o secuencias de comandos dentro de los espacios de trabajo con permiso de escritura. Usa hooks de contenedores como orientación de políticas automatizada y rieles de protección operativos. Si se requiere una resistencia estricta a la manipulación contra ejecuciones de modelos no confiables, monta fuentes de configuración desde repositorios de solo lectura.

¿Qué sigue?