Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
7b7d8bd
feat: Integrate Antigravity harness in Controller V2 with Registry-ce…
anj-s May 22, 2026
4f3a4b2
Merge branch 'main' into u/anj/harness-interface-3
anj-s May 27, 2026
cf7b353
AX V2 Harness Redesign: Implement Antigravity WebSocket Streaming
anj-s May 28, 2026
e4a2e14
examples: Refactor antigravity agent.py to perform real-time token st…
anj-s May 28, 2026
9767aa4
examples: Refactor agent.py to use low-level L2 Conversation and Loca…
anj-s May 28, 2026
1a65a28
harness: Add full E2E ToolCall streaming and local execution support
anj-s May 28, 2026
25f8f84
examples: Consolidate agent.py and weather_agent.py into a single ult…
anj-s May 28, 2026
4c857c6
harness: Cleanly relocate Go E2E program to cmd/e2e/main.go
anj-s May 28, 2026
ef0342e
harness: Add explicit compile-time interface assertions in antigravit…
anj-s May 28, 2026
bccd63f
python: Add mandatory Google license headers to regenerated proto mod…
anj-s May 28, 2026
b950b89
harness: Re-architect always-on streaming harness to use standard gRP…
anj-s May 28, 2026
27b2e4a
Merge branch 'main' into u/anj/harness-interface-3
anj-s May 28, 2026
b216469
docs: Add architectural note comments to agent.py and harness_server.py
anj-s Jun 1, 2026
e6e595e
Merge branch 'main' into u/anj/harness-interface-3
anj-s Jun 1, 2026
300cb35
docs: Add E2E setup and running instructions header to cmd/e2e/main.go
anj-s Jun 1, 2026
c1e2b69
feat: Upgrade agent.py to support an interactive multi-turn console c…
anj-s Jun 1, 2026
598976a
revert: Revert interactive multi-turn console chat loop to preserve s…
anj-s Jun 1, 2026
a0e96f5
revert: Revert comment above RegisterHarness to keep the TODO for reg…
anj-s Jun 1, 2026
60830d7
refactor: remove obsolete local script path checks in E2E main
anj-s Jun 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 25 additions & 33 deletions e2e.go → cmd/e2e/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,23 @@

// Package main implements an end-to-end demonstration of the Antigravity harness
// integration with AX Controller V2.
//
// TO RUN THIS E2E DEMONSTRATION:
//
// Step 1: Start the Python gRPC Harness Server (in a separate terminal or background):
// PYTHONPATH=python:. /Users/anjalisridhar/.gemini/jetski/worktrees/harness-interface-3/implement-agy-sdk-streaming-20260528/.venv/bin/python python/antigravity/harness_server.py --port 50053
//
// Step 2: Run this Go E2E client:
// go run cmd/e2e/main.go
package main


import (
"context"
"fmt"
"net"
"os"
"os/exec"
"time"

"github.com/google/ax/internal/controller/executor"
"github.com/google/ax/internal/controller/executor/executortest"
Expand All @@ -46,46 +56,26 @@ func main() {
})

// -------------------------------------------------------------------------
// Demo 2: Build-time Fallback (Antigravity with bad script path)
// -------------------------------------------------------------------------
fmt.Println("\n--- Demo 2: Build-time Fallback ---")
fmt.Println("Registering 'antigravity' with non-existent script. Should fallback to Test Harness.")
runDemo(ctx, "antigravity", func(reg *controller2.Registry) {
// Build harness with bad path, manually implementing fallback check
var badHarness harness.Harness
scriptPath := "non-existent-script.py"
if _, err := exec.LookPath("python3"); err != nil {
fmt.Printf("WARNING: python3 not found, falling back to test harness: %v\n", err)
badHarness = harnesstest.New()
} else if _, err := os.Stat(scriptPath); err != nil {
fmt.Printf("WARNING: Antigravity agent script not found at %s, falling back to test harness: %v\n", scriptPath, err)
badHarness = harnesstest.New()
} else {
badHarness = harness.NewAntigravityHarness(scriptPath)
}
reg.RegisterHarness("antigravity", badHarness)
})

// -------------------------------------------------------------------------
// Demo 3: Antigravity Execution (Requires google-antigravity & GEMINI_API_KEY)
// Demo 2: Antigravity Execution (Requires google-antigravity & GEMINI_API_KEY)
// -------------------------------------------------------------------------
fmt.Println("\n--- Demo 3: Antigravity Execution ---")
fmt.Println("\n--- Demo 2: Antigravity Execution ---")
fmt.Println("Registering 'antigravity' with real script. Attempting execution.")
if os.Getenv("GEMINI_API_KEY") == "" {
fmt.Println("WARNING: GEMINI_API_KEY is not set. Execution will likely fail if dependencies are missing, but we will try anyway.")
}
runDemo(ctx, "antigravity", func(reg *controller2.Registry) {
// Build harness with real path, manually implementing fallback check
// With the new stateful gRPC-based streaming harness, connectivity checks on the
// server address replace the build-time checks for local script file presence.
var realHarness harness.Harness
scriptPath := "examples/antigravity_agent/agent.py"
if _, err := exec.LookPath("python3"); err != nil {
fmt.Printf("WARNING: python3 not found, falling back to test harness: %v\n", err)
realHarness = harnesstest.New()
} else if _, err := os.Stat(scriptPath); err != nil {
fmt.Printf("WARNING: Antigravity agent script not found at %s, falling back to test harness: %v\n", scriptPath, err)
address := "localhost:50053"
conn, err := net.DialTimeout("tcp", address, 1*time.Second)
if err != nil {
fmt.Printf("WARNING: Antigravity harness server not active at %s, falling back to test harness: %v\n", address, err)
realHarness = harnesstest.New()
} else {
realHarness = harness.NewAntigravityHarness(scriptPath)
conn.Close()
fmt.Printf("Connected to Antigravity gRPC harness server at %s\n", address)
realHarness = harness.NewAntigravityHarness(address)
}
reg.RegisterHarness("antigravity", realHarness)
})
Expand All @@ -112,6 +102,8 @@ func runDemo(ctx context.Context, agentID string, setupRegistry func(reg *contro
for _, out := range resp.Outputs {
if textContent := out.GetContent().GetText().GetText(); textContent != "" {
fmt.Printf("Agent Output: %s\n", textContent)
} else if toolCall := out.GetContent().GetToolCall(); toolCall != nil {
fmt.Printf("Agent Triggered Tool Call: %s (ID: %s)\n", toolCall.GetFunctionCall().Name, toolCall.Id)
}
}
return nil
Expand All @@ -122,7 +114,7 @@ func runDemo(ctx context.Context, agentID string, setupRegistry func(reg *contro
Role: "user",
Content: &proto.Content{
Type: &proto.Content_Text{
Text: &proto.TextContent{Text: "Who are you?"},
Text: &proto.TextContent{Text: "What is the weather in New York?"},
},
},
},
Expand Down
75 changes: 67 additions & 8 deletions examples/antigravity_agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,80 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# NOTE ON ARCHITECTURE:
# This file plays a dual-purpose role:
# 1. Standalone Sandbox: Can be run directly via CLI (python agent.py "prompt") for local L2 debugging.
# 2. Declarative Config Module: Exposes 'agent_config' globally, which python/antigravity/harness_server.py
# dynamically imports to serve this agent over production gRPC.

import asyncio

import sys
from google.antigravity import Agent, LocalAgentConfig
from google.antigravity import LocalAgentConfig
from google.antigravity.connections.local import LocalConnectionStrategy
from google.antigravity.conversation.conversation import Conversation
from google.antigravity.tools.tool_runner import ToolRunner
from google.antigravity.types import Text, Thought, ToolCall

# 1. Define a custom local python tool
def get_weather(city: str) -> str:
"""Retrieves the current weather report for a specified city.

Args:
city (str): The name of the city for which to retrieve the weather report.

Returns:
str: Weather report status and details.
"""
# Output to stderr so it does not pollute the stdout stream capture
sys.stderr.write(f"\n[PYTHON TOOL get_weather executed for city: {city}]\n")
sys.stderr.flush()
c = city.lower()
if "new york" in c or "nyc" in c:
return "The weather in New York is sunny with a temperature of 25 degrees Celsius (77 degrees Fahrenheit)."
elif "san francisco" in c or "sf" in c:
return "The weather in San Francisco is foggy with a temperature of 16 degrees Celsius (60.8 degrees Fahrenheit)."
else:
return f"Weather information for '{city}' is not available."

# 2. Expose agent_config globally for harness_server.py config loading
agent_config = LocalAgentConfig(
system_instructions="You are a helpful agent. Use the get_weather tool to answer weather questions.",
tools=[get_weather]
)

# Expose the L2 configuration strategy factory for custom loaders
strategy_factory = lambda: LocalConnectionStrategy(tool_runner=ToolRunner(tools=[get_weather]))

async def main():
# Initialize the agent configuration. It automatically picks up GEMINI_API_KEY from the environment.
config = LocalAgentConfig()
async with Agent(config) as agent:
# Check if the user has sent a prompt otherwise thrown an error

# 3. Initialize the local connection strategy
strategy = strategy_factory()

# 4. Create the stateful conversation session
print("Starting stateful Antigravity conversation (L2 API)...")
async with Conversation.create(strategy) as conversation:
prompt = sys.argv[1] if len(sys.argv) > 1 else None
if not prompt:
raise ValueError("Please provide a prompt for your agent. Usage: python agent.py <prompt>")
response = await agent.chat(prompt)
print(await response.text())

# 5. Send query and receive streaming ChatResponse
response = await conversation.chat(prompt)

# 6. Stream semantic chunks (Thoughts, Text, and ToolCalls) in real-time
async for chunk in response.chunks:
if isinstance(chunk, Text):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to use a more structured format like JSONL.

sys.stdout.write(chunk.text)
sys.stdout.flush()
elif isinstance(chunk, Thought):
# Display thought process in comment style
sys.stdout.write(f"\n[Thinking]: {chunk.text}")
sys.stdout.flush()
elif isinstance(chunk, ToolCall):
sys.stdout.write(f"\n[Tool Call]: {chunk.name} with args {chunk.args}\n")
sys.stdout.flush()
print()

if __name__ == "__main__":
asyncio.run(main())


74 changes: 74 additions & 0 deletions hack/run-antigravity-streaming.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/bin/bash
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -e

# Check if GEMINI_API_KEY is set
if [ -z "$GEMINI_API_KEY" ]; then
echo "ERROR: GEMINI_API_KEY environment variable is not set."
echo "Please set it using: export GEMINI_API_KEY=\"your-key\""
exit 1
fi

PORT=50053
ADDRESS="localhost:$PORT"
AGENT_FILE="examples/antigravity_agent/agent.py"

# 1. Start Python gRPC server in the background
echo "Starting Python gRPC Harness Server on port $PORT..."
PYTHONPATH=python:. .venv/bin/python -m python.antigravity.harness_server --agent_file "$AGENT_FILE" --port "$PORT" > /tmp/antigravity_harness.log 2>&1 &
SERVER_PID=$!

# Register trap to ensure server is killed on script exit
cleanup() {
echo "Cleaning up: killing Python server (PID: $SERVER_PID)..."
kill "$SERVER_PID" || true
wait "$SERVER_PID" 2>/dev/null || true
echo "Cleanup complete!"
}
trap cleanup EXIT

# 2. Wait for the Python server to be healthy
echo "Waiting for Python server to become healthy..."
MAX_ATTEMPTS=30
ATTEMPT=1
HEALTHY=false

while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
# We can check if the port is open using nc (netcat)
if nc -z localhost "$PORT"; then
HEALTHY=true
break
fi
sleep 0.2
ATTEMPT=$((ATTEMPT + 1))
done

if [ "$HEALTHY" = false ]; then
echo "ERROR: Python server failed to start within 6 seconds."
echo "Server logs (/tmp/antigravity_harness.log):"
cat /tmp/antigravity_harness.log
exit 1
fi
echo "Python server is active!"

# 3. Build and run the Go E2E V2 demonstration
echo "Building e2e..."
/opt/homebrew/bin/go build -o bin/e2e ./cmd/e2e

echo "Executing E2E Demo with Antigravity gRPC Harness..."
bin/e2e

echo "Success!"
Loading
Loading