Expand description
§agy-bridge
Build LLM agents in Rust — with tools, hooks, streaming, and multimodal input.
Rust bridge for the Google Antigravity SDK via PyO3.
Rust’s compile-time checks make it a natural fit for vibe coding agents — schema mismatches, missing parameters, and typos in tool definitions (via
#[llm_tool]) are caught at build time, not at runtime.
§Installation
Add agy-bridge to your Cargo.toml:
[dependencies]
agy-bridge = "0.1.4"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }Install the Python SDK:
pip install google-antigravity watchfiles
watchfilesis only needed for file-change triggers; timer triggers work without it.
§Quick Start
use agy_bridge::AgyBridge;
#[tokio::main]
async fn main() -> Result<(), agy_bridge::error::Error> {
agy_bridge::load_dotenv();
let bridge = AgyBridge::builder().build()?;
let agent = bridge.default_agent().await?;
// Send a message and get the full reply text.
let text = agent
.chat("Reply with 'Hello!' and nothing else.")
.await?
.text()
.await?;
println!("{text}");
agent.shutdown().await?;
Ok(())
}§Streaming Responses
use agy_bridge::{AgyBridge, config::AgentConfig};
#[tokio::main]
async fn main() -> Result<(), agy_bridge::error::Error> {
agy_bridge::load_dotenv();
let bridge = AgyBridge::builder().build()?;
// bridge.agent() returns an AgentBuilder — chain .tools() / .hooks()
// before .await, or .await directly for a bare agent.
let agent = bridge
.agent(
AgentConfig::builder()
.system_instructions("You are a poet.")
.build(),
)
.await?;
let mut response = agent.chat("Write a short poem about space.").await?;
if let Some(mut stream) = response.take_text_stream() {
while let Some(chunk) = stream.recv().await {
print!("{chunk}");
}
}
println!();
agent.shutdown().await?;
Ok(())
}§Features
§Multimodal Content
Pass text, images, audio, video, and documents in a single chat turn:
use agy_bridge::{
AgyBridge,
content::{Content, ContentPrimitive, Image},
};
#[tokio::main]
async fn main() -> Result<(), agy_bridge::error::Error> {
agy_bridge::load_dotenv();
let bridge = AgyBridge::builder().build()?;
let agent = bridge.default_agent().await?;
// Load an image from a file path (auto-detects MIME type):
let image = Image::from_file("blank.png")?;
let content = Content::Multi {
parts: vec![
ContentPrimitive::Text { text: "Describe this image.".into() },
ContentPrimitive::Image(image),
],
};
let text = agent.chat(content).await?.text().await?;
println!("{text}");
agent.shutdown().await?;
Ok(())
}§Custom Tools
Define tools with the #[llm_tool] proc macro — doc comments become descriptions:
use agy_bridge::{AgyBridge, config::AgentConfig, prelude::*, tools::ToolRegistry};
/// Gets the current weather for a city.
#[llm_tool]
fn get_weather(
/// The city to look up.
city: &str,
) -> Result<String, String> {
Ok(format!("It's sunny in {city}."))
}
#[tokio::main]
async fn main() -> Result<(), agy_bridge::error::Error> {
agy_bridge::load_dotenv();
let bridge = AgyBridge::builder().build()?;
let mut registry = ToolRegistry::new();
registry.register(GetWeather);
let agent = bridge
.agent(AgentConfig::builder().build())
.tools(registry)
.await?;
let text = agent.chat("What's the weather in Tokyo?").await?.text().await?;
println!("{text}");
agent.shutdown().await?;
Ok(())
}For full control, implement the RustTool trait directly:
use agy_bridge::tools::{JsonSchema, RustTool, ToolContext, ToolError, ToolOutput, ToolRegistry};
use serde::Deserialize;
#[derive(Deserialize, JsonSchema)]
struct SearchParams {
/// The search query string.
query: String,
}
struct SearchTool;
impl RustTool for SearchTool {
type Params = SearchParams;
const NAME: &'static str = "search";
const DESCRIPTION: &'static str = "Search a knowledge base";
async fn call(&self, params: Self::Params, _ctx: &ToolContext) -> Result<ToolOutput, ToolError> {
Ok(format!("Results for: {}", params.query).into())
}
}
let registry = ToolRegistry::new().with_tool(SearchTool);
assert_eq!(registry.definitions().len(), 1);§MCP Integration
Connect external MCP servers:
use agy_bridge::config::{AgentConfig, McpServer};
let server = McpServer::stdio("npx")
.args([
"-y",
"@modelcontextprotocol/server-postgres",
"postgresql://postgres:postgres@localhost:5432/postgres",
])
.build();
let config = AgentConfig::builder().mcp_servers([server]).build();
assert_eq!(config.mcp_servers.len(), 1);§Hooks and Policies
Control agent behavior with hooks and a declarative policy system:
use agy_bridge::{
AgyBridge,
config::AgentConfig,
hooks::{HookResult, Hooks},
};
#[tokio::main]
async fn main() -> Result<(), agy_bridge::error::Error> {
agy_bridge::load_dotenv();
let bridge = AgyBridge::builder().build()?;
// 1. Register lifecycle hooks
let mut hooks = Hooks::new();
hooks.on_pre_turn("turn_logger", |ctx| {
println!("[turn {}] {}", ctx.turn_number, ctx.prompt);
});
hooks.on_pre_tool_call_decide("safety_gate", |ctx| {
if ctx.tool_name == "dangerous_tool" {
HookResult::deny("blocked by policy")
} else {
HookResult::allow()
}
});
// 2. Create the agent configuration
let config = AgentConfig::builder().build();
// 3. Pass hooks to the agent builder
let agent = bridge
.agent(config)
.hooks(hooks)
.await?;
let text = agent.chat("What is the capital of Japan?").await?.text().await?;
println!("{text}");
agent.shutdown().await?;
Ok(())
}use agy_bridge::policies::{PolicyRule, PolicySet};
let mut policies = PolicySet::new();
policies.push(PolicyRule::allow("view_file")).unwrap();
policies.push(PolicyRule::deny("run_command")).unwrap();
policies.push(PolicyRule::DenyAll).unwrap();
assert!(policies.evaluate("view_file").is_allowed());
assert!(policies.evaluate("run_command").is_denied());
assert!(policies.evaluate("unknown_tool").is_denied());§Triggers
Run background tasks that react to timers or file changes:
use agy_bridge::{
AgyBridge,
config::AgentConfig,
triggers::{TriggerConfig, TriggerEntry},
};
#[tokio::main]
async fn main() -> Result<(), agy_bridge::error::Error> {
agy_bridge::load_dotenv();
let bridge = AgyBridge::builder().build()?;
let periodic = TriggerEntry {
name: "poll_status".into(),
config: TriggerConfig::every_secs(30),
message_template: "Check deployment status".into(),
};
let file_watch = TriggerEntry {
name: "watch_workspace".into(),
config: TriggerConfig::on_file_change(std::env::current_dir()?),
message_template: "Files changed: {changes}".into(),
};
let config = AgentConfig::builder()
.triggers(vec![periodic, file_watch])
.build();
let agent = bridge.agent(config).await?;
// The agent will now automatically run triggers in the background.
let text = agent.chat("Hello!").await?.text().await?;
println!("{text}");
agent.shutdown().await?;
Ok(())
}Note:
on_file_changetriggers require thewatchfilesPython package (pip install watchfiles). Timer triggers (every) work without extra dependencies.
§Subagents
Spawn child agents that share the parent’s runtime:
use agy_bridge::{AgyBridge, config::AgentConfig};
#[tokio::main]
async fn main() -> Result<(), agy_bridge::error::Error> {
agy_bridge::load_dotenv();
let bridge = AgyBridge::builder().build()?;
let parent = bridge.agent(
AgentConfig::builder()
.system_instructions("You are a coordinator.")
.build(),
).await?;
let child = parent.spawn_subagent(
AgentConfig::builder()
.system_instructions("You are a math specialist.")
.model("gemini-3.5-flash")
.build(),
None,
).await?;
let text = child.chat("What is 17 * 23?").await?.text().await?;
println!("{text}");
child.shutdown().await?;
parent.shutdown().await?;
Ok(())
}§Examples
The examples/ directory contains runnable programs for every feature:
§Getting Started
| Example | Description |
|---|---|
hello_world | Minimal agent — create, prompt, print |
streaming | Stream text tokens as they arrive |
custom_tools | #[llm_tool] proc macro |
multimodal | Text + image input |
mcp_tools | MCP server integration |
structured_output | JSON schema–constrained responses |
hooks | Lifecycle hooks |
policies | Declarative tool access control |
triggers | Periodic and file-change triggers |
subagents | Parent/child agent spawning |
human_in_the_loop | Human-in-the-loop chat patterns |
persistence | Conversation persistence |
observability | Tracing and usage metadata |
error_handler | Structured error handling patterns |
autonomous_shell | Autonomous shell command execution |
persona_config | Custom persona and model configuration |
agent_skills | Agent skill registration |
app_data_dir_override | Custom app data directory |
§Deep Dives
| Example | Description |
|---|---|
async_chat | Advanced async chat patterns |
round_based_chat | Multi-round structured conversations |
agent_middleware | Hook-based middleware pipeline |
host_tool_hooks | Host-side tool interception hooks |
interactive_cli | Multi-turn CLI chat application |
multimodal_pipeline | Multi-stage multimodal processing |
doc_maintenance_agent | Documentation maintenance agent |
docstring_maintenance_agent | Code docstring maintenance agent |
cargo run --example getting_started_hello_world§License
Licensed under either of:
at your option.
This is not a Google product. Use at your own risk.
agy-bridge: Standalone reusable PyO3 bridge for the Google Antigravity SDK.
Re-exports§
pub use config::AgentConfig;pub use config::BuiltinTools;pub use config::CapabilitiesConfig;pub use config::GeminiConfig;pub use config::LocalAgentConfig;pub use config::McpServer;pub use config::McpSseServer;pub use config::McpStdioServer;pub use config::McpStreamableHttpServer;pub use config::SystemInstructions;pub use content::Audio;pub use content::Content;pub use content::ContentPrimitive;pub use content::Document;pub use content::Image;pub use content::Video;pub use error::Error;pub use hooks::HookCallback;pub use hooks::HookEntry;pub use hooks::HookPoint;pub use hooks::HookResult;pub use hooks::HookSet;pub use hooks::Hooks;pub use policies::AskUserHandler;pub use policies::PolicyDecision;pub use policies::PolicyRule;pub use policies::PolicySet;pub use runtime::RuntimeConfig;pub use streaming::ChatResponseHandle;pub use streaming::ChatResult;pub use streaming::ResponseEvent;pub use streaming::StreamChunk;pub use triggers::TriggerConfig;pub use triggers::TriggerEntry;pub use types::ConversationMessage;pub use types::MessageRole;pub use types::Step;pub use types::UsageMetadata;
Modules§
- agent
- Agent lifecycle management: creation, chat, shutdown. Agent lifecycle management for the Antigravity SDK bridge.
- config
- Configuration types for agents, models, capabilities, and MCP servers. Agent configuration bridge types.
- content
- Multimodal content types for chat input (text, image, document, audio, video). Multimodal content types for chat input (text, image, document, audio, video).
- error
- Error types for the bridge. Bridge error types and helpers for mapping Python exceptions to Rust errors.
- hooks
- Pre/post-turn and tool-call lifecycle hooks. Hook bridge for the Antigravity SDK.
- policies
- Policy rules for tool-call filtering and workspace scoping. Policy bridge for the Antigravity SDK.
- prelude
- Convenience prelude — pull in everything you need with a single glob import.
- quota
- Quota tracking and backoff state. Quota tracking and exponential backoff with jitter.
- runtime
- Python runtime bridge: command dispatch over a dedicated thread. Python runtime manager: owns a dedicated Python thread with an asyncio event loop.
- streaming
- Streaming response channels for text, thought, and tool-call events. Streaming response bridge for the Antigravity SDK.
- tools
- Custom Rust tool dispatch and definition types. Custom tool registration for the Antigravity SDK bridge.
- triggers
- Event-driven trigger definitions. Trigger configuration types for the Python Antigravity SDK.
- types
- Shared domain types (messages, steps, usage metadata). Core types for the agent SDK bridge.
Structs§
- Agent
Builder - Builder for creating an
Agenton anAgyBridge. - AgyBridge
- Primary entry point for the Antigravity bridge.
- AgyBridge
Builder - Builder for constructing an
AgyBridgeinstance. - Tool
Context - Context passed to Rust tools during dispatch, mirroring the Python SDK’s
ToolContext. - Tool
Definition - Describes a custom tool that can be registered with an agent.
- Tool
Error - An error returned from a tool execution. The error message is sent back to the model as the tool’s error response. Structured metadata can be attached for hooks and logging — it is not sent to the model.
- Tool
Output - The return value of a Rust tool execution.
- Tool
Registry
Traits§
- Rust
Tool - A custom tool implemented entirely in Rust with strongly-typed parameters.
Functions§
- load_
dotenv - Load environment variables from a
.envfile into the process environment.
Type Aliases§
- Agent
- Convenience alias for an agent backed by the bridge’s runtime.
Attribute Macros§
- llm_
tool - Re-export the
#[llm_tool]proc-macro so users only needagy_bridgein their dependency list. Transforms a function into aRustToolimplementation.