Recording_2026-05-21_20-07-01_optimized.mp4
The Google Antigravity SDK is a Rust library for building AI agents powered by Antigravity and Gemini. It provides a secure, scalable, and stateful infrastructure layer that abstracts the agentic loop, letting you focus on what your agent does rather than how it runs.
-
Add the SDK and tokio to your project:
cargo add antigravity-sdk-rust cargo add tokio --features full
-
Obtain the
localharnessbinary: The SDK relies on a compiled native Go runtime binary (localharness) to orchestrate agent operations. You have two options to install it:-
Option A: Without Python/pip (Recommended for Rust-only environments & web servers) Run the helper installer script to download and extract the binary directly from PyPI (wheels are standard ZIP files):
./scripts/install_harness.sh export ANTIGRAVITY_HARNESS_PATH="$(pwd)/bin/localharness"
-
Option B: Using Python/pip If Python is already installed on your development machine, simply run:
pip install google-antigravity
The Rust SDK will automatically locate the binary inside the Python package installation directory fallback.
⚠️ Platform Support Note: The upstreamgoogle-antigravitypackage on PyPI currently provides wheels for:- macOS Apple Silicon (arm64) ✅
- Linux x86_64 ✅
- Linux aarch64 ✅
The following platforms are not supported — no wheels are published:
- Intel (x86_64) macOS ❌
- Windows (x86_64 and ARM64) ❌
If you are on an unsupported platform, use a Linux VM/container or an Apple Silicon Mac instead.
-
Get started by setting your API key and running the hello_world example:
export GEMINI_API_KEY="your_api_key_here"
cargo run --example hello_worldThe Agent struct manages the full lifecycle — binary discovery, tool wiring, hook registration, and policy defaults.
use antigravity_sdk_rust::agent::Agent;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
// Configures and constructs the Agent using the builder pattern.
// PhantomData guarantees compile-time verification of safety policies.
let agent = Agent::builder()
.allow_all()
.build();
// Spawns the harness session and transitions the Agent state to Started.
let agent = agent.start().await?;
let response = agent.chat("Say 'Hello World!'").await?;
println!("Agent: {}", response.text);
agent.stop().await?;
Ok(())
}For full control over the connection lifecycle, use Conversation with a ConnectionStrategy directly. Conversation is a stateful session that accumulates step history:
use antigravity_sdk_rust::agent::AgentConfig;
use antigravity_sdk_rust::connection::AnyConnection;
use antigravity_sdk_rust::conversation::Conversation;
use antigravity_sdk_rust::local::LocalConnectionStrategy;
use antigravity_sdk_rust::tools::ToolRunner;
use antigravity_sdk_rust::types::GeminiConfig;
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let tool_runner = ToolRunner::new();
let strategy = LocalConnectionStrategy::new(
"localharness".to_string(), // path to harness
GeminiConfig {
enable_google_search: Some(true),
enable_url_context: Some(true),
..Default::default()
},
Default::default(),
None,
None,
vec![],
vec![],
Some(tool_runner),
None,
"my_conversation_id".to_string(),
vec![], // MCP servers
);
let connection = strategy.connect().await?;
let conversation = Conversation::new(AnyConnection::Local(Arc::new(connection)), None);
let response = conversation.chat_to_completion("What files are here?").await?;
println!("Agent: {}", response.text);
Ok(())
}Register Rust functions as custom tools:
use antigravity_sdk_rust::agent::{Agent, AgentConfig};
use antigravity_sdk_rust::tools::Tool;
use serde_json::Value;
struct WeatherTool;
impl Tool for WeatherTool {
fn name(&self) -> &str {
"get_weather"
}
fn description(&self) -> &str {
"Get the current weather for a city."
}
fn parameters_json_schema(&self) -> &str {
r#"{
"type": "object",
"properties": {
"city": {
"type": "string"
}
},
"required": ["city"]
}"#
}
async fn call(&self, args: Value) -> Result<Value, anyhow::Error> {
let city = args.get("city").and_then(|c| c.as_str()).unwrap_or("Tokyo");
Ok(serde_json::json!({ "weather": format!("It's sunny in {}", city) }))
}
}Control agent behavior with a declarative policy system:
use antigravity_sdk_rust::policy::{self, Policy};
let policies = vec![
policy::deny_all(), // Block all tools by default
policy::allow("VIEW_FILE"), // Allow reading/viewing files
];The SDK provides a typed error hierarchy via AntigravityError:
use antigravity_sdk_rust::error::AntigravityError;
// Three error categories:
// - AntigravityError::Connection(msg) — network/transport failures
// - AntigravityError::Execution(msg) — agent execution failures
// - AntigravityError::Validation { message, errors } — input validation failuresSend images, documents, audio, and video to the agent:
use antigravity_sdk_rust::types::{Content, Media, MimeType, ImageMime};
// Text-only (backwards compatible)
let response = agent.chat("What is 2+2?").await?;
// From file (auto-detects MIME type from extension)
let content = Content::from_file("photo.jpg", Some("Describe this")).unwrap();
// From raw bytes
let media = Media {
data: vec![0xFF, 0xD8, 0xFF],
mime_type: MimeType::Image(ImageMime::Jpeg),
description: Some("A photo".to_string()),
};
let content = Content::media(media);Supported formats: BMP, JPEG, PNG, WebP (images), PDF, JSON, CSS, CSV, HTML, JS, TXT, RTF, XML (documents), WAV, MP3, AAC, OGG, FLAC, Opus, MPEG, M4A, L16 (audio), 3GPP, AVI, MP4, MPEG, MOV, WebM, WMV, FLV (video).
The full hook lifecycle is available:
use antigravity_sdk_rust::hooks::Hook;
use antigravity_sdk_rust::types::{HookResult, ToolCall, ToolResult, ChatResponse};
struct MyHook;
impl Hook for MyHook {
// Session lifecycle
async fn on_session_start(&self) -> Result<(), anyhow::Error> { Ok(()) }
async fn on_session_end(&self) -> Result<(), anyhow::Error> { Ok(()) }
// Turn lifecycle
async fn pre_turn(&self) -> Result<HookResult, anyhow::Error> {
Ok(HookResult { allow: true, message: String::new() })
}
async fn post_turn(&self, _response: &ChatResponse) -> Result<(), anyhow::Error> { Ok(()) }
// Tool lifecycle
async fn pre_tool_call(&self, _call: &ToolCall) -> Result<HookResult, anyhow::Error> {
Ok(HookResult { allow: true, message: String::new() })
}
async fn post_tool_call(&self, _result: &ToolResult) -> Result<(), anyhow::Error> { Ok(()) }
// History compaction
async fn on_compaction(&self, _summary: &str) -> Result<(), anyhow::Error> { Ok(()) }
}Hooks can share state across lifecycle events via a parent-chaining key-value store:
use antigravity_sdk_rust::context::HookContext;
use std::sync::Arc;
// Session context (root)
let session = Arc::new(HookContext::new());
session.set("user_id", "u-123");
// Turn context (inherits session)
let turn = Arc::new(HookContext::child(session));
turn.set("turn_count", 1i32);
// get() walks up the chain:
assert_eq!(turn.get::<String>("user_id"), Some("u-123".to_string()));Tools can opt-in to receiving a ToolContext for session state and agent communication:
use antigravity_sdk_rust::tools::Tool;
use antigravity_sdk_rust::tool_context::ToolContext;
use serde_json::Value;
struct StatefulTool;
impl Tool for StatefulTool {
fn name(&self) -> &str { "stateful_tool" }
fn description(&self) -> &str { "A tool with session state" }
fn parameters_json_schema(&self) -> &str { r#"{"type":"object"}"# }
fn needs_context(&self) -> bool { true } // Opt-in
async fn call(&self, args: Value) -> Result<Value, anyhow::Error> {
Ok(Value::Null) // Fallback
}
async fn call_with_context(
&self,
args: Value,
ctx: &ToolContext,
) -> Result<Value, anyhow::Error> {
// Access session state
let count: i32 = ctx.get_state("call_count").unwrap_or(0);
ctx.set_state("call_count", count + 1);
Ok(serde_json::json!({ "calls": count + 1 }))
}
}Convenience factories for common trigger patterns:
use antigravity_sdk_rust::trigger_helpers::every;
use std::time::Duration;
// Periodic trigger — fires every 30 seconds
let heartbeat = every(Duration::from_secs(30), "check_status");Built-in REPL for conversational agents:
use antigravity_sdk_rust::agent::Agent;
use antigravity_sdk_rust::interactive::run_interactive_loop;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let agent = Agent::builder().allow_all().build().start().await?;
run_interactive_loop(&agent).await?;
agent.stop().await?;
Ok(())
}Connect to external MCP servers and expose their tools to the agent:
use antigravity_sdk_rust::agent::Agent;
use antigravity_sdk_rust::types::McpServerConfig;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let agent = Agent::builder()
.mcp_server(McpServerConfig::Stdio {
name: "my_server".to_string(),
command: "npx".to_string(),
args: vec!["my-mcp-server".to_string()],
enabled_tools: None,
disabled_tools: None,
})
.allow_all()
.build();
let agent = agent.start().await?;
let response = agent.chat("Use the MCP tools to help me.").await?;
println!("{}", response.text);
agent.stop().await?;
Ok(())
}Three transport types are supported:
McpServerConfig::Stdio— launch a local subprocess (e.g.,npx,uvx)McpServerConfig::Sse— connect via Server-Sent EventsMcpServerConfig::Http— connect via standard HTTP with configurable timeouts
Each variant supports enabled_tools / disabled_tools for fine-grained tool filtering.
For more complex use cases, stream internal model reasoning/thinking and intercept tool call dispatches in real-time using StreamChunk:
use antigravity_sdk_rust::agent::Agent;
use antigravity_sdk_rust::types::StreamChunk;
use futures_util::StreamExt;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let agent = Agent::builder().allow_all().build().start().await?;
let conversation = agent.conversation();
let mut stream = conversation.chat("Explain quantum computing").await?;
while let Some(chunk_res) = stream.next().await {
match chunk_res? {
// 1. Stream reasoning/thinking deltas
StreamChunk::Thought { text, .. } => {
eprint!("💭 {}", text); // show thinking in grey/stderr
}
// 2. Stream response text tokens
StreamChunk::Text { text, .. } => {
print!("{}", text);
}
// 3. Stream strongly-typed ToolCall events
StreamChunk::ToolCall(call) => {
println!("\n🔧 Executing: {} (args: {})", call.name, call.args);
}
}
}
agent.stop().await?;
Ok(())
}The SDK supports server-side Google Search grounding and provides a client-side search fallback:
- Google Search Grounding: Enable Gemini's native Google Search grounding tool by setting
enable_google_search: Some(true)inGeminiConfig. This enables the model to natively perform search queries server-side to return up-to-date online information. - Web Search Fallback: If the model decides to invoke a tool call named
google_searchorweb_searchand no custom search tool has been registered withToolRunner, the SDK automatically runs a built-in search fallback. On native platforms, it spawns apython3subprocess to scrape and parse DuckDuckGo search results. On WASM platforms, it returns an empty result block indicating search is not available.
When custom client-side tools are executed, the SDK connection dynamically generates and streams synthetic Step updates:
- Enters
ACTIVEstate before executing the tool, allowing the user interface to render an active tool execution card. - Emits
DONE(on success) orERROR(on failure) once execution completes, displaying the results/error on the timeline. - To prevent collision with step indices assigned by the harness, client-side tool steps use synthetic indices starting at
50,000.
The SDK provides two patterns for building web applications with AI agents:
For standard web servers (VPS, Docker, bare metal). The Agent runs in-process with full SDK features.
cd examples/leptos_axum
echo "GEMINI_API_KEY=your-key" > .env
cargo leptos serve
# Open http://localhost:3000For edge/serverless deployments on Spin (formerly Fermyon Spin, now under Akamai). Since Spin components cannot make outbound TCP/WebSocket connections directly, this target runs in Sidecar Mode, where the component communicates via HTTP with a native runner process (agent_server):
# Terminal 1: Start the agent sidecar
cd examples/agent_server
GEMINI_API_KEY=your-key cargo run
# Terminal 2: Start Spin
cd examples/leptos_ssr_axum
spin build --up
# Open http://localhost:3000See
examples/README.mdfor detailed architecture diagrams and configuration options.
The SDK supports compiling directly to the wasm32-wasip1 WebAssembly target. This enables running agents inside sandboxed Wasm runtimes (such as Spin, Wasmtime, or Wasmer) that support WASI network sockets.
In standard native environments, the SDK spawns the localharness binary as a local subprocess. Since process spawning is not supported inside WebAssembly sandboxes, when targeting WASM, the SDK switches to Direct Network Connection Mode via WasmConnectionStrategy and WasmConnection:
- Host Server: A host-side
localharnessWebSocket server is run on the machine/VM (configured viaANTIGRAVITY_HARNESS_HOSTandANTIGRAVITY_HARNESS_PORT). - WebSocket Handshake: The
WasmConnectionStrategyopens a TCP socket to the harness server, initiates a WebSocket client handshake with the Gemini API key passed via thex-goog-api-keyheader, and upgrades it to a bi-directional stream. - Session Handshake: The client sends an
InitializeConversationEventcontaining theHarnessConfig(tools, workspaces, and system instructions) to start the stateful agent trajectory. - Asynchronous Event Loop: Two asynchronous tasks are spawned under the WASM event loop:
- Reader Loop: Continuously reads WebSocket frames, parses incoming
OutputEventmessages (includingStepUpdateandTrajectoryStateUpdate), maps them to SDKSteptypes, dispatches lifecycle hooks, and routes tool calls. - Sender Loop: Buffers and sends outgoing
InputEventmessages (user messages, tool responses, question replies, etc.) back to the harness.
- Reader Loop: Continuously reads WebSocket frames, parses incoming
The mock and unit test suite in src/wasm.rs validates WASM-specific functionality (connection lifecycle, event loops, tool call/result extraction, and idle state transitions). Since it uses a standard WebSocket framework, you can run the suite natively on your host machine without a full WebAssembly runner:
# Run all unit tests including the WASM mock connection tests
cargo test wasm::testsThis mock test suite programmatically binds a local WebSocket server to a dynamically allocated free port, completes the initialization handshake, feeds mock trajectory state updates/step updates, and asserts that WasmConnection correctly receives messages, invokes callback hooks, and cleanly terminates the stream upon transitioning to the idle state.
The SDK has been fully refactored to use native async traits (stable since Rust 1.75 / Rust 2024), completely removing the dependency on the #[async_trait] macro.
- Native Async Traits: Custom implementations of
Connection,Hook,Tool, andTriggernow use standard async function definitions returningimpl Future<Output = ...> + Sendwhere necessary to maintain compile-time thread safety bounds. - Dynamic Dispatch via Blanket Impls: Because native async traits are not directly object-safe (
dyn Traitcompatible) without manual workarounds, the SDK defines companion traitsDynConnection,DynHook,DynTool, andDynTriggerwhich are object-safe and automatically implemented via blanket implementations for any type implementing the base trait. This allows clean, zero-overhead dynamic dispatch (e.g.Arc<dyn DynHook>).
This project uses just to manage development tasks.
- Help / List Commands: List all available
justrecipes.just # or just help
- Run SDK Examples: Start any of the SDK examples by directory or file name (handles both file and folder examples, converting dashes to underscores automatically).
# Run the hello_world.rs console example just example hello_world # Run the agent_server HTTP sidecar example just example agent_server # Run the leptos_ssr_axum edge web example just example leptos_ssr_axum
- Check Code Quality: Run all style, lint, and test checks.
just check
- Install Local Harness: Download and configure the required
localharnessbinary.just install
- Bump version & Release tag: Bump the package version, update Cargo.lock, commit using
--no-verify(skipping git hooks), and tag the release.# Auto-bump patch version (e.g., 0.1.0 -> 0.1.1) just version # Or force/override with a specific version just version 0.2.0
- Publish to Crates.io: Manually publish the package to crates.io (runs
just checkfirst).just publish
For more detailed documentation on specific components, see:
- Agent — High-level, batteries-included entry point.
- Connections — Transport and backend abstraction.
- Conversation — Stateful session management.
- Hooks — Agent lifecycle interception and policies.
- MCP — Model Context Protocol integration.
- Tools — In-process tool execution.
- Triggers — Background tasks and external events.
For more information, see ARCHITECTURE.md.
This project is licensed under the MIT License.