The Google Antigravity control model, explained
Antigravity is Google’s agent-first development ecosystem — the IDE, the agy CLI that replaced Gemini CLI in May 2026, extensions for VS Code, JetBrains, Zed and Xcode, and an enterprise tier under Gemini Enterprise. The binary is closed source, but the control surface is well documented and largely verifiable from the outside: what follows is read from the official docs and verified where possible against the shipped agy 1.1.21 binary. This page is the reference: what Antigravity ships natively for controlling tool execution, exactly how each mechanism behaves, and where the model ends.
This page covers Antigravity’s own controls. For governing Antigravity with ACP, see the install guide; for the cross-harness picture, the controls comparison.
The permissions engine: three lists, one shape
Rules live in ~/.gemini/antigravity-cli/settings.json under a permissions object with allow, ask, and deny arrays. Every rule is an action(target) resource:
{
"permissions": {
"allow": ["command(git)", "write_file(src/)", "mcp(linter/*)"],
"deny": ["command(rm -rf)", "command(sudo)", "write_file(.git/)"],
"ask": ["command(*)", "execute_url(aws.amazon.com)"]
}
}
Seven actions cover the surface: read_file, write_file, read_url, execute_url (browser actuation), command (prefix or anchored regex — command(npm run (build|lint|test))), mcp (mcp(server/tool), mcp(server/*), mcp(*)), and unsandboxed (see the sandbox section). Precedence is strict — deny > ask > allow — so a command(*) in ask forces a prompt past any narrower allow. Two implicit rules to know: allowing write_file on a path grants read_file there, and denying read_file denies write_file.
The defaults are sensible: files inside the workspace are auto-allowed, and everything else — commands, MCP tools, URLs, out-of-workspace files — defaults to ask. During a prompt you can widen the target’s scope for the rest of the turn (a file to its parent directory), with the notable exception of terminal commands, which can’t be scope-edited at the prompt.
This is a genuinely good rule language. It’s less expressive than the TOML policy engine Gemini CLI shipped (per-mode rules, five priority tiers, root-owned admin policy files — the high-water mark of the July survey), but it covers files, commands, URLs, browser actuation, and MCP tools in one uniform syntax, and MCP coverage at server/tool granularity is better than most harnesses manage.
Headless: the empty chair, answered correctly
In print mode (-p), there are no prompts, and Antigravity resolves the empty chair the right way: an unobtainable approval is soft-denied — the run continues, exits 0, and a stderr notice names the tool and the allow rule you’d need to pre-grant it. A changelog entry mid-2026 fixed headless runs that previously hung or silently auto-approved such calls, which tells you the behavior is deliberate, tested, and recent.
That puts Antigravity in the small club (dsh, Grok Build’s dontAsk, Gemini CLI’s policy engine before it) that answers unanswerable asks with a deny rather than a shrug — and it matters below, because hook-issued asks inherit the same resolution.
The hook system: five events, and the first native ask
Hooks register in hooks.json — workspace-local at .agents/hooks.json (behind folder trust) or user-global at ~/.gemini/config/hooks.json, shared by the CLI, the IDE, and the app. Five events: PreToolUse, PostToolUse, PreInvocation, PostInvocation, Stop. Tool events take a regex matcher on the tool name (run_command|view_file, browser_.*); handlers are shell commands (type: "command" is the only kind) with a 30-second default timeout, camelCase JSON on stdin, JSON decision on stdout.
The vocabulary is the headline. A PreToolUse hook answers with:
{ "decision": "deny", "reason": "…" }
where decision is one of allow, deny, ask, force_ask, deny_unless_prior_grant — plus an optional permissionOverrides array that can grant action(target) resources for the turn. ask prompts the human but respects cached “Always Allow” grants; force_ask prompts unconditionally, ignoring them. No other mainstream harness lets a hook hand a call to the human as a first-class outcome — Claude Code’s hooks can ask, but Grok Build’s gate is allow/deny only, and most others are deny-only. For an external policy layer, force_ask is the exact primitive you want: an approval requirement that a locally cached grant can’t pre-empt. PreInvocation/PostInvocation can inject steps into the loop, and PostInvocation can force-continue or terminate; Stop hooks can re-enter the loop with decision: "continue" (with a built-in cap on consecutive continuations, so a always-blocking stop hook can’t hold the agent hostage forever — a thoughtful touch).
Four sharp edges, all verified:
- A failing hook blocks the call. Crash, timeout, non-zero exit — the harness reports “pre-tool hook failed” and the tool call does not run. This is fail-closed, the opposite of Claude Code’s and Grok Build’s fail-open cores, and it cuts both ways: no silent lapse of a broken policy hook (good), but a hook whose upstream dependency is down can brick every tool call in the session (bad, if the hook doesn’t carry its own posture). Denials must travel as JSON with exit 0 — there is no exit-code deny channel.
- The payload doesn’t name its event. Unlike every Claude-lineage contract, the stdin JSON has no
hookEventName— a handler serving multiple events must be registered with the event as an argument. - The hook environment is sanitized. Only whitelisted variables reach the subshell, so env-var-based configuration of a hook mostly doesn’t.
PostToolUsecarries no tool output. The payload has thetoolCalland anerrorstring — not what the tool returned. Output-content scanning isn’t possible from the hook payload alone (the full exchange is in the transcript file the payload points at).
The sandbox
enableTerminalSandbox (default false) confines agent-launched commands with native OS mechanisms — nsjail (Linux), sandbox-exec (macOS), AppContainer (Windows). The approval prompt adapts to sandbox state: enabled, you can approve a single run without restrictions; disabled, you can opt a risky command into containment. The unsandboxed(pattern) permission action exempts matching commands from confinement while still running them through the permission lists. Inside the workspace, a Git repo’s .git directory is mounted read-only even for otherwise-writable agents.
Same caveat as most of the field: it’s off by default. Our sandbox positioning applies unchanged — containment bounds the blast radius; it doesn’t record or decide anything.
Audit
Locally: per-conversation transcripts (transcript.jsonl under the app data directory) — a full exchange record, not a decision ledger. The enterprise tier is where audit becomes a product: central logging of prompts, agent responses, and metadata behind a single admin toggle, with IAM inheritance, VPC Service Controls, workspace/browser/MCP access restrictions, and pooled spend caps on a rolling seven-day meter, via eligible Gemini Enterprise plans. Google states enterprise session data isn’t used to train foundation models.
Grade it honestly: this is the most complete admin plane any harness vendor ships today. Its boundary is its ecosystem — the controls attach at the Gemini Enterprise tier and govern Antigravity. A fleet that also runs Claude Code, Codex, or Cursor gets no shared record, no shared policy, and no shared approval queue from it.
The escape hatch
--dangerously-skip-permissions auto-approves every tool permission request (stream output reports always-proceed) — and it is total. Verified live on 1.1.21 with an A/B pair of otherwise-identical headless turns: without the flag, a registered PreToolUse hook fires before the permission layer; with the flag, the hook layer is not invoked at all — no hook execution, no decision, no record. That’s the opposite of Claude Code (deny rules, hooks, and the circuit breaker survive --dangerously-skip-permissions) and Grok Build (deny rules and PreToolUse hooks fire in every mode including always-approve). On Antigravity, the escape hatch doesn’t just skip the prompts — it removes the interception surface. If a policy hook is part of your control story, the flag is the line it cannot cross, and unattended fleets should treat its use as the event to alert on.
Where the model ends
- No decision ledger below enterprise. Transcripts record what happened; nothing records what was decided and why — which rule matched, what was asked, who approved. The enterprise audit toggle exists precisely because that record has buyers.
- Config is workspace-writable. Global settings and hooks live in the user profile, but workspace
.agents/hooks.jsonloads behind folder trust — trust the folder and its hooks are live. - PostToolUse can’t see output, so no native post-hoc content control.
- The escape hatch is total.
--dangerously-skip-permissionsremoves hooks from the call path entirely (verified) — no other mainstream harness we’ve tested unhooks its own interception layer with one flag. - One vendor’s fleet. The controls — including the enterprise plane — cover Antigravity. The moment your fleet is heterogeneous, so is your control surface.
Fleet running more than Antigravity? One hook puts ACP’s policy, approvals, and audit record on every Antigravity tool call — the same policy set that governs your Claude Code, Codex, Cursor, and Grok Build sessions.