Antigravity Agent API Interception & Wrapper Pattern
How the agentapi binary is intercepted and wrapped to capture all calls, arguments, environment variables, and stdin data for reverse engineering the Antigravity harness
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 sessionsagentapi_getConversationMetadataHandler_Metadata-- Retrieves conversation metadataagentapi_sendMessageHandler_Execute-- Sends messages to the agentagentapi_sendMessageHandler_Metadata-- Message metadata endpointagentapi_resolveModel-- Resolves model configurationagentapi_printUsageAndExit-- Usage/help outputagentapi_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:
- Conversation lifecycle (create, resume, delete)
- Message passing (user input -> model output)
- Tool execution routing
- Authentication and CSRF protection
- 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:
- All command-line arguments (varargs)
- All environment variables (filtered and full)
- stdin data (if any, with size limits)
- The working directory
- Timestamps and process metadata
- Parent process information
Architecture
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
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:
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:
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
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():
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
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:
#!/bin/sh
exec /usr/bin/python3 /Users/alefita/probe/sdk-test/agentapi_interceptor.py "$@"
Installation
# 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:
# 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
# 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:
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:
- The SDK's protobuf bindings (
localharness_pb2.py) are the authoritative schema definition - The agentapi binary is a thin CLI wrapper around the same harness
- Intercepting agentapi reveals the CLI-level API surface that the SDK abstracts away
Key Findings from Interception
The interception pattern revealed:
- Environment-driven configuration: The harness reads
GEMINI_API_KEY,ANTIGRAVITY_HARNESS_PATH, and other env vars directly - Subcommand structure:
agentapi sendMessage,agentapi newConversation,agentapi getConversationMetadata - CSRF protection: Internal interceptor chain includes CSRF unary interceptors
- Model resolution:
agentapi_resolveModelhandles model name -> endpoint mapping - 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/.