---
name: sdk-test-agent-api
type: reference
title: "Antigravity Agent API Interception & Wrapper Pattern"
description: "How the agentapi binary is intercepted and wrapped to capture all calls, arguments, environment variables, and stdin data for reverse engineering the Antigravity harness"
tags: [antigravity, agentapi, interception, reverse-engineering, wrapper, localharness, harness]
timestamp: 2026-07-21
---

# Antigravity Agent API Interception & Wrapper Pattern

The `agentapi` binary is the internal command-line interface used by the Antigravity harness ecosystem (IDE, CLI, and SDK) to communicate with the agent backend. This page documents the interception pattern used to capture and analyze all agentapi calls, the wrapper shell script, and how this fits with the broader antigravity-2.0 ecosystem.

---

## What Is agentapi?

The `agentapi` binary lives at `~/.gemini/antigravity/bin/agentapi` and is invoked by the Antigravity application layer (Antigravity.app, IDE plugins, CLI tools) to communicate with the Go-based `localharness` runtime. It serves as the bridge between the user-facing application and the agent execution engine.

The binary's internal structure -- revealed through reverse engineering of the Go binary at `/Users/alefita/probe/sdk-test/reverse_engineering/` -- includes:

- `agentapi_newConversationHandler_Execute` -- Creates new conversation sessions
- `agentapi_getConversationMetadataHandler_Metadata` -- Retrieves conversation metadata
- `agentapi_sendMessageHandler_Execute` -- Sends messages to the agent
- `agentapi_sendMessageHandler_Metadata` -- Message metadata endpoint
- `agentapi_resolveModel` -- Resolves model configuration
- `agentapi_printUsageAndExit` -- Usage/help output
- `agentapi_HandleAgentAPI_csrfUnaryInterceptor_func2` -- CSRF protection interceptor

The agentapi binary is the programmatic interface that the SDK's `LocalConnectionStrategy` wraps when running in "local harness" mode. It handles:
1. Conversation lifecycle (create, resume, delete)
2. Message passing (user input -> model output)
3. Tool execution routing
4. Authentication and CSRF protection
5. Model resolution and configuration

---

## The Interception Pattern

### Motivation

To understand how the Antigravity harness communicates with its backend, the agentapi binary was intercepted using a transparent wrapper pattern. This captures:

1. All command-line arguments (varargs)
2. All environment variables (filtered and full)
3. stdin data (if any, with size limits)
4. The working directory
5. Timestamps and process metadata
6. Parent process information

### Architecture

```mermaid
sequenceDiagram
    participant App as Antigravity.app / IDE
    participant Wrapper as agentapi (wrapper)
    participant Interceptor as agentapi_interceptor.py
    participant Real as agentapi.bak (original binary)
    participant Harness as localharness (Go)

    App->>Wrapper: exec agentapi [args...]
    Wrapper->>Interceptor: exec python3 agentapi_interceptor.py [args...]
    Note over Interceptor: Capture argv, env, stdin, cwd
    Interceptor->>Interceptor: Write JSONL to /tmp/agentapi_intercept.jsonl
    Interceptor->>Interceptor: Print summary to stderr
    Interceptor->>Real: os.execv(real_binary, [args...])
    Real->>Harness: WebSocket / protobuf communication
    Harness-->>Real: OutputEvent
    Real-->>App: Response
```

### Files

| File | Path | Purpose |
|:-----|:-----|:--------|
| `agentapi_interceptor.py` | `/Users/alefita/probe/sdk-test/agentapi_interceptor.py` | Python interceptor that captures all data and optionally forwards to real binary |
| `agentapi_wrapper.sh` | `/Users/alefita/probe/sdk-test/agentapi_wrapper.sh` | Shell shim that delegates to the Python interceptor |
| `agentapi.bak` | `~/.gemini/antigravity/bin/agentapi.bak` | Backup of the original agentapi binary |

---

## The Interceptor (`agentapi_interceptor.py`)

A PEP 723 script (run via `uv run` or standalone Python 3.11+).

### Configuration

```python
LOG_FILE = "/tmp/agentapi_intercept.jsonl"      # JSONL append-only log
FORWARD_TO_REAL = True                           # Forward to real binary after logging
REAL_BINARY_CANDIDATES = [
    os.path.expanduser("~/.gemini/antigravity/bin/agentapi.bak"),
    "/Applications/Antigravity.app/Contents/Resources/bin/language_server",
]
```

### Data Capture

The interceptor captures the following per-invocation record:

```python
record = {
    "timestamp": 1753000000.0,
    "timestamp_iso": "2026-07-21T12:00:00-0300",
    "pid": 12345,
    "ppid": 12340,
    "uid": 501,
    "cwd": "/Users/alefita/workdir/project",
    "argv": ["agentapi", "sendMessage", "--json", "..."],
    "argc": 4,
    "argv_detail": {
        "script": "agentapi",
        "subcommand": "sendMessage",
        "remaining_args": ["--json", "..."],
    },
    "env_interesting": {
        "GEMINI_API_KEY": "AIza...",
        "ANTIGRAVITY_HARNESS_PATH": "/path/to/localharness",
        "HOME": "/Users/alefita",
        # ... filtered env vars
    },
    "env_count_total": 87,
    "stdin_data": None,  # or {"encoding": "utf-8", "data": "..."}
    "parent_process": "Antigravity --type=zygote",
}
```

### Environment Variable Filtering

Only environment variables with specific prefixes are captured in the "interesting" subset:

```python
interesting_prefixes = (
    "GEMINI", "GOOGLE", "ANTIGRAVITY", "AGY", "HOME", "USER",
    "PATH", "SHELL", "TERM", "XDG", "OAUTH", "TOKEN", "AUTH",
    "API_KEY", "CLOUD", "GCP", "VERTEX", "MLX", "GRPC",
)
```

The full environment is also available in the record under `env_full` for complete analysis.

### Stdin Capture

```python
def capture_stdin_if_available(max_bytes=8192):
    """Non-blocking read of stdin if data is available."""
    if sys.stdin.isatty():
        return None
    readable, _, _ = select.select([sys.stdin], [], [], 0.1)
    if readable:
        data = sys.stdin.buffer.read(max_bytes)
        try:
            return {"encoding": "utf-8", "data": data.decode("utf-8")}
        except UnicodeDecodeError:
            return {"encoding": "hex", "data": data.hex(), "size_bytes": len(data)}
    return None
```

Uses `select()` with a 0.1s timeout for non-blocking detection. Limits capture to 8 KiB to avoid blocking on large payloads.

### Forward-to-Real Mechanism

After logging, the interceptor optionally forwards the call to the real binary using `os.execv()`:

```python
if "language_server" in real_binary:
    cmd = [real_binary, "agentapi"] + sys.argv[1:]
else:
    cmd = [real_binary] + sys.argv[1:]

os.execv(cmd[0], cmd)
```

This replaces the current process with the real binary, making the interception transparent to the caller. The `language_server` binary requires an `agentapi` subcommand prefix, while the backup `agentapi.bak` does not.

### Parent Process Detection

```python
ps_result = subprocess.run(
    ["ps", "-p", str(ppid), "-o", "comm=,args="],
    capture_output=True, text=True, timeout=2
)
record["parent_process"] = ps_result.stdout.strip()
```

Identifies the calling process (e.g., `Antigravity --type=zygote`, `Google Chrome`, `python3`).

---

## The Wrapper (`agentapi_wrapper.sh`)

A minimal shell shim that replaces the original `agentapi` binary:

```bash
#!/bin/sh
exec /usr/bin/python3 /Users/alefita/probe/sdk-test/agentapi_interceptor.py "$@"
```

### Installation

```bash
# 1. Backup original
cp ~/.gemini/antigravity/bin/agentapi ~/.gemini/antigravity/bin/agentapi.bak

# 2. Install wrapper
cp agentapi_wrapper.sh ~/.gemini/antigravity/bin/agentapi
chmod +x ~/.gemini/antigravity/bin/agentapi
```

### Usage

Once installed, every call to `agentapi` from the Antigravity ecosystem is transparently intercepted:

```bash
# From Antigravity.app, IDE, or CLI:
agentapi sendMessage --json '{"message": "hello"}'

# Interceptor captures, then forwards to real binary
# Log written to /tmp/agentapi_intercept.jsonl
```

### Reading Captured Data

```bash
# Human-readable summary (printed to stderr during each call)
# Look at the stderr output from the intercepted call

# Machine-readable log
cat /tmp/agentapi_intercept.jsonl | python3 -m json.tool

# Filter by subcommand
cat /tmp/agentapi_intercept.jsonl | jq 'select(.argv_detail.subcommand == "sendMessage")'

# Find all environment variables across calls
cat /tmp/agentapi_intercept.jsonl | jq '.env_interesting | keys[]' | sort -u

# Timeline of calls
cat /tmp/agentapi_intercept.jsonl | jq '[.timestamp_iso, .argv_detail.subcommand, .cwd]'
```

---

## How This Fits with the SDK

The `agentapi` interceptor pattern and the Antigravity Python SDK are complementary tools for understanding and extending the harness:

```mermaid
graph TB
    subgraph SDK["Python SDK (google-antigravity)"]
        Agent["Agent"]
        LConn["LocalConnection"]
        LStrat["LocalConnectionStrategy"]
        PB["localharness_pb2.py<br/>(protobuf bindings)"]
    end

    subgraph Intercept["Interception Layer"]
        Wrapper["agentapi_wrapper.sh"]
        Interceptor["agentapi_interceptor.py"]
    end

    subgraph Harness["Go Harness"]
        LH["localharness binary"]
        WS["WebSocket server"]
        AG["Agent runtime"]
    end

    Agent --> LStrat
    LStrat -->|"spawns subprocess"| LH
    LH --> WS
    WS --> AG

    Wrapper -->|"replaces"| agentapi
    Interceptor -->|"captures + forwards"| agentapi.bak
    agentapi.bak -->|"communicates with"| LH
```

### SDK Path vs. Interception Path

| Aspect | SDK Path | Interception Path |
|:-------|:---------|:------------------|
| Entry point | `Agent(config)` | `agentapi [subcommand] [args]` |
| Transport | WebSocket (direct to harness) | Binary stdin/stdout + WebSocket |
| Config | `LocalAgentConfig` (Pydantic) | Command-line arguments + env vars |
| Proto layer | `localharness_pb2.py` (Python) | Internal Go protobuf |
| Purpose | Programmatic agent development | Reverse engineering and analysis |

### What the SDK Reveals

The SDK's `LocalConnectionStrategy` bypasses the `agentapi` binary entirely -- it spawns `localharness` directly and communicates via WebSocket. This means:

1. The SDK's protobuf bindings (`localharness_pb2.py`) are the authoritative schema definition
2. The agentapi binary is a thin CLI wrapper around the same harness
3. Intercepting agentapi reveals the CLI-level API surface that the SDK abstracts away

### Key Findings from Interception

The interception pattern revealed:

1. **Environment-driven configuration**: The harness reads `GEMINI_API_KEY`, `ANTIGRAVITY_HARNESS_PATH`, and other env vars directly
2. **Subcommand structure**: `agentapi sendMessage`, `agentapi newConversation`, `agentapi getConversationMetadata`
3. **CSRF protection**: Internal interceptor chain includes CSRF unary interceptors
4. **Model resolution**: `agentapi_resolveModel` handles model name -> endpoint mapping
5. **Parent process diversity**: Calls originate from Electron (Antigravity.app), Chrome extensions, and Python scripts

---

## Related Reverse Engineering

The interception pattern is part of a broader reverse engineering effort documented in `/Users/alefita/probe/sdk-test/reverse_engineering/`:

| Component | Decompiled Functions | Purpose |
|:----------|:--------------------|:--------|
| `agy/` | `interceptor_*`, `agentapi_*` | Main agent process interceptors |
| `localharness/` | `agentapi_ptrnewConversationHandler_Execute` | Conversation creation |
| `language_server_ide/` | `interceptor_*`, `agentapi_*` | IDE-specific language server |
| `language_server_hub/` | `interceptor_*`, `agentapi_*` | Hub language server |
| `CLIProxyAPI/` | `interceptor.go`, `handlers_interceptors_test.go` | Go proxy API with interceptor patterns |

The Go binary's interceptor chain includes:
- **CSRF interceptor** -- Cross-site request forgery protection (wraps unary, streaming client, and streaming handler)
- **Latency interceptor** -- Request timing metrics
- **Mock data interceptor** -- Test data injection
- **Request ID interceptor** -- Correlation ID propagation
- **Trace task interceptor** -- Distributed tracing

---

## Cross-References

- antigravity-2.0 -- The Antigravity research harness
- [[sdk-test-antigravity-sdk]] -- Complete SDK documentation
- [[mcp-ecosystem]] -- Model Context Protocol integration

---

*Source: `antigravity-sdk-python` v0.1.2 + manual reverse engineering at `/Users/alefita/probe/sdk-test/`.*
