Govern Google Antigravity with Agentic Control Plane
See and control every tool call Antigravity makes — shell, file edits, browser actuation, MCP tools, subagents — from one dashboard, alongside your Claude Code, Codex, Cursor, and Grok Build sessions. Antigravity's hook vocabulary has a native ask; this hook puts your policy on that path.
TL;DR
curl -sf https://agenticcontrolplane.com/install.sh | bash
irm https://agenticcontrolplane.com/install.ps1 | iex
The installer detects Antigravity (the agy CLI or an existing ~/.gemini/antigravity-cli profile), drops the hook at ~/.acp/hooks/antigravity/hook.mjs, merges the registration into ~/.gemini/config/hooks.json (shared by the CLI, the IDE, and the app — merged under an acp key, never overwritten), and opens your browser once to provision a workspace into ~/.acp/credentials. The next Antigravity session is governed.
MIT-licensed, zero dependencies, one readable file — read it before you run it.
The ask that stays an ask
Antigravity is the first harness we’ve integrated whose hooks can hand a call to the human natively. When ACP policy answers ask, the hook returns force_ask — Antigravity renders its own approval card, and because force_ask deliberately ignores cached “Always Allow” grants, a local remembered approval can’t pre-empt a policy hold. (On Grok Build we document the opposite caveat — a local allow rule outranks an ACP ask. Here that gap doesn’t exist.)
Unattended, the same answer resolves correctly with no work on our side: headless Antigravity soft-denies unobtainable approvals itself, continuing the run with a stderr notice. The empty chair answers “no” natively.
What gets installed and where
| Path | Purpose |
|---|---|
~/.acp/hooks/antigravity/hook.mjs |
The hook — one Node file, zero dependencies, MIT on GitHub |
~/.acp/hooks/antigravity/acp.json |
The registration fragment: PreToolUse and PostToolUse with matcher *, plus Stop, each running the same file with the event as its argument |
~/.gemini/config/hooks.json |
The fragment merged in under its own acp key — the file the CLI, the IDE, and the app all read; existing hooks in it are kept |
~/.acp/credentials |
The workspace key from browser auth (written after install; same file every ACP harness reads) |
~/.acp/config.json |
Optional overrides, snake_case keys — agent_tier, check_timeout_ms, govern_base, console_base, shadow |
~/.acp/lapse.log |
One line appended whenever a call proceeded without a policy check, and why |
~/.acp/antigravity-sessions/ |
Per-session counters that feed the receipt; each file is removed when its session’s Stop hook prints the receipt |
The installer is idempotent: the merge writes the same acp key every time, so re-running it updates the registration rather than duplicating it. On a machine without Antigravity the section is a no-op. Under --local it is skipped and says so — this hook needs a workspace.
How it works
One layer, not three. Codex and Claude Code installs wire a hook, an MCP connector, and a model proxy; Antigravity gets the hook, and that’s a deliberate consequence of how Antigravity is built:
- Hook layer —
PreToolUsewith matcher*reaches every tool call Antigravity dispatches: shell, file edits, browser actuation, MCP tools, and subagents, across every surface that reads the sharedhooks.json. Because MCP tool names pass through the hook unchanged, MCP calls are checked on the same path as everything else — there is nothing for a separate connector to cover. - No model proxy. Antigravity’s Gemini endpoint is fixed; there is no base-URL override to point at the ACP proxy. So the cost X-ray that meters Claude Code, Codex, and Cursor model calls is not available here — Antigravity is listed as tool-call control only on that page, and this install writes nothing for it. Hooks see tool calls, not tokens.
- Audit layer — every checked call lands in your ACP activity log as one row under the client name
antigravity-hook, with the canonical tool name, the native tool name, the decision, its reason, the tier, and the session id.
What’s on the wire
| Antigravity event | ACP call | Effect |
|---|---|---|
PreToolUse (matcher *) |
POST /govern/tool-use |
allow / deny / force_ask before anything runs — CLI, IDE, and app |
PostToolUse |
POST /govern/tool-output |
audit + completion record (Antigravity’s payload carries the call and error status, not the tool’s output — contract) |
Stop |
— | session receipt in the scrollback: calls checked, denies, holds, and a console review link |
Tool names. Antigravity’s native names (run_command, view_file, replace_file_content, …) are mapped to the canonical vocabulary before the policy check so content floors fire on input shape, with the native name preserved in the audit record as client_tool_name. run_command’s command string travels in args.CommandLine (verified live) and is lifted into the canonical command field alongside. Unknown and MCP tool names pass through. The full map is on the control-model page.
Failure posture
Antigravity’s hook core is fail-closed: a crashed, timed-out, or non-zero-exiting hook blocks the tool call. That’s the right default for a policy path — and it means a hook must never let an upstream outage become its own crash. This hook always answers in JSON with exit 0 and carries its own posture: gateway unreachable in an attended session fails open, loudly ([ACP] ⚠ UNGOVERNED plus a durable ~/.acp/lapse.log line — an ACP outage must never brick your session); unattended tiers fail closed. One retry on transport failure; an HTTP error status is the server answering and is never retried. The internal 4-second decision budget sits far below the registered 30-second timeout, so the answer always arrives before Antigravity’s error path.
One Antigravity-specific note: the hook environment is sanitized, so configuration rides in ~/.acp/config.json (snake_case keys), not env vars. Unattended fleets should pin "agent_tier": "background" there.
Setting up policy
Policy is set from the dashboard; the hook carries what the rules need to key on.
Tool policies. Rules key on the canonical vocabulary the hook maps to — Bash, Read, Write, Edit, Glob, Grep, WebSearch, WebFetch, Task — and, for shell, on the sub-command the gateway classifies from the leading token (Bash.git, Bash.npm, Bash.docker, …). A rule on Bash.git therefore covers run_command calls whose CommandLine starts with git, whatever Antigravity called the tool. MCP tools arrive under their own names.
Tiers. Antigravity’s payload has no permission-mode field, so the tier is the hook’s to resolve — in this order: ACP_AGENT_TIER if it reaches the hook, then agent_tier in ~/.acp/config.json, then background if CI is set in the environment, else interactive.
| Antigravity context | ACP tier | Posture |
|---|---|---|
IDE or agy session with a person present |
interactive (default) |
Policy ask becomes a force_ask card; gateway outage fails open with a warning |
agy -p on a CI runner or a scheduler |
background — pin it in ~/.acp/config.json |
Policy ask is soft-denied by Antigravity itself; gateway outage fails closed |
Standing floor. Above every workspace’s rules, a short hardline list denies unconditionally — recursive delete of the root, a system directory, or the home directory; mkfs; dd or a redirect onto a raw block device; a fork bomb; kill -1; shutdown, reboot, init 0/6, systemctl poweroff. The control-model page walks the live-verified case with the exact reason string.
Manual install
mkdir -p ~/.acp/hooks/antigravity ~/.gemini/config
curl -fsSL https://raw.githubusercontent.com/agentic-control-plane/antigravity-acp-plugin/main/hook.mjs \
-o ~/.acp/hooks/antigravity/hook.mjs
# Merge the registration (never overwrite — hooks.json is shared):
curl -fsSL https://raw.githubusercontent.com/agentic-control-plane/antigravity-acp-plugin/main/hooks/acp.json \
-o /tmp/acp-agy-hooks.json
node -e 'const fs=require("fs");const p=process.env.HOME+"/.gemini/config/hooks.json";let cur={};try{cur=JSON.parse(fs.readFileSync(p,"utf8"))}catch{};const add=JSON.parse(fs.readFileSync("/tmp/acp-agy-hooks.json","utf8"));fs.writeFileSync(p,JSON.stringify({...cur,...add},null,2))'
Credentials live at ~/.acp/credentials (the installer provisions them; or paste a workspace key from cloud.agenticcontrolplane.com). Check registration inside Antigravity with /hooks.
What you’ll see in the dashboard
Once a session has run, your console shows the calls under the client name antigravity-hook (the hook identifies itself as antigravity-hook/<version> on every request). Each row carries:
tool_name— the canonical name the policy keyed on (Bash,Edit, …)client_tool_name— what Antigravity actually called (run_command,replace_file_content, …)- the decision and its reason — for a deny, the reason is the same text the hook returned to Antigravity after its
[ACP] Denied by policy:prefix agent_tier, and the session id, which is Antigravity’s ownconversationId
That session id is what the end-of-session receipt links to: [ACP] Session receipt: N tool calls governed · … — review this session: https://cloud.agenticcontrolplane.com/sessions/<conversationId>. The receipt prints once, at Stop, and only for sessions in which at least one call was checked.
Troubleshooting
Hook isn’t firing. Inside Antigravity, /hooks lists what’s registered. Then check ~/.gemini/config/hooks.json has an acp key with "enabled": true and the three events — a hand-edit that dropped the key is the usual cause. The registration runs node "$HOME/.acp/hooks/antigravity/hook.mjs" …, so Antigravity has to be able to launch node; on a fail-closed core, a hook it can’t launch reports pre-tool hook failed and blocks the call rather than skipping it.
Nothing fires under --dangerously-skip-permissions. By design — Antigravity’s, not ours. The flag removes the hook layer entirely (verified live), so no hook is consulted and there is no record. Unblock headless runs with scoped permissions.allow rules instead.
[ACP] ⚠ UNGOVERNED: no credential. The hook found neither ACP_BEARER_TOKEN nor ~/.acp/credentials. Calls proceed unchecked until you connect: re-run the installer, or paste a workspace key into ~/.acp/credentials. Every such call also writes a ~/.acp/lapse.log line.
[ACP] ⚠ UNGOVERNED: gateway unreachable (…). An attended session failed open — the call ran without a policy check, and the lapse is logged. Check govern_base in ~/.acp/config.json if you’ve overridden it, and outbound network from the machine. One retry already happened before the warning; an HTTP error status from the gateway is never retried.
Every call is denied on a CI runner. The runner resolved to the background tier (set CI or a pinned agent_tier) and the gateway was unreachable, so the hook failed closed: the reason reads [ACP] Gateway unreachable (…) — background tier stays blocked when policy can't be consulted. That’s the intended safety net. Fix reachability and credentials on the runner; if you decide an outage should let that runner through, set "agent_tier": "interactive" in its ~/.acp/config.json, knowing the trade is a loud warning instead of a block.
No receipt at the end of the session. The receipt prints only when at least one call was checked, and only from the Stop hook — if the Stop entry is missing from the acp key, re-run the installer. Counters live in ~/.acp/antigravity-sessions/<conversationId>.json between calls.
Antigravity was skipped under --local. Expected: ACP’s on-device engine isn’t wired for Antigravity, and the installer says so. Run without --local to connect a workspace.
A force_ask card doesn’t look the way this page describes. This page doesn’t describe it — how the interactive TUI renders a force_ask reason is not something we have verified live, so it’s listed as pending below rather than pictured.
Verified vs. pending
Live-verified end to end on agy 1.1.21 (2026-08-26): the hook fires in real turns before the native permission layer; run_command’s payload key is CommandLine, normalized to the canonical shape — the production hardline floor denied a recursive root delete under the native tool name; headless soft-deny and the session receipt behave as documented.
One verified limit to plan around: --dangerously-skip-permissions bypasses the hook layer entirely — this hook (and any other) is simply not invoked under the flag, unlike Claude Code and Grok Build. Scoped permissions.allow rules, not the flag, are the right way to unblock headless runs; if you operate a fleet, treat the flag’s appearance as an alertable event. Still pending: force_ask rendering in the interactive TUI (offline-verified against the documented contract).
Verify the installer
The installer is open source — read every line before you run it.
· Installer source: github.com/agentic-control-plane/acp-install (MIT, auditable)
· The hook itself: github.com/agentic-control-plane/antigravity-acp-plugin — one file, zero dependencies, offline tests against a mock gateway
· SHA-256:
/install.sh.sha256 (compare with the acp-install mirror to verify you got the published version)· Dry read:
curl -sf https://agenticcontrolplane.com/install.sh | less — see every line before running
How Antigravity’s own controls work — the permissions engine, sandbox, headless posture, and the enterprise plane: the control-model page. The permission rule language on its own: the permissions reference.
Frequently asked questions
How do I add policy and an audit log to Google Antigravity?
Install the ACP hook. Antigravity fires PreToolUse hooks on every matched tool call across the CLI, IDE, and app, reading one shared registration at ~/.gemini/config/hooks.json. One command installs the hook and connects a workspace; every tool call is then checked against your policy before it runs and recorded.
What happens when ACP policy says ask in Antigravity?
It becomes a native Antigravity prompt card. The hook answers force_ask, which prompts the human unconditionally — cached Always-Allow grants don’t pre-empt it. In headless mode Antigravity itself soft-denies the unanswerable ask with a stderr notice, which is exactly the unattended posture ACP wants.
Antigravity hooks fail closed — what does that mean for this hook?
Antigravity blocks the tool call if a hook crashes, times out, or exits non-zero. The ACP hook therefore always answers in JSON with exit 0 and applies its own posture for gateway trouble: attended sessions fail open with a loud UNGOVERNED warning and a durable lapse-log entry; unattended tiers fail closed. Its 4-second internal budget sits far below the registered 30-second timeout, so the hook always answers before the harness’s error path.
Does the ACP hook still work under --dangerously-skip-permissions?
No — and neither does any other Antigravity hook. Verified live: under the flag, the hook layer is not invoked at all, so there is no interception surface left to stand on. This differs from Claude Code and Grok Build, where deny paths survive their bypass flags. Use scoped permissions.allow rules to unblock headless runs instead, and treat the flag as an alertable event in fleets.
Is the Antigravity ACP hook safe to install — does it add dependencies?
It’s one zero-dependency Node file plus a JSON registration merged into ~/.gemini/config/hooks.json under its own acp key — MIT, readable at github.com/agentic-control-plane/antigravity-acp-plugin. The installer merges the registration; it never overwrites existing hooks or touches your permission lists.
What does an Antigravity session look like in the ACP dashboard?
Every checked call is one audit row under the client name antigravity-hook, carrying the canonical tool name the policy keyed on (Bash, Read, Edit, …), Antigravity’s native tool name as client_tool_name (run_command, view_file, replace_file_content, …), the decision and its reason, the agent tier, and the session id — which is Antigravity’s own conversationId, the same id the end-of-session receipt links to.
Can ACP meter what Antigravity's model calls cost?
No. Antigravity’s Gemini endpoint is fixed — there is no base-URL override to point at the ACP proxy — so the cost X-ray that Claude Code, Codex, and Cursor sessions get is not available for Antigravity. The hook covers tool calls: policy, approvals, and the audit record. Spend has to be read from Google’s side.
Why is every tool call denied in my CI run?
Because the gateway couldn’t be reached and the run is on the background tier, which fails closed by design — nobody is watching, so the block is the safety net. The deny reason says so explicitly. Fix reachability (govern_base in ~/.acp/config.json, network egress from the runner, credentials in ~/.acp/credentials). If you would rather an outage let calls through on that runner, set agent_tier to interactive there — knowing the trade is a loud UNGOVERNED warning instead of a block.
Related
- Antigravity hooks reference — the five events, the stdin payload, the decision contract, fail-closed, and how to write your own
- Antigravity permissions reference —
allow/ask/deny, the seven actions, prefix andregex:matching, the presets - Antigravity’s control model, explained — the deep page, with a live-verified blocked call end to end
- Claude Code — the sibling hook contract, fail-open instead of fail-closed
- Codex CLI — hook plus MCP connector plus model proxy, the three-layer install
- Turn on Cost X-Ray — which harnesses can be metered, and why Antigravity isn’t one of them