Skip to content

feat(agent): add Antigravity (agy) as a first-class agent - #57

Merged
Rfluid merged 5 commits into
mainfrom
feat/agy
Sep 17, 2026
Merged

Rfluid merged 5 commits into
mainfrom
feat/agy

Conversation

@Rfluid

@Rfluid Rfluid commented Sep 17, 2026

Copy link
Copy Markdown
Owner

Implements docs/plans/antigravity-agent.md, included in the branch.

Google's Antigravity CLI (agy) is a fourth agent in daily use with its own rate-limit windows that nothing else surfaces. This puts them in the tray ring, the Quota tab, the Forecast tab and aura quota, and adds an activity-only reader.

Three commits, reviewable in order:

766f82e fix(reader) — deterministic peak_hour. Independent pre-existing bug.
c02141f refactor(core) — share executable lookup between plugins and agents. No behaviour change for plugins.
bdec11c feat(agent) — the agent itself.

Quota — why a subprocess

agy -p "/usage" --output-format json. A direct call to the underlying RPC was investigated and rejected: agy keeps its OAuth token in the OS keyring, and v1internal:retrieveUserQuotaSummary is gated on the CLI's own client identity — a valid bearer token alone returns 403 You do not have a valid license of this product. Going direct would mean a reverse-engineered internal proto, a license handshake, and cross-platform keyring credential handling, all undocumented.

The slash command is answered locally from a cached backend reading, so it starts no LLM turn and consumes no quota (num_turns: 0). A 20s TTL cache absorbs the ~3s of Go-binary startup. Windows are reordered so position 0 is the session and position 1 the week, matching every other agent and keeping the tray_*_source defaults meaningful.

Activity — conversation_summaries.db

Read via diesel (bundled SQLite). Opened read-only through file:…?mode=ro so a running agy is never blocked — deliberately not immutable=1, which reads the main file alone and would silently miss anything still in the WAL; that's the fallback for when -shm can't be created.

Rows from both the CLI and the Antigravity IDE count. They share one account and one table (app_data_dir distinguishes them); filtering to CLI-only reduced a real profile from 13 sessions spanning 278 days to 1.

No token counts

Antigravity's trajectories are schema-less protobuf with no plaintext model names, so there is nothing to attribute. AgentKind::reports_tokens answers this synchronously and UsageSnapshot::tokens_unreported carries it on a loaded snapshot; a test pins the two together. The Models tab is dropped entirely rather than rendered blank — both its chart and its per-model bars are token-derived. The selected section is resolved against the visible list at render time, so switching profiles while sitting on Models falls back to Quota instead of desyncing.

Also in here

  • New optional command key on agent profiles, for an install outside PATH.
  • bin_path. Aura runs from a GUI launcher, a systemd user unit or a launchd agent, none of which source shell rc files — so PATH omits ~/.local/bin, which is exactly where agy installs itself. The plugin runner already compensated when spawning children; that logic is now shared, and binaries are resolved to an absolute path up front. That matters for portability: Unix resolves a bare program name against the PATH handed to the child, but Windows resolves it against the parent's.
  • aura quota's WINDOW column 14 → 18. Claude/GPT · week is the first 17-character label any backend has produced.

Cross-platform

Verified by reading the sources that decide the behaviour, not by running on those machines:

  • SQLite URI — checked against sqlite3ParseUri. In the filename state it treats exactly %, ?, # specially, so & and = are literal and must not be encoded. No drive-letter special case, so file:C:/dir/x.db is correct. The authority check runs on the raw string before decoding, so a UNC path would fail with invalid uri authority; the second slash is encoded to sidestep it.
  • Windows console — aura is windows_subsystem = "windows", so a console-subsystem child opens a window. agy is one and the tray polls every 30s; CREATE_NO_WINDOW prevents a CMD window flashing twice a minute.
  • Windows binary location — agy installs to %LOCALAPPDATA%\agy\bin, which is searched explicitly.
  • macOS — launchd gives a bundled app only /usr/bin:/bin:/usr/sbin:/sbin, and agy's installer adds its directory to your shell profile, which launchd never reads. Covered by the same fallback list. The bundle uses the hardened runtime with no entitlements file, so it is not sandboxed and spawning is unrestricted.
  • Data dir — ~/.gemini/antigravity-cli on all three; agy hardcodes that relative path.
  • Compilation — the #[cfg]-gated code type-checks on all six release targets.

aarch64-pc-windows-msvc is the one target that cross-compiles rather than building natively, which now matters because bundled SQLite compiles C. The runner image carries Microsoft.VisualStudio.Component.VC.Tools.ARM64, and that target is already experimental: true.

Testing

302 tests pass (+58), clippy --all-targets -D warnings clean, fmt clean.

Verified against a live agy 1.2.4 install: setup-config detection, aura quota returning all four windows with reset times, aura usage reporting 13 sessions / 1673 messages over 278 days (and 1 session / 2 messages at --period 7d), and the whole thing working under the real systemd user-unit PATH. The icon renders to a clean arc through usvg/resvg 0.45, the same pipeline gpui::SvgRenderer uses.

Not verified by execution: the modal's rendering of the hidden Models tab, and anything on Windows or macOS. Worth a look from a launched (not terminal) Aura if you have either.

`hour_counts` is a HashMap and `max_by_key` keeps the last maximum it
sees, so when two hours tied the winner depended on hash iteration order
and changed between runs on identical data — the Summary tab's "Peak
hour" flipped on every refresh.

Ties now resolve to the earliest hour. Agent-agnostic, but sparse
histories hit it constantly: a real 13-session profile had a four-way
tie at three sessions each.
The plugin runner already knew that Aura's inherited PATH is the wrong
one: it runs from a GUI launcher, a systemd user unit or a launchd
agent, none of which source the user's shell rc files, so PATH routinely
omits the per-user bin directories where CLI tools install themselves.
Its `augmented_path()` helper compensated when spawning plugins.

Move that into `bin_path`, and add `resolve_executable`, which returns
an absolute path instead. Widening only the child's PATH is not enough
on its own: Unix resolves a bare program name against the PATH handed to
the child, but Windows resolves it against the parent's. Resolving up
front behaves the same everywhere and lets callers report which
directories were searched.

No behaviour change for plugins.
Google's Antigravity CLI is a fourth agent in daily use with its own
rate-limit windows that nothing else surfaces. Adds `AgentKind::
Antigravity` with quota and forecast parity, plus an activity-only
reader.

Quota comes from `agy -p "/usage" --output-format json`. A direct call
to the underlying RPC was investigated and rejected: `agy` keeps its
OAuth token in the OS keyring, and `v1internal:retrieveUserQuotaSummary`
is gated on the CLI's own client identity — a valid bearer token alone
returns 403. The slash command is answered locally from a cached backend
reading, so it starts no LLM turn and consumes no quota; a 20s TTL cache
absorbs the ~3s of Go-binary startup. Windows are reordered so position
0 is the session and position 1 the week, matching every other agent and
keeping the `tray_*_source` defaults meaningful.

Activity comes from `conversation_summaries.db` via diesel. Opened
read-only through a `file:…?mode=ro` URI so a running `agy` is never
blocked — not `immutable=1`, which reads the main file alone and would
miss anything still in the WAL. Rows from both the CLI and the
Antigravity IDE count; they share one account and one table.

Antigravity publishes no token counts (its trajectories are schema-less
protobuf with no plaintext model names), so `AgentKind::reports_tokens`
and `UsageSnapshot::tokens_unreported` carry that fact: the Models tab
is dropped entirely rather than rendered blank, and token stats read
"not reported" instead of `0`.

Also adds an optional `command` key on agent profiles, for an install
outside PATH.
Windows CI caught two Unix-isms in the tests added by the previous two
commits. Both were test bugs; neither indicated a product problem.

`bin_path` tests built PATH strings by hand with `:`. On Windows the
separator is `;`, so the whole string parsed as one entry, which then
failed the `is_dir` filter and vanished — taking the assertion with it.
They also hardcoded `/usr/bin` as an inherited entry, which does not
exist there. Both now go through `std::env::join_paths` against real
temporary directories, so they assert the same thing on every host
instead of quietly degenerating.

The reader test built a directory named `we?rd#dir` to prove that URI
syntax in a path is escaped. `?` is not a legal filename character on
Windows, so the setup failed before reaching the code under test. The
name is now narrowed per platform to the characters that can actually
occur there, which keeps the assertion meaningful on Windows rather than
skipping it.
Comment thread crates/aura/src/cli/quota.rs Fixed
Two suppressions that previously existed only as clicks in the Security
tab now live in the repo, where they are reviewable and travel with the
branch.

`paths-ignore: vendor` stops scanning `vendor/gpui`, a patched copy of
Zed's gpui carried for the macOS 26 NSApplication fix. We don't author it
and can't act on findings in it, so its alerts — currently a standing
`rust/access-invalid-pointer` — are pure noise. This takes effect because
CodeQL analyses Rust with build mode `none`; `paths-ignore` would be
ignored if it were tracing a real `cargo build`.

`query-filters` excludes `rust/cleartext-logging`, which fires on
`aura quota`'s output. `ClaudeOauth` holds `access_token` and
`refresh_token` in the same struct as `subscription_type`, and CodeQL's
taint tracking is field-insensitive, so any field of it reaching a print
is flagged. Only the plan tier reaches stdout; the tokens are used solely
for the authorization header.

That second exclusion is repo-wide — CodeQL has no path-scoped rule
filter and Rust has no inline suppression (github/codeql#21637) — so it
also silences a genuine future leak. The file says so, and the per-alert
dismissals remain as the narrower control.
@Rfluid
Rfluid merged commit 4f51adc into main Sep 17, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants