# Google Antigravity Tool-Call Control & Audit — Install Guide

Install ACP for Google Antigravity (agy) via its native hook system. Every tool call policy-checked before it runs, ACP approvals as native prompt cards via force_ask, an audit row per call, and a session receipt. What gets installed where, what the dashboard shows, and how to fix it when it doesn't fire.

# Govern Google Antigravity with Agentic Control Plane

<p style="font-size:17px;line-height:1.6;color:var(--acp-text);max-width:660px;margin:8px 0 6px;">See and control <strong>every tool call Antigravity makes</strong> — shell, file edits, browser actuation, MCP tools, subagents — from one dashboard, alongside your Claude Code, Codex, Cursor, and Grok Build sessions. Antigravity's <a href="/controls/antigravity">hook vocabulary has a native ask</a>; this hook puts your policy on that path.</p>

## TL;DR



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.

<p style="font-size:12.5px;color:var(--acp-text-faint);margin:8px 0 0;">MIT-licensed, zero dependencies, one readable file — <a href="https://github.com/agentic-control-plane/antigravity-acp-plugin">read it before you run it</a>.</p>

## 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](/integrations/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](/controls/antigravity), 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](https://github.com/agentic-control-plane/antigravity-acp-plugin) |
| `~/.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:

1. **Hook layer** — `PreToolUse` with matcher `*` reaches every tool call Antigravity dispatches: shell, file edits, browser actuation, MCP tools, and subagents, across every surface that reads the shared `hooks.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.
2. **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](/cost-tracking) 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.
3. **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](/controls/antigravity)) |
| `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](/controls/antigravity#what-crosses-the-wire).

## Failure posture

Antigravity's hook core is [fail-closed](/controls/antigravity): 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](/controls/antigravity#what-a-blocked-call-looks-like) walks the live-verified case with the exact reason string.

## Manual install

```sh
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](https://cloud.agenticcontrolplane.com)). Check registration inside Antigravity with `/hooks`.

## What you'll see in the dashboard

Once a session has run, [your console](https://cloud.agenticcontrolplane.com/login?from=%2Fonboarding&surface=antigravity){: data-track="AntigravityIntegration: Console Signup"} 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 own `conversationId`

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](/blog/antigravity-permissions-reference) 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](/controls/antigravity). 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
{: #verify-the-installer}

The installer is open source — read every line before you run it.

<div style="margin:20px 0 28px;padding:16px 20px;border:1px solid #d0d0e8;border-radius:10px;background:#f0effe;font-size:13px;line-height:1.7;">
  <strong style="color:#5b5bd6;">Review before you pipe to bash:</strong><br/>
  <span style="color:#4a4a52;">· Installer source: <a href="https://github.com/agentic-control-plane/acp-install" style="color:#5b5bd6;">github.com/agentic-control-plane/acp-install</a> (MIT, auditable)</span><br/>
  <span style="color:#4a4a52;">· The hook itself: <a href="https://github.com/agentic-control-plane/antigravity-acp-plugin" style="color:#5b5bd6;">github.com/agentic-control-plane/antigravity-acp-plugin</a> — one file, zero dependencies, offline tests against a mock gateway</span><br/>
  <span style="color:#4a4a52;">· SHA-256: <a href="/install.sh.sha256" style="color:#5b5bd6;"><code style="font-size:12px;">/install.sh.sha256</code></a> (compare with the <a href="https://github.com/agentic-control-plane/acp-install" style="color:#5b5bd6;">acp-install mirror</a> to verify you got the published version)</span><br/>
  <span style="color:#4a4a52;">· Dry read: <code style="font-size:12px;">curl -sf https://agenticcontrolplane.com/install.sh | less</code> — see every line before running</span>
</div>

*How Antigravity's own controls work — the permissions engine, sandbox, headless posture, and the enterprise plane: [the control-model page](/controls/antigravity). The permission rule language on its own: [the permissions reference](/blog/antigravity-permissions-reference).*

## Frequently asked questions



## Related

- [Antigravity hooks reference](/blog/antigravity-hooks-reference) — the five events, the stdin payload, the decision contract, fail-closed, and how to write your own
- [Antigravity permissions reference](/blog/antigravity-permissions-reference) — `allow` / `ask` / `deny`, the seven actions, prefix and `regex:` matching, the presets
- [Antigravity's control model, explained](/controls/antigravity) — the deep page, with a live-verified blocked call end to end
- [Claude Code](/integrations/claude-code) — the sibling hook contract, fail-open instead of fail-closed
- [Codex CLI](/integrations/codex) — hook plus MCP connector plus model proxy, the three-layer install
- [Turn on Cost X-Ray](/cost-tracking) — which harnesses can be metered, and why Antigravity isn't one of them

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "HowTo",
  "name": "Install Agentic Control Plane in Google Antigravity",
  "description": "Register the ACP PreToolUse hook in Google Antigravity so every tool call is checked against workspace policy before it runs, with approvals as native force_ask prompt cards and an audit row per call.",
  "totalTime": "PT2M",
  "step": [
    {"@type": "HowToStep", "name": "Run the installer", "text": "curl -sf https://agenticcontrolplane.com/install.sh | bash — detects the agy CLI or an existing ~/.gemini/antigravity-cli profile, places the hook at ~/.acp/hooks/antigravity/hook.mjs, and merges its registration into ~/.gemini/config/hooks.json under an acp key without overwriting existing hooks."},
    {"@type": "HowToStep", "name": "Connect a workspace", "text": "The installer opens your browser once to provision a workspace; the key is saved to ~/.acp/credentials. Until it exists, calls proceed with an UNGOVERNED warning rather than being blocked."},
    {"@type": "HowToStep", "name": "Pin the tier for unattended runs", "text": "On CI runners or schedulers, write {\"agent_tier\": \"background\"} to ~/.acp/config.json — Antigravity sanitizes the hook environment, so the file is the reliable channel. Background tier fails closed when the gateway is unreachable."},
    {"@type": "HowToStep", "name": "Start a new Antigravity session and confirm", "text": "The registration is read at session start. Inside Antigravity, /hooks lists the ACP entries; the first checked call appears in the dashboard under the client name antigravity-hook, and a session receipt with a review link prints at Stop."}
  ]
}
</script>
