antigravity-codes
Typed Rust interface for the Google Antigravity
agent runtime — the localharness binary that ships inside the
google-antigravity wheels.
Tested against google-antigravity 0.1.10.
Maturity warning: this crate is new and should be considered highly untested. Upstream is alpha (
0.1.x) and reserves protobuf extension ranges on its hottest messages, so expect churn. Wire captures that break the types are very welcome in issues.
What this wraps
google-antigravity on PyPI is a Python client for a compiled Go binary
called localharness, which is where the agent loop, the built-in tools, and
the model calls actually live. This crate is a client for that same binary — a
sibling of the Python SDK, not a binding to it. No Python at runtime.
Getting the binary
The harness is distributed only inside the platform wheels on PyPI. There
is no standalone release, so nothing will put it on your PATH for you:
Discovery order is $ANTIGRAVITY_HARNESS_PATH, then localharness on PATH,
then whatever you pass to HarnessOptions::binary.
Protocol
stdio is used only to bootstrap, then everything moves to a loopback WebSocket:
| Step | Transport | Payload |
|---|---|---|
| 1. Handshake | stdio, u32le-length-prefixed |
binary protobuf InputConfig → OutputConfig (port + API key) |
| 2. Connect | ws://127.0.0.1:{port}/ |
x-goog-api-key header |
| 3. Initialize | WebSocket | InitializeConversationEvent → InitializeConversationResponse |
| 4. Converse | WebSocket | InputEvent ↔ OutputEvent |
Everything after the handshake is protobuf's canonical JSON mapping:
camelCase members, 64-bit integers as strings, bytes as base64, enums as
value names.
A conversation must be configured with at least one model. A harness initialised with none exits immediately and drops the socket without an error frame — the crate surfaces the process's stderr in that case, because that is the only diagnosis available.
Two things that bite on a first run:
- Built-in tools are off unless enabled. A harness with none will answer
"I do not have file reading or command execution tools enabled" rather than
read your workspace.
HarnessOptionsdefaults toHarnessSideTools::read_only()— list, search, find, view, fetch — matching the reference Python SDK. Widen withHarnessSideTools::all()(shell and file writes) or narrow with::none(). - Free-tier quota is per model, and the
promodels have none. A request against one returns429 … limit: 0rather than an answer.gemini-flash-latestworks on a free key.
Usage
[]
= "0.1"
use ;
async
Answering the harness
A turn is not one-way. Depending on configuration the harness stops and waits for the client, and stays blocked until answered:
| Request | Raised when | Answered with |
|---|---|---|
ToolCall |
the model calls a tool declared via HarnessOptions::tool |
ToolResponse |
CallHookRequest |
a lifecycle hook registered via HarnessOptions::hook fires |
CallHookResponse |
PolicyDecisionRequest |
a dynamic policy rule needs adjudicating | PolicyDecisionResponse |
UserQuestionsRequest |
the agent asks the user something | UserQuestionsResponse |
| tool confirmation | a tool needs approval before it runs | ToolConfirmation |
Client answers all five from the Handlers you register. None of them arrive
unless the corresponding feature was configured, so an empty Handlers is fine
for plain chat. When one does arrive unhandled, the defaults keep the turn
moving: an unimplemented tool fails that one call, hooks return "no opinion",
policy returns NO_MATCH, questions are cancelled, and tool confirmations are
refused — silently approving would undo the control you asked for.
Clients
| Type | What it gives you |
|---|---|
RawClient |
The frames, unchanged. You drive the loop. |
Client |
Turn-oriented: streams assembled Steps and answers the harness for you. |
Feature Flags
| Feature | Description | WASM-compatible |
|---|---|---|
types |
Wire types and the handshake codec only (serde) | Yes |
async-client |
Async WebSocket client using tokio | No |
integration-tests |
Enables tests that need a real harness binary | No |
= { = "0.1", = false, = ["types"] }
Examples
# https://aistudio.google.com/apikey
All three take ANTIGRAVITY_MODEL to override the model, defaulting to
gemini-flash-latest.
Regenerating the protocol
The wire types are generated from the FileDescriptorProto embedded in the
wheel's localharness_pb2.py — not from the .proto files in the upstream
repo, which run ahead of what ships and are written in protobuf edition 2024
(unparseable by protoc < v31, and unsupported by prost).
Drift against the latest published wheel is checked nightly by
scripts/check_antigravity_schema_drift.py, which reads ~30 KB of the 37 MB
wheel using HTTP range requests.
Testing
# Against a real harness. Most of these need no API key: the harness runs the
# whole turn lifecycle locally and only fails when it calls the model.
ANTIGRAVITY_HARNESS_PATH=/.local/bin/localharness \
License
Apache-2.0