Skip to content

Repository files navigation

Antigravity SDK for Java

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!).

How it works

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.

Features & Usage

This SDK achieves full feature parity with the Python SDK. Below are examples of what you can do.

1. Basic Agent Setup

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());
}

2. Streaming Responses

Stream tokens as they are generated by the model. The SDK supports both simple callback functions and Java 9 Reactive Streams (Flow.Publisher).

Callback Streaming

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);
}

Reactive Streams (Publisher)

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!");
        }
    });
}

Sugared Reactive Streaming (Thoughts & Tool Calls)

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).

3. Tool Calling

Provide custom tools that the agent can execute during its turn. The SDK provides two ways to do this:

Annotated Tools (Recommended)

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();

Dynamic Tools

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();

4. Agent Skills

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!

5. Security Policies

Restrict which tools the agent is allowed to execute using Security Policies. Policies are evaluated strictly in the order they are added.

Basic Policy (Deny All)

AgentConfig config = AgentConfig.builder()
    .instructions("You are restricted from running dangerous commands.")
    .addPolicy(Policies.denyAll()) // Blocks all tool executions by default
    .build();

Advanced "Deny by Default" Posture

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.

6. Lifecycle Hooks

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();

7. Model Context Protocol (MCP)

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();

8. Background Triggers

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 closes

9. Multimodal Inputs

Pass 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);

10. Structured Outputs

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());
}

11. Subagents

Agents can spawn and delegate tasks to subagents.

AgentConfig config = AgentConfig.builder()
    .capabilities(CapabilitiesConfig.builder().enableSubagents(true).build())
    .build();

12. Persistence (Sessions)

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"
}

13. Observability & Token Usage

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());

14. Cancellation

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!");
        }
    }
}

15. Slash Commands

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());
}

16. Built-in Tools (Capabilities)

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!
}

17. Local Models, Environment Variables & Thinking Severity

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();

18. Model Retry Configuration & Audio Analysis

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());
}

19. Structured Tool Exception Handling & BuiltinTools Exports

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();

20. Unified Connection Tracing (DebugConfig)

Configure client logging levels and server-side distributed tracing uniformly across connection strategies using DebugConfig.

AgentConfig config = AgentConfig.builder()
    .debugConfig(DebugConfig.defaults())
    .build();

21. Budget Caps, Agent Behavior & Modality Breakdown (v0.1.12)

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");
        }
    }
}

22. Run Command Options, Workspace Containment & Step Correlation (v0.1.13)

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();

23. Compaction Hooks, Subagent Skill Policies & Trace Correlation (v0.1.14)

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();

24. Gemini 3.8 Flash, Lightweight Agent Mode, Command Sandboxing & Stop Hooks (v0.1.16)

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();

License

This project is licensed under the Apache License, Version 2.0.

Disclaimer

This is not an officially supported Google product. It is a community-driven, experimental port created for educational and development purposes.

About

An unofficial Java port of the Antigravity AI Agent SDK.

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages