Хуки позволяют выполнять собственные скрипты или внешние HTTP-запросы непосредственно до или после того, как агент выполнит код или изменит файлы в удаленной изолированной среде. Используйте хуки, чтобы расширить цикл агента с помощью автоматизированных ограничений и фоновых рабочих процессов, например:
- Обеспечение безопасности и ограничение доступа перед выполнением команд оболочки с высоким риском или чтением файлов с ограниченным доступом.
- Автоматизация преобразований конвейера данных сразу после того, как агент создает или изменяет файлы.
Передача телеметрических данных аудита во внешние системы мониторинга после выполнения инструмента.
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(""));
Проложить маршрут
package main
import (
"context"
"fmt"
"log"
"google.golang.org/genai"
"google.golang.org/genai/interactions/models/interactions"
"google.golang.org/genai/interactions/models/operations"
)
func main() {
ctx := context.Background()
client, err := genai.NewClient(ctx, nil)
if err != nil {
log.Fatal(err)
}
hooksConfig := `{
"security-gate": {
"pre_tool_execution": [
{
"matcher": "code_execution",
"hooks": [
{
"type": "command",
"command": "python3 /.agents/hooks-scripts/gate.py",
"timeout": 10
}
]
}
]
}
}`
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"}))
`
env := interactions.Environment{
Sources: []interactions.Source{
{
Type: interactions.SourceTypeInline.ToPointer(),
Target: genai.Ptr(".agents/hooks.json"),
Content: genai.Ptr(hooksConfig),
},
{
Type: interactions.SourceTypeInline.ToPointer(),
Target: genai.Ptr(".agents/hooks-scripts/gate.py"),
Content: genai.Ptr(gateScript),
},
},
}
res, err := client.Interactions.Create(ctx, operations.CreateInteractionRequest{
Body: operations.NewCreateInteractionRequestBody(interactions.CreateAgentInteraction{
Agent: interactions.AgentOption("antigravity-preview-09-2026"),
Input: interactions.NewInteractionsInput("Run `rm -rf /tmp/forbidden` using code_execution."),
Tools: []interactions.Tool{interactions.NewTool(interactions.CodeExecution{})},
Environment: genai.Ptr(interactions.NewCreateAgentInteractionEnvironment(env)),
}),
})
if err != nil {
log.Fatal(err)
}
if res.Interaction.OutputText != nil {
fmt.Println(*res.Interaction.OutputText)
}
}
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"
}
]
}
}'
Поддерживаемые события жизненного цикла
Хуки поддерживают два события в песочнице:
| Событие | Когда срабатывает | Описание |
|---|---|---|
pre_tool_execution |
непосредственно перед запуском инструмента; | Может одобрить (allow) или заблокировать (deny) инструмент до его выполнения. Когда вы блокируете ответ, модель видит причину отказа и адаптируется. |
post_tool_execution |
Сразу после завершения работы инструмента | Выполняет последующие задачи, такие как форматирование кода, запуск модульных тестов или регистрация телеметрии. Нельзя заблокировать или отменить выполненные действия. |
pre_tool_execution
Срабатывает непосредственно перед выполнением инструмента. Ваш скрипт считывает сведения о вызове инструмента из stdin и выводит решение в формате JSON (allow или deny) в stdout.
Входная полезная нагрузка (stdin):
{
"tool_call": {
"name": "code_execution",
"args": {
"code": "rm -rf /tmp/forbidden",
"language": "bash"
}
},
"environment_id": "env_xyz789"
}
Ответ (stdout):
Чтобы одобрить вызов инструмента:
{
"decision": "allow"
}
Чтобы заблокировать вызов инструмента и вернуть отзыв модели:
{
"decision": "deny",
"reason": "Destructive command blocked by security gate."
}
Если хук отклоняет команду, вызов функции сразу пропускается. Агент увидит сообщение об ошибке с причиной отказа прямо в текущем диалоге. После этого модель может исправить ошибку, выбрав другую команду или объяснив пользователю, почему она не может выполнить его запрос.
Если скрипт выводит нераспознанный код JSON, обычный текст или что-то ещё, кроме {"decision": "deny"}, среда выполнения обрабатывает ответ как одобрение (allow).
post_tool_execution
Срабатывает сразу после завершения работы инструмента. Ваш скрипт считывает сведения о выполнении и статусе ошибок из stdin.
Входная полезная нагрузка (stdin):
{
"tool_call": {
"name": "code_execution",
"args": {
"code": "python3 /workspace/app.py",
"language": "bash"
}
},
"environment_id": "env_xyz789"
}
Если команда оболочки выводит ошибки в стандартный поток ошибок (stderr) или операция с файловой системой завершается неудачно, в полезную нагрузку включается поле "error" с текстом ошибки. Если команда выполнена без ошибок, поле "error" не указывается.
Ответ (stdout):
{}
Поскольку хуки после выполнения инструмента используются исключительно для фоновых задач, таких как форматирование кода или ведение журнала, среда выполнения игнорирует любые значения решений, возвращаемые в stdout.
Обнаружение конфигурации
Во время выполнения автоматически обнаруживаются определения хуков из .agents/hooks.json или /.agents/hooks.json в изолированной среде. Вы можете предоставить hooks.json вместе со своими скриптами, используя любой поддерживаемый источник среды:
- Монтирование репозитория. Репозиторий Git, содержащий
.agents/hooks.jsonвместе сAGENTS.md. - Cloud Storage (
gcs). Сегмент GCS, содержащийhooks.json, скопированный в среду. - Встроенные источники. Исходная строка JSON и содержимое скрипта, переданные в
environment.sourcesпри вызовеclient.interactions.create.
Схема hooks.json
Файл hooks.json группирует определения событий (pre_tool_execution или post_tool_execution) под специальными названиями. Вы можете включить или отключить каждую группу отдельно:
{
"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
}
]
}
]
}
}
Синтаксис и правила сопоставления
Каждая группа правил в hooks.json определяет, когда и как активируются обработчики, с помощью свойств matcher и hooks:
| Поле | Тип | Описание |
|---|---|---|
enabled |
boolean |
Необязательное поле. Установите значение false, чтобы отключить группу (по умолчанию используется значение true). |
matcher |
string |
Регулярное выражение для поиска названий целевых инструментов в контейнере. |
hooks |
array |
Упорядоченный список определений обработчиков (command или http). Обработчики выполняются последовательно в порядке объявления. |
Как работает оценка регулярных выражений
Когда агент вызывает инструмент в изолированной среде, среда выполнения сравнивает название контейнера инструмента с шаблоном matcher, используя стандартные регулярные выражения RE2. Если регулярное выражение соответствует названию инструмента, все обработчики в массиве hooks выполняются по порядку. Если одному инструменту соответствуют несколько групп правил, выполняются все массивы обработчиков.
Вы можете выбрать любое встроенное название инструмента контейнера: выполнение кода (code_execution) или операции с файловой системой (view_file, write_to_file, replace_file_content, list_dir и delete_file).
Часто используемые выражения для сопоставления
"code_execution": точное соответствие строк для команд оболочки и выполнения скриптов."write_to_file": точное соответствие для создания файлов в файловой системе и записи на диск."view_file|write_to_file"– вертикальная черта разделяет несколько названий инструментов в одном правиле.".*_file"– регулярное выражение с подстановочным знаком, соответствующее любому инструменту, название которого заканчивается на_file(например,view_file,write_to_fileилиdelete_file). Это выражение охватывает только часть набора инструментов файловой системы. Названияreplace_file_contentиlist_dirне заканчиваются на_file, поэтому их нужно указывать явно. Для стандартных регулярных выражений RE2 требуется.*. Простые шаблоны оболочки, такие как*_file, не являются допустимым синтаксисом регулярных выражений и не будут соответствовать.".*","*"или""– универсальный шаблон, который перехватывает каждый вызов инструмента в контейнере.
Типы обработчиков
Команды-перехватчики
Команды-перехватчики выполняют команду оболочки или скрипт в изолированной среде. Скрипт получает JSON-объект события в stdin и выводит JSON-объект решения в stdout.
| Поле | Тип | Описание |
|---|---|---|
type |
string |
Необходимое значение: "command". |
command |
string |
Командная строка для запуска в изолированной среде (например, python3 /.agents/hooks-scripts/gate.py). |
timeout |
integer |
Время ожидания в секундах. По умолчанию: 30. |
HTTP-хуки
HTTP-хуки отправляют JSON-файл события в виде запроса POST на внешний URL HTTPS непосредственно из сети песочницы. Целевой сервер возвращает свое решение в теле HTTP-ответа в том же формате JSON ({"decision": "allow"} или {"decision": "deny", "reason": "..."}).
| Поле | Тип | Описание |
|---|---|---|
type |
string |
Необходимое значение: "http". |
url |
string |
Внешняя конечная точка HTTPS, в которую отправляется полезная нагрузка события. |
headers |
object |
Необязательные пары "ключ-значение" для пользовательских заголовков, не содержащих конфиденциальную информацию (например, {"X-Event-Source": "agent-sandbox"}). Для аутентификации используйте учетные данные в белом списке сети. |
timeout |
integer |
Время ожидания в секундах. По умолчанию: 30. |
Прокси-сервер исходящего трафика и преобразование токенов
Поскольку хуки HTTP выполняются непосредственно в сетевом пространстве имен изолированной среды, исходящие запросы проходят через прозрачный исходящий прокси-сервер. Такая архитектура дает два важных преимущества в плане безопасности:
- Белый список сети. Конечные точки должны быть явно разрешены в файле
network.allowlistвашей среды. Прокси-сервер блокирует трафик обратной связи (127.0.0.1илиlocalhost). Всегда указывайте внешние конечные точки, добавленные в белый список. - Внедрение учетных данных. Вам не нужно хранить ключи API или секретные токены носителя внутри
.agents/hooks.jsonили монтировать их в контейнер. Сохраните секрет как учетные данные и ссылайтесь на него по идентификатору из файлаnetwork.allowlistсреды. Исходящий прокси-сервер автоматически перехватывает исходящий трафик HTTP-хуков и вставляет в него настоящий заголовок аутентификации перед выходом из песочницы. Встроенные правилаtransformзадают заголовки одинаковым образом, но учетные данные следует использовать, если вы хотите повторно использовать секрет в проекте и менять его в одном месте. Подробнее о конфигурации сети…
Как среда выполнения обрабатывает решения и ошибки
- Синхронное ожидание. Агент приостанавливает работу и ждет, пока ваши хуки не будут выполнены.
- Блокировка выполнения инструмента. Если предвызов инструмента возвращает значение
{"decision": "deny", "reason": "<your reason>"}, среда выполнения немедленно отменяет вызов инструмента. Модель видит причину отклонения в истории чата и адаптируется, выбирая безопасную альтернативу или объясняя пользователю блокировку. - Обработка сбоев скриптов, ошибок HTTP и тайм-аутов. Если скрипт команды завершается со сбоем (ненулевой код выхода), хук HTTP возвращает код статуса, отличный от 2xx (например, ошибку сервера 4xx или 5xx), или если время ожидания операции истекает или возвращается нераспознанный JSON, среда выполнения рассматривает это как одобрение (
allow). Выполнение инструмента продолжается в обычном режиме, поэтому неработающий скрипт или недоступный сервер телеметрии никогда не приведут к зависанию приложения.
Примеры использования
Восстановление многоходовых диалогов для обеспечения конфиденциальности и соответствия требованиям
Если хук блокирует доступ к ресурсам с ограниченным доступом, например к каталогам, содержащим информацию, позволяющую идентифицировать личность, или конфиденциальные финансовые записи, вы можете передать previous_interaction_id при следующем вызове, чтобы продолжить сдачу в той же среде. Агент прочитает объяснение отказа и автоматически восстановится, запросив вместо этого одобренные общедоступные таблицы.
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(""));
Проложить маршрут
package main
import (
"context"
"fmt"
"log"
"google.golang.org/genai"
"google.golang.org/genai/interactions/models/interactions"
"google.golang.org/genai/interactions/models/operations"
)
func main() {
ctx := context.Background()
client, err := genai.NewClient(ctx, nil)
if err != nil {
log.Fatal(err)
}
hooksConfig := `{
"privacy-gate": {
"pre_tool_execution": [
{
"matcher": "read_file",
"hooks": [
{
"type": "command",
"command": "python3 /.agents/hooks-scripts/check_privacy.py",
"timeout": 5
}
]
}
]
}
}`
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))
`
env := interactions.Environment{
Sources: []interactions.Source{
{
Type: interactions.SourceTypeInline.ToPointer(),
Target: genai.Ptr(".agents/hooks.json"),
Content: genai.Ptr(hooksConfig),
},
{
Type: interactions.SourceTypeInline.ToPointer(),
Target: genai.Ptr(".agents/hooks-scripts/check_privacy.py"),
Content: genai.Ptr(checkPrivacyScript),
},
{
Type: interactions.SourceTypeInline.ToPointer(),
Target: genai.Ptr("workspace/private/employees.json"),
Content: genai.Ptr(`{"employees": [{"id": 1, "salary": 150000, "ssn": "000-00-0000"}]}`),
},
{
Type: interactions.SourceTypeInline.ToPointer(),
Target: genai.Ptr("workspace/public/summary.json"),
Content: genai.Ptr(`{"department": "Engineering", "team_size": 42, "status": "active"}`),
},
},
}
// Step 1: Agent attempts to read confidential PII records and is intercepted
res1, err := client.Interactions.Create(ctx, operations.CreateInteractionRequest{
Body: operations.NewCreateInteractionRequestBody(interactions.CreateAgentInteraction{
Agent: interactions.AgentOption("antigravity-preview-09-2026"),
Input: interactions.NewInteractionsInput("Use your filesystem tool to read `/workspace/private/employees.json` and summarize the employee details."),
Environment: genai.Ptr(interactions.NewCreateAgentInteractionEnvironment(env)),
}),
})
if err != nil {
log.Fatal(err)
}
int1 := res1.Interaction
if int1.OutputText != nil {
fmt.Println(*int1.OutputText)
}
// Step 2: Continue in the same environment using previous_interaction_id; agent recovers with public tables
res2, err := client.Interactions.Create(ctx, operations.CreateInteractionRequest{
Body: operations.NewCreateInteractionRequestBody(interactions.CreateAgentInteraction{
Agent: interactions.AgentOption("antigravity-preview-09-2026"),
Input: interactions.NewInteractionsInput("Understood. Please read the approved `/workspace/public/summary.json` file instead and provide the summary."),
Environment: genai.Ptr(interactions.NewCreateAgentInteractionEnvironment(*int1.EnvironmentID)),
PreviousInteractionID: int1.ID,
}),
})
if err != nil {
log.Fatal(err)
}
if res2.Interaction.OutputText != nil {
fmt.Println(*res2.Interaction.OutputText)
}
}
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"'"
# }'
Внешние журналы аудита и телеметрия
Отправлять события аудита в реальном времени из изолированной среды на внешний сервер мониторинга при каждом чтении или изменении файлов.
- Сопоставление с помощью нескольких инструментов. Поскольку сопоставители используют стандартные регулярные выражения, вы можете объединить несколько инструментов в одном правиле, используя вертикальные черты (
view_file|write_to_file|replace_file_content) или подстановочные знаки (.*_file). Не храните секреты в конфигурации. Сохраните токен аутентификации как учетные данные и укажите его идентификатор в конфигурации сети среды (
network.allowlist.credential). Исходящие запросы будут содержать реальный токен доступа, добавленный исходящим прокси-сервером. В этом примере заголовок задается встроенным вtransform, который защищен тем же прокси-сервером и подходит, если токен относится к этой конфигурации.
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.builder()
.allowlist(Allowlist.of(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(""));
Проложить маршрут
package main
import (
"context"
"fmt"
"log"
"google.golang.org/genai"
"google.golang.org/genai/interactions/models/interactions"
"google.golang.org/genai/interactions/models/operations"
)
func main() {
ctx := context.Background()
client, err := genai.NewClient(ctx, nil)
if err != nil {
log.Fatal(err)
}
// Define hook without secrets; the egress proxy injects headers dynamically
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
}
]
}
]
}
}`
env := interactions.Environment{
Sources: []interactions.Source{
{
Type: interactions.SourceTypeInline.ToPointer(),
Target: genai.Ptr(".agents/hooks.json"),
Content: genai.Ptr(hooksConfig),
},
},
Network: genai.Ptr(interactions.NewNetwork(interactions.EnvironmentNetworkEgressAllowlist{
Allowlist: genai.Ptr(interactions.NewAllowlist([]interactions.AllowlistEntry{
{
Domain: "telemetry.example.com",
Transform: genai.Ptr(interactions.NewTransform(map[string]string{
"Authorization": "Bearer telemetry_secret_token_123",
})),
},
{
Domain: "*",
},
})),
})),
}
res, err := client.Interactions.Create(ctx, operations.CreateInteractionRequest{
Body: operations.NewCreateInteractionRequestBody(interactions.CreateAgentInteraction{
Agent: interactions.AgentOption("antigravity-preview-09-2026"),
Input: interactions.NewInteractionsInput("Use your filesystem tool to create `/workspace/audit.log` containing 'event 1', then immediately read it back using your filesystem read tool."),
Environment: genai.Ptr(interactions.NewCreateAgentInteractionEnvironment(env)),
}),
})
if err != nil {
log.Fatal(err)
}
if res.Interaction.OutputText != nil {
fmt.Println(*res.Interaction.OutputText)
}
}
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": "*"}
]
}
}
}'
Ограничения
- Область действия инструмента песочницы. Хуки перехватывают встроенные инструменты в песочнице: выполнение кода (
code_execution) и операции с файловой системой (view_file,write_to_file,replace_file_content,list_dirиdelete_file). Они не срабатывают при вызове пользовательских функций (function) или внешних инструментов Model Context Protocol (mcp_server), обрабатываемых вне контейнера. - Белые списки сетей. HTTP-перехватчики выполняются в сети контейнера. Вам необходимо явно разрешить целевые URL в файле
network.allowlistвашей среды. Прокси-сервер блокирует петлевые адреса (localhost,127.0.0.1). - Автоматическое одобрение при ошибках. Если скрипт хука завершается с ошибкой (ненулевой статус выхода), истекает время ожидания или происходит сбой, среда выполнения регистрирует ошибку и позволяет продолжить вызов инструмента. Это гарантирует, что неисправные скрипты линтера или зависшие процессы никогда не приведут к взаимной блокировке ваших приложений.
- Защита конфигурации изолированной среды. Поскольку хуки выполняются в изолированной среде контейнера, агенты с инструментами записи в файловую систему или разрешениями на выполнение кода оболочки могут изменять локальные файлы
.agents/hooks.jsonили скрипты в рабочих областях с возможностью записи. Используйте хуки контейнера в качестве автоматизированных рекомендаций по соблюдению правил и операционных ограничений. Если требуется высокая степень защиты от несанкционированного доступа при выполнении ненадежных моделей, монтируйте источники конфигурации из репозиториев с доступом только для чтения.
Дальнейшие действия
- Узнайте, как настроить постоянные удаленные песочницы и среды.
- Изучите возможности и встроенные инструменты агента Antigravity.
- Подробную информацию о многоходовых сеансах и потоковой передаче можно найти в обзоре Interactions API.