Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
142 changes: 142 additions & 0 deletions e2e.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// 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.

// Package main implements an end-to-end demonstration of the Antigravity harness
// integration with AX Controller V2.
package main

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

"github.com/google/ax/internal/controller/executor"
"github.com/google/ax/internal/controller/executor/executortest"
"github.com/google/ax/internal/controller2"
"github.com/google/ax/internal/harness"
"github.com/google/ax/internal/harness/harnesstest"
"github.com/google/ax/proto"
)

func main() {
ctx := context.Background()
fmt.Println("==================================================")
fmt.Println("AX Controller V2 - E2E Harness Demonstration")
fmt.Println("==================================================")

// -------------------------------------------------------------------------
// Demo 1: Runtime Fallback (No harness registered)
// -------------------------------------------------------------------------
fmt.Println("\n--- Demo 1: Runtime Fallback ---")
fmt.Println("Requesting 'unregistered-agent'. Should fallback to Test Harness (Hello World).")
runDemo(ctx, "unregistered-agent", func(reg *controller2.Registry) {
// Do not register any harness
})

// -------------------------------------------------------------------------
// 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)
// -------------------------------------------------------------------------
fmt.Println("\n--- Demo 3: 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
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)
realHarness = harnesstest.New()
} else {
realHarness = harness.NewAntigravityHarness(scriptPath)
}
reg.RegisterHarness("antigravity", realHarness)
})
}

func runDemo(ctx context.Context, agentID string, setupRegistry func(reg *controller2.Registry)) {
reg := controller2.NewRegistry()
setupRegistry(reg)

log := &executortest.MemoryEventLog{}
c, err := controller2.New(ctx, controller2.Config{
Registry: reg,
EventLogBuilder: func() (executor.EventLog, error) {
return log, nil
},
})
if err != nil {
fmt.Printf("Error creating controller: %v\n", err)
return
}
defer c.Close()

handler := controller2.ExecHandler(func(resp *proto.ExecResponse) error {
for _, out := range resp.Outputs {
if textContent := out.GetContent().GetText().GetText(); textContent != "" {
fmt.Printf("Agent Output: %s\n", textContent)
}
}
return nil
})

inputs := []*proto.Message{
{
Role: "user",
Content: &proto.Content{
Type: &proto.Content_Text{
Text: &proto.TextContent{Text: "Who are you?"},
},
},
},
}

err = c.Exec(ctx, &proto.ExecRequest{
ConversationId: "e2e-conv",
Inputs: inputs,
AgentId: agentID,
}, handler)

if err != nil {
fmt.Printf("Execution Failed (as expected if environment is not ready): %v\n", err)
} else {
fmt.Println("Execution Succeeded!")
}
}
27 changes: 27 additions & 0 deletions examples/antigravity_agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Antigravity Agent Example

This directory contains a simple example of an agent built using the `google-antigravity` SDK.

## Prerequisites

Ensure you have Python 3.10+ installed.

## Setup

1. Install the required dependencies:
```bash
pip install -r requirements.txt
```

2. Set your Gemini API key in your environment:
```bash
export GEMINI_API_KEY="your-api-key-here"
```

## Running the Agent

Run the agent script directly:

```bash
python agent.py
```
28 changes: 28 additions & 0 deletions examples/antigravity_agent/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 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.

import asyncio
import sys
from google.antigravity import Agent, LocalAgentConfig

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:
prompt = sys.argv[1] if len(sys.argv) > 1 else "Explain quantum computing in one sentence."
response = await agent.chat(prompt)
print(await response.text())

if __name__ == "__main__":
asyncio.run(main())
1 change: 1 addition & 0 deletions examples/antigravity_agent/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
google-antigravity
24 changes: 16 additions & 8 deletions internal/controller2/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package controller2
import (
"context"
"fmt"
"log"

"github.com/google/ax/internal/controller/executor"
"github.com/google/ax/internal/harness/harnesstest"
Expand All @@ -31,20 +32,21 @@ type ExecHandler func(resp *proto.ExecResponse) error
// Controller is the main controller that coordinates all components.
// It acts as a single-writer system for managing agentic loops.
type Controller struct {
registry *Registry
eventLog executor.EventLog
registry *Registry
eventLog executor.EventLog
}

// Config configures the controller.
type Config struct {
Registry *Registry
EventLogBuilder executor.EventLogBuilder
}

// New creates a new controller instance.
func New(ctx context.Context, cfg Config) (*Controller, error) {
// Initialize agent registry
registry := NewRegistry()

if cfg.Registry == nil {
return nil, fmt.Errorf("registry is required")
}
if cfg.EventLogBuilder == nil {
return nil, fmt.Errorf("event log builder is required")
}
Expand All @@ -54,8 +56,8 @@ func New(ctx context.Context, cfg Config) (*Controller, error) {
}

return &Controller{
registry: registry,
eventLog: eventLog,
registry: cfg.Registry,
eventLog: eventLog,
}, nil
}

Expand All @@ -70,7 +72,13 @@ func (d *Controller) Exec(ctx context.Context, req *proto.ExecRequest, handler E
// TODO(jbd): Resume an incomplete execution if there exists one.
// TODO(jbd): Enable bringing a remote harness that implements HarnessService.

h := harnesstest.New()
// Retrieve harness from registry
h, err := d.registry.GetHarness(req.AgentId)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks like we are retrieving an existing harness instance given an agent id. Are we mixing the two concepts here?

if err != nil {
// Fallback to test harness
log.Printf("WARNING: harness %s not found in registry, falling back to test harness: %v", req.AgentId, err)
h = harnesstest.New()
}
exec, err := h.Start(ctx, req.ConversationId)
if err != nil {
return fmt.Errorf("failed to start harness session: %w", err)
Expand Down
Loading
Loading