Welcome to the Antigravity SDK for Java, an unofficial Java port of the Python-based Antigravity SDK!
This library allows you to build, configure, host, and execute powerful AI agents in Java, bridging the gap for enterprise Java developers who want to harness the power of Antigravity.
Note
This entire project was autonomously generated and implemented using Antigravity under the guidance of a human developer (me!).
The official Antigravity SDK (currently available in Python) operates by wrapping a core, pre-compiled Go binary (localharness) that manages the underlying agent interactions, state, and websocket communications.
To build this Java SDK, I reverse-engineered the Python implementation's internal gRPC and WebSocket protocol layer. I extract the appropriate native Go binary from the upstream Python wheels at build time, spawn it as a subprocess, and seamlessly orchestrate the exact same agent capabilities in native Java.
This SDK achieves full feature parity with the Python SDK. Below are examples of what you can do.
Configure an agent with a system prompt and execute a turn.
AgentConfig config = AgentConfig.builder()
.instructions("You are a helpful assistant.")
.build();
try (Agent agent = new Agent(config)) {
AgentResponse response = agent.chat("Hello, who are you?").get(120, TimeUnit.SECONDS);
System.out.println(response.text());
}Stream tokens as they are generated by the model. The SDK supports both simple callback functions and Java 9 Reactive Streams (Flow.Publisher).
AgentConfig config = AgentConfig.builder()
.instructions("Write a long story.")
.build();
try (Agent agent = new Agent(config)) {
CompletableFuture<AgentResponse> future = agent.chatStream("Tell me a story about a brave knight.", chunk -> {
System.out.print(chunk.textDelta());
});
future.get(120, TimeUnit.SECONDS);
}Return a standard java.util.concurrent.Flow.Publisher to integrate natively with modern reactive frameworks (like Spring WebFlux, Project Reactor, or RxJava 3).
try (Agent agent = new Agent(config)) {
Flow.Publisher<AgentResponseChunk> publisher = agent.chatPublisher("Tell me a story.");
// Example 1: Project Reactor / Spring WebFlux
// Flux<AgentResponseChunk> flux = reactor.core.publisher.Flux.from(publisher);
// Example 2: RxJava 3
// Flowable<AgentResponseChunk> flowable = io.reactivex.rxjava3.core.Flowable.fromPublisher(publisher);
// Example 3: Standard Java 9+ Flow.Subscriber
publisher.subscribe(new Flow.Subscriber<>() {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
subscription.request(Long.MAX_VALUE);
}
@Override
public void onNext(AgentResponseChunk item) {
System.out.print(item.textDelta());
}
@Override
public void onError(Throwable throwable) {
throwable.printStackTrace();
}
@Override
public void onComplete() {
System.out.println("\nDone!");
}
});
}For complex UIs, you can extract streams of just the model's internal thoughts, or intercept tool dispatch events in real-time using agent.streamChat():
import io.github.glaforge.antigravity.AgentStream;
AgentStream stream = agent.streamChat("Think step-by-step and calculate the weather.");
// 1. Stream just the internal reasoning/thinking deltas
Flux.from(stream.thoughts())
.subscribe(thought -> System.out.println("Thinking: " + thought));
// 2. Stream strongly-typed ToolCall events
Flux.from(stream.toolCalls())
.subscribe(call -> System.out.println("Executing tool: " + call.name()));
// Wait for the final complete response
AgentResponse response = stream.result().get(120, TimeUnit.SECONDS);(You can also access the combined chunks() publisher directly from the AgentStream object).
Provide custom tools that the agent can execute during its turn. The SDK provides two ways to do this:
The easiest way is to use the @Tool and @Param annotations. The SDK will automatically generate the required JSON Schema for the LLM, seamlessly parsing primitives and complex POJO/Record arguments.
public class MyToolbox {
@Tool(name = "get_weather", description = "Get the weather for a location.")
public String getWeather(
@Param(name = "location", description = "The city and state, e.g. San Francisco, CA") String location
) {
return "The weather in " + location + " is sunny.";
}
}
AgentConfig config = AgentConfig.builder()
.instructions("You can fetch the weather.")
.addTool(new MyToolbox())
.build();For advanced use-cases where tools need to be defined at runtime without classes, you can implement the DynamicTool interface directly and provide the raw JSON Schema:
AgentConfig config = AgentConfig.builder()
.instructions("You can fetch the weather.")
.addTool(new DynamicTool() {
@Override
public String getName() { return "get_weather"; }
// A simple record to define our parameters schema
record WeatherParams(String location) {}
public ToolDefinition getDefinition() {
return ToolDefinition.builder()
.name("get_weather")
.description("Get the weather for a location.")
.parametersSchema(WeatherParams.class) // Auto-generates the schema!
.build();
}
@Override
public Object execute(JsonNode arguments) {
String location = arguments.get("location").asText();
return "The weather in " + location + " is sunny.";
}
})
.build();Extend your agent with complex capabilities by loading file-based skills conforming to the open Agent Skills specification.
AgentConfig config = AgentConfig.builder()
.instructions("You are a specialized developer.")
.addSkillPath("/path/to/my-agent-skill")
.addSkillPath("/path/to/another-skill")
.build();Tip
Bundled SDK Agent Skill: This repository includes an official, open-specification Agent Skill for the Antigravity SDK for Java under skills/antigravity-sdk-java/.
You can load this skill into your agents (.addSkillPath("skills/antigravity-sdk-java")) or register it with AI coding tools (such as the Antigravity CLI, Cursor, Windsurf, or Claude Code) to provide your AI assistants with native expertise on configuring, hosting, and executing agents with this SDK!
Restrict which tools the agent is allowed to execute using Security Policies. Policies are evaluated strictly in the order they are added.
AgentConfig config = AgentConfig.builder()
.instructions("You are restricted from running dangerous commands.")
.addPolicy(Policies.denyAll()) // Blocks all tool executions by default
.build();This example demonstrates how to build a robust security posture by explicitly denying dangerous operations, interactively prompting the user for sensitive operations, allowing safe tools, and blocking everything else as a fallback.
import java.util.Scanner;
AgentConfig config = AgentConfig.builder()
.instructions("You are a secure agent operating in a restricted environment.")
// 1. Specific Denylist rules (e.g., block dangerous 'rm -rf' commands)
.addPolicy(Policies.denyIf((toolName, argsNode) -> {
if ("run_command".equals(toolName) && argsNode.has("command_line")) {
String cmd = argsNode.get("command_line").asText();
return cmd.contains("rm -rf"); // Return true to DENY
}
return false; // PASS
}))
// 2. Interactive confirmation rules using askUser for sensitive files
.addPolicy(Policies.askUser((toolName, argsNode) -> {
if ("view_file".equals(toolName) && argsNode.has("path")) {
String path = argsNode.get("path").asText();
if (path.contains("production.key")) {
System.out.println("⚠️ Agent wants to read production key! Allow? (y/n)");
Scanner scanner = new Scanner(System.in);
return scanner.nextLine().trim().equalsIgnoreCase("y");
}
}
return true; // Auto-allow other read_file attempts that reach this policy
}))
// 3. Specific Allowlist rules (explicitly allow safe tools)
.addPolicy(Policies.allowTools("list_dir", "get_weather"))
// 4. Deny by Default posture (block all other tools not explicitly handled above)
.addPolicy(Policies.denyAll())
.build();Note
Protobuf Policy Engine Alignment: The underlying antigravity-sdk-protocol module maintains full wire compatibility with upstream PolicyConfig, PolicyRule, PolicyDecision, and PolicyEvaluationOutcome Protobuf structures for low-level policy negotiation over localharness WebSockets.
Hook into the agent's execution lifecycle to monitor, intercept, or modify interactions. The SDK natively enforces the three core hook categories:
- Inspect Hooks (
PostTurnHook, etc.): Read-Only, Non-Blocking. Used for logging, audit trails, and metrics. - Decide Hooks (
PreTurnHook, etc.): Read-Only, Blocking. Used for custom approval/denial logic and policies. - Transform Hooks (
OnToolErrorHook, etc.): Modifying, Blocking. Used for sanitizing data in transit or recovering from tool errors.
AgentConfig config = AgentConfig.builder()
.instructions("You are an observed agent.")
.addPreTurnHook((prompt, context) -> {
System.out.println("Starting turn with prompt: " + prompt);
return CompletableFuture.completedFuture(HookResult.allowed());
})
// Intercept and sanitize tool arguments before execution
.addPreToolCallDecideHook((toolCall, context) -> {
if ("get_weather".equals(toolCall.name())) {
HookResult result = HookResult.builder()
.allow(true)
.modifiedArgumentsJson("{\"location\": \"Paris, France\"}")
.build();
return CompletableFuture.completedFuture(result);
}
return CompletableFuture.completedFuture(HookResult.allowed());
})
// Intercept interactions (like asking the user a question)
.addOnInteractionHook(request -> {
System.out.println("Agent asked: " + request.questions().get(0).questionText());
// Programmatically answer the agent's question
InteractionAnswer answer = InteractionAnswer.builder()
.freeformResponse("My answer to your question is...")
.build();
return CompletableFuture.completedFuture(List.of(answer));
})
.build();Seamlessly connect to Model Context Protocol (MCP) servers to expand your agent's capabilities dynamically.
// Standard I/O MCP Server
McpServerConfig stdioConfig = McpServerConfig.stdio(
"npx",
List.of("-y", "@modelcontextprotocol/server-sqlite", "test.db")
);
// Server-Sent Events (SSE) MCP Server
McpServerConfig sseConfig = McpServerConfig.sse("http://localhost:8080/sse");
// Streamable HTTP / HTTP MCP Server
McpServerConfig httpConfig = McpServerConfig.streamableHttp("http://localhost:8080/mcp");
AgentConfig config = AgentConfig.builder()
.addMcpServer(stdioConfig)
.addMcpServer(sseConfig)
.addMcpServer(httpConfig)
.build();You can configure background tasks to run periodically and inject new information into the agent's context asynchronously.
import io.github.glaforge.antigravity.triggers.Triggers;
import java.util.concurrent.TimeUnit;
AgentConfig config = AgentConfig.builder()
.instructions("If you are given a deployment status, notify the user.")
.addTrigger(Triggers.every(60, TimeUnit.SECONDS, ctx -> {
// This will run in the background every 60 seconds
ctx.fireTrigger("Check the deployment status.");
}))
.build();
try (Agent agent = new Agent(config)) {
// The trigger will run in the background while the session is active.
agent.chat("Start watching the deployment.").get(120, TimeUnit.SECONDS);
} // Trigger is automatically stopped when the agent closesPass images, audio, and video directly to the agent.
AgentResponse response = agent.chat(
AgentInput.Text.of("What is in this image?"),
AgentInput.Image.fromFile(Path.of("image.png"))
).get(120, TimeUnit.SECONDS);Force the agent to respond in a specific JSON schema format.
public record Person(String name) {}
AgentConfig config = AgentConfig.builder()
.instructions("Extract the person's name and return it in the provided schema. Do not output anything else.")
.finishToolSchema(Person.class)
.build();
try (Agent agent = new Agent(config)) {
AgentResponse response = agent.chat("Extract: Alice").get(120, TimeUnit.SECONDS);
// The response now safely parses into your strongly typed Record!
Person parsedPerson = response.getStructuredOutput(Person.class);
System.out.println(parsedPerson.name());
}Agents can spawn and delegate tasks to subagents.
AgentConfig config = AgentConfig.builder()
.capabilities(CapabilitiesConfig.builder().enableSubagents(true).build())
.build();Agents support resuming past sessions using the conversationId.
AgentConfig config1 = AgentConfig.builder().build();
String conversationId;
try (Agent agent = new Agent(config1)) {
agent.chat("My name is Guillaume.").get(120, TimeUnit.SECONDS);
conversationId = agent.getConversationId();
}
AgentConfig config2 = AgentConfig.builder()
.conversationId(conversationId)
.build();
try (Agent agent = new Agent(config2)) {
AgentResponse response = agent.chat("What is my name?").get(120, TimeUnit.SECONDS);
System.out.println(response.text()); // Outputs "Guillaume"
}You can extract token usage metrics for the latest turn or for the entire session.
AgentResponse response = agent.chat("Calculate the distance to the moon.").get(120, TimeUnit.SECONDS);
UsageMetadata usage = response.usageMetadata();
System.out.println("Input tokens: " + usage.promptTokenCount());
System.out.println("Output tokens: " + usage.candidatesTokenCount());
System.out.println("Total tokens: " + usage.totalTokenCount());Agents can be cancelled during execution. The SDK will cleanly interrupt the underlying Go harness and throw an AgentCancelledException.
AgentConfig config = AgentConfig.builder().build();
try (Agent agent = new Agent(config)) {
// Start a long-running request
CompletableFuture<AgentResponse> future = agent.chat("Write a very long story.");
// Cancel the agent immediately from another thread
agent.cancel();
try {
future.get(120, TimeUnit.SECONDS);
} catch (ExecutionException e) {
if (e.getCause() instanceof AgentCancelledException) {
System.out.println("Agent was cancelled successfully!");
}
}
}The Antigravity SDK natively supports CLI-style slash commands directly in the chat interface. Commands like /help or /clear are executed seamlessly by the underlying harness.
AgentConfig config = AgentConfig.builder().build();
try (Agent agent = new Agent(config)) {
// You can send slash commands directly!
AgentResponse response = agent.chat("/help").get(120, TimeUnit.SECONDS);
System.out.println(response.text());
}You can instantly enable powerful built-in tools (like web search, shell execution, image generation, or file editing) without writing them yourself using the CapabilitiesConfig.
CapabilitiesConfig capabilities = CapabilitiesConfig.builder()
.enableWebSearch(true)
.enableShell(true)
.enableWriteFile(true)
.enableFileEdit(true)
.enableListDir(true)
.enableGrepSearch(true)
.enableGenerateImage(true)
.build();
AgentConfig config = AgentConfig.builder()
.instructions("Search the web for the latest news and generate a diagram.")
.capabilities(capabilities)
.build();
try (Agent agent = new Agent(config)) {
// The agent now has access to web search, file operations, and image generation natively!
}Execute local Gemma models (LiteRTAgentConfig), local OpenAI-compatible backends like Ollama / LM Studio (LocalOpenAIAgentConfig), configure custom process environment variables, and tune reasoning severity (ThinkingLevel).
// 1. Local Gemma Model with LiteRT
LiteRTAgentConfig litertConfig = LiteRTAgentConfig.builder()
.modelPath("/models/gemma-2-9b.litertlm")
.backend(LiteRTAgentConfig.Backend.GPU)
.instructions("Local Gemma assistant")
.build();
// 2. Local OpenAI Endpoint (Ollama / LM Studio)
LocalOpenAIAgentConfig ollamaConfig = LocalOpenAIAgentConfig.builder()
.baseUrl("http://localhost:11434/v1")
.modelName("llama3")
.build();
// 3. Custom Environment Variables & Thinking Severity ("extra_high")
AgentConfig config = AgentConfig.builder()
.instructions("Deep reasoning architecture assistant.")
.environmentVariables(Map.of("CUSTOM_ENV_VAR", "value"))
.generation(GenerationConfig.builder()
.thinkingLevel(ThinkingLevel.EXTRA_HIGH)
.build())
.build();Configure automatic retries with exponential backoff for transient API errors and invalid model outputs using RetryConfig. Pass audio input parts directly in prompts for meeting summary workflows.
// Configure agent with benchmark exponential retries
AgentConfig config = AgentConfig.builder()
.instructions("Summarize meeting audio recordings accurately.")
.retryConfig(RetryConfig.benchmark())
.build();
byte[] meetingAudio = Files.readAllBytes(Path.of("meeting.mp3"));
AgentInput.Audio audioInput = new AgentInput.Audio("audio/mp3", meetingAudio, "Q3 roadmap sync");
try (Agent agent = new Agent(config)) {
AgentResponse response = agent.chat(audioInput).get(120, TimeUnit.SECONDS);
System.out.println(response.text());
}Surfacing tool execution failures programmatically as ToolExecutionError allows safe recovery in OnToolErrorHook. Use BuiltinTools constants and helper methods (readOnly(), nondestructive(), etc.) to reference built-in tool definitions.
// Reference built-in tool categories
List<BuiltinTools> safeTools = BuiltinTools.readOnly();
AgentConfig config = AgentConfig.builder()
.addOnToolErrorHook((call, err, ctx) -> {
if (err instanceof ToolExecutionError tee) {
System.err.println("Tool failed: " + tee.getToolName() + " with args: " + tee.getArgumentsJson());
}
return CompletableFuture.completedFuture("Safely recovered from tool error");
})
.build();Configure client logging levels and server-side distributed tracing uniformly across connection strategies using DebugConfig.
AgentConfig config = AgentConfig.builder()
.debugConfig(DebugConfig.defaults())
.build();Enforce strict session-level limits on model calls, tool calls, and token usage using BudgetConfig, toggle autonomous versus interactive mode using AgentBehavior, specify inference ServiceTier, and inspect fine-grained token usage broken down by Modality.
// 1. Configure session-level budget limits and autonomous behavior
BudgetConfig budget = BudgetConfig.builder()
.maxModelCalls(10)
.maxToolCalls(25)
.maxInputTokens(50_000L)
.maxOutputTokens(10_000L)
.maxTotalTokens(60_000L)
.build();
AgentConfig config = AgentConfig.builder()
.instructions("Autonomous research assistant with budget limits.")
.budgetConfig(budget)
.agentBehavior(AgentBehavior.AUTONOMOUS)
.generation(GenerationConfig.builder()
.serviceTier(ServiceTier.PRIORITY)
.thinkingLevel(ThinkingLevel.HIGH)
.build())
.build();
try (Agent agent = new Agent(config)) {
AgentResponse response = agent.chat("Perform deep codebase analysis.").get(120, TimeUnit.SECONDS);
// 2. Inspect multimodal token breakdown from UsageMetadata
UsageMetadata usage = response.usage();
if (usage != null) {
System.out.println("Total tokens: " + usage.totalTokenCount());
System.out.println("Service tier: " + usage.serviceTier());
for (ModalityTokenCount detail : usage.promptTokensDetails()) {
System.out.println(" Modality " + detail.modality() + ": " + detail.tokenCount() + " tokens");
}
}
}Configure daemon tasks and execution timeouts for the builtin run_command tool with RunCommandConfig, enforce strict filesystem containment policies with WorkspaceContainment, correlate trajectory steps (stepId) across hooks and structured errors, and rewrite arguments dynamically in pre-tool hooks.
// 1. Configure run_command tool behavior with daemon permissions and custom timeout
RunCommandConfig runCmdConfig = RunCommandConfig.builder()
.enableDaemons(true)
.timeoutSeconds(120.0)
.build();
CapabilitiesConfig capabilities = CapabilitiesConfig.builder()
.enableShell(true)
.runCommandConfig(runCmdConfig)
.build();
// 2. Configure workspace containment policy & pre-tool argument rewriting
AgentConfig config = AgentConfig.builder()
.instructions("Secure assistant with workspace containment and daemon permissions.")
.capabilities(capabilities)
.workspaceContainment(WorkspaceContainment.ENABLED)
.addPreToolCallDecideHook((toolCall, ctx) -> {
System.out.println("Executing tool " + toolCall.name() + " on step: " + toolCall.stepId());
if ("run_command".equals(toolCall.name())) {
// Rewrite tool arguments safely in hook
return CompletableFuture.completedFuture(
HookResult.allowedWithModifiedArguments("{\"command_line\": \"echo safe\"}")
);
}
return CompletableFuture.completedFuture(HookResult.allowed());
})
.addOnToolErrorHook((toolCall, err, ctx) -> {
if (err instanceof ToolExecutionError tee) {
System.err.println("Step " + tee.getStepId() + " failed: " + tee.getMessage());
}
return CompletableFuture.completedFuture("Recovered");
})
.build();Listen to context compaction events with OnCompactionHook, inspect trajectory termination reasons (StopReason) and depth metadata, correlate call IDs and step indices across hook types, and configure subagent skill inheritance policies (SubagentSkillsConfig).
// Intercept history compaction notifications and inspect trajectory metadata
AgentConfig config = AgentConfig.builder()
.instructions("Assistant with compaction logging and subagent skill controls.")
.addOnCompactionHook((compactionArgs, ctx) -> {
System.out.println("Compacted trajectory: " + compactionArgs.getTrajectoryId()
+ " at step: " + compactionArgs.getStepIndex()
+ " summary: " + compactionArgs.getSummary());
return CompletableFuture.completedFuture(null);
})
.build();Agents now default to gemini-3.8-flash for higher quality reasoning. Configure lightweight agents optimized for small/local models via .lightweight(), isolate shell execution with command sandboxing, and listen to cancellation/stop events with OnStopHook.
// 1. Configure lightweight agent for local/small models with sandboxed command execution
RunCommandConfig runCmd = RunCommandConfig.builder()
.enableSandbox(true)
.timeoutSeconds(60.0)
.build();
AgentConfig config = AgentConfig.builder()
.instructions("Lightweight agent running with sandbox isolation and stop monitoring.")
.lightweight() // Sets MINIMAL behavior, disables subagents, enables minimal safe tools
.capabilities(CapabilitiesConfig.builder().enableShell(true).runCommandConfig(runCmd).build())
.addOnStopHook((stopArgs, ctx) -> {
System.out.println("Agent stopped: " + stopArgs.getStopReason()
+ " on trajectory: " + stopArgs.getTrajectoryId());
return CompletableFuture.completedFuture(null);
})
.build();This project is licensed under the Apache License, Version 2.0.
This is not an officially supported Google product. It is a community-driven, experimental port created for educational and development purposes.