---
name: sdk-test-protobuf
type: reference
title: "Protobuf Deep Analysis — Antigravity Wire Protocol"
description: "Complete protobuf message reference for the Antigravity localharness protocol: every message type, all fields, oneof patterns, serialization format, and wire protocol details"
tags: [protobuf, wire-protocol, serialization, antigravity, localharness, message-types]
timestamp: 2026-07-22
---

# Protobuf Deep Analysis — Antigravity Wire Protocol

## Overview

The Antigravity SDK communicates with the localharness Go binary via **Protocol Buffers serialized as JSON** over a WebSocket connection. The `localharness_pb2.py` file (430 lines) is auto-generated from `localharness.proto` and defines the complete wire protocol.

This page documents every message type, every field, every oneof pattern, and the serialization rules.

## Wire Format

### stdin/stdout (Handshake Phase)

Length-prefixed binary protobuf:
```
[4 bytes uint32 little-endian: message_length][protobuf bytes: message_body]
```

This is standard binary protobuf, NOT JSON. The handshake is synchronous and blocking.

### WebSocket (Runtime Phase)

JSON-serialized protobuf. The SDK uses `google.protobuf.json_format.MessageToJson()` and `Parse()` for serialization. Example wire message:

```json
{
  "stepUpdate": {
    "cascadeId": "abc-123",
    "trajectoryId": "traj-0",
    "stepIndex": 3,
    "state": "ACTIVE",
    "source": "MODEL",
    "target": "USER",
    "textDelta": "Here are the files",
    "listDirectory": {
      "directoryPath": "/workspace",
      "results": [
        {"name": "src", "isDirectory": true},
        {"name": "README.md", "isDirectory": false, "fileSize": 1234}
      ]
    }
  },
  "seqNum": 42,
  "timestampMicros": 1721654400000000
}
```

**Field naming**: Protobuf field names use `snake_case` in the `.proto` definition but are serialized as `camelCase` in JSON (protobuf JSON mapping convention).

## Message Category Map

```mermaid
graph TB
    subgraph "Handshake (stdin/stdout)"
        IC["InputConfig<br/>SDK → Harness"]
        OCI["OutputConfig<br/>Harness → SDK"]
        CI["ClientInfo<br/>part of InputConfig"]
    end

    subgraph "Initialization (WebSocket)"
        ICE["InitializeConversationEvent<br/>SDK → Harness"]
        HC["HarnessConfig<br/>central config message"]
    end

    subgraph "Model Configuration (oneof in HarnessConfig)"
        GC["GeminiConfig"]
        GMC["GemmaConfig"]
        CBC["CustomBackendConfig"]
    end

    subgraph "Runtime: Harness → SDK"
        OE["OutputEvent<br/>main envelope"]
        SU["StepUpdate<br/>workhorse message"]
        TSU["TrajectoryStateUpdate"]
        TC["ToolCall"]
        UM["UsageMetadata"]
    end

    subgraph "Runtime: SDK → Harness"
        IE["InputEvent<br/>main envelope"]
        UI["UserInput"]
        TCF["ToolConfirmation"]
        TR["ToolResponse"]
        UQR["UserQuestionsResponse"]
    end

    subgraph "Configuration Sub-messages"
        SI["SystemInstructions"]
        TL["Tool"]
        HST["HarnessSideTools"]
        WS["Workspace"]
        MCP["McpServerConfig"]
    end

    OE --> SU
    OE --> TSU
    OE --> TC
    OE --> UM
    ICE --> HC
    HC --> GC
    HC --> GMC
    HC --> CBC
    HC --> SI
    HC --> TL
    HC --> HST
    HC --> WS
    HC --> MCP
```

## Handshake Messages

### InputConfig (SDK to Harness, stdin)

Sent as the first message over stdin. The harness blocks until it receives this.

| Field | Proto # | Type | Default | Description |
|-------|---------|------|---------|-------------|
| `storage_directory` | 1 | string | "" | Path for trajectory persistence on disk |
| `port` | 2 | uint32 | 0 | Requested WebSocket port. 0 = auto-assign |
| `bind_address` | 3 | string | "localhost" | WebSocket bind address |
| `client_info` | 4 | ClientInfo | — | SDK metadata |

**Behavior**: If `port` is 0, the harness selects an available port. The actual port is reported back in `OutputConfig.port`.

### ClientInfo (sub-message of InputConfig)

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `language` | 1 | string | Always "python" for the Python SDK |
| `version` | 2 | string | SDK version from package metadata (e.g., "0.1.2") |
| `language_version` | 3 | string | Python version string (e.g., "3.12.4") |

### OutputConfig (Harness to SDK, stdout)

Sent by the harness after it has started its WebSocket server.

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `port` | 1 | int32 | Actual WebSocket port the server is listening on |
| `api_key` | 2 | string | Ephemeral API key for WebSocket authentication header |

The `api_key` is a randomly generated string, valid only for this harness process lifetime. It is used in the `x-goog-api-key` header during the WebSocket handshake.

## Initialization Messages

### InitializeConversationEvent (SDK to Harness, first WebSocket message)

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `config` | 1 | HarnessConfig | Complete agent configuration |

This is the ONLY message the SDK sends before the runtime loop begins. The harness uses it to set up the entire agent session.

### HarnessConfig (the central configuration message)

This is the most important message in the protocol. It defines the entire agent session.

| Field | Proto # | Type | Oneof Group | Description |
|-------|---------|------|-------------|-------------|
| `cascade_id` | 1 | string | — | Conversation/session identifier. Used to correlate trajectories |
| `gemini_config` | 2 | GeminiConfig | `model_config` | Cloud Gemini API configuration |
| `gemma_config` | 3 | GemmaConfig | `model_config` | Local Gemma model configuration |
| `custom_backend` | 13 | CustomBackendConfig | `model_config` | External backend (DeepSeek, etc.) |
| `system_instructions` | 4 | SystemInstructions | — | Agent persona and instructions |
| `tools` | 5 | repeated Tool | — | Custom Python tool definitions (schemas only) |
| `harness_side_tools` | 6 | HarnessSideTools | — | Built-in tool enable/disable toggles |
| `compaction_threshold` | 7 | uint32 | — | Token count that triggers context compaction |
| `workspaces` | 8 | repeated Workspace | — | Allowed filesystem directories |
| `skills_paths` | 9 | repeated string | — | Directories to scan for skill files |
| `finish_tool_schema_json` | 10 | string | — | JSON schema for structured output via FINISH tool |
| `initial_trajectory` | 11 | bytes | — | Serialized trajectory for session resumption |
| `app_data_dir` | 12 | string | — | Override for harness app data directory |
| `mcp_servers` | 14 | repeated McpServerConfig | — | MCP server configurations |

### model_config Oneof Pattern

The `model_config` field is a **oneof** that selects the backend. Exactly one must be set:

```protobuf
oneof model_config {
  GeminiConfig gemini_config = 2;
  GemmaConfig gemma_config = 3;
  CustomBackendConfig custom_backend = 13;
}
```

| Variant | Use Case | Proto Field |
|---------|----------|-------------|
| `GeminiConfig` | Cloud Gemini API | 2 |
| `GemmaConfig` | Local Gemma via OpenAI-compatible endpoint | 3 |
| `CustomBackendConfig` | Any external backend (DeepSeek, etc.) | 13 |

If none is set, the harness falls back to environment variable `GEMINI_API_KEY` and default Gemini config.

## Model Configuration Messages

### GeminiConfig

| Field | Proto # | Type | Default | Description |
|-------|---------|------|---------|-------------|
| `api_key` | 1 | string | — | Gemini API key. Falls back to $GEMINI_API_KEY env var |
| `base_url` | 2 | string | — | Custom API endpoint (overrides default Gemini endpoint) |
| `model_name` | 3 | string | "gemini-3.5-flash" | Model identifier |
| `thinking_level` | 4 | string | — | Extended thinking level (minimal/low/medium/high) |
| `enable_url_context` | 5 | bool | false | Enable URL context grounding |
| `enable_google_search` | 6 | bool | false | Enable Google Search grounding |
| `use_vertex` | 7 | bool | false | Route through Vertex AI |
| `project` | 8 | string | — | GCP project ID (required if use_vertex=true) |
| `location` | 9 | string | — | GCP region (required if use_vertex=true) |

### GemmaConfig

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `base_url` | 1 | string | OpenAI-compatible endpoint (e.g., "http://localhost:8080/v1") |
| `model_name` | 2 | string | Model identifier (e.g., "gemma-4-12B-it-qat-4bit") |

This is the dedicated config for local Gemma models. The harness sends requests using the OpenAI Chat Completions format to the specified endpoint. See [[sdk-test-integrations]] for Gemma 4 MTP pipeline details.

### CustomBackendConfig

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `backend_type` | 1 | string | Backend identifier (e.g., "deepseek", "ollama") |
| `config_json` | 2 | string | JSON-encoded backend-specific configuration |

This is the escape hatch for any backend not natively supported. The `config_json` field carries arbitrary configuration that the harness forwards to the backend adapter.

## System Instructions Messages

### SystemInstructions (oneof)

```protobuf
oneof system_instructions {
  CustomSystemInstructions custom = 1;
  AppendedSystemInstructions appended = 2;
}
```

### CustomSystemInstructions

Full replacement of the system prompt. Use with caution -- this replaces ALL default instructions.

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `parts` | 1 | repeated Part | Content parts (text only for system instructions) |

### AppendedSystemInstructions

Appends to the default system prompt. This is the recommended approach.

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `custom_identity` | 1 | string | Custom identity statement (e.g., "You are a security analyst") |
| `sections` | 2 | repeated Section | Additional instruction sections |

### Section

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `title` | 1 | string | Section heading |
| `content` | 2 | string | Section body |

## Tool Definition Messages

### Tool (custom Python tool schema)

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `name` | 1 | string | Function name (e.g., "get_weather") |
| `description` | 2 | string | Function docstring |
| `parameters_json_schema` | 3 | string | JSON Schema for input parameters |
| `response_json_schema` | 4 | string | JSON Schema for response format |

These are schema-only definitions. The harness calls back to the SDK via `ToolCall` when it needs to execute a Python tool.

### HarnessSideTools (11 toggle configs)

Each sub-message has a single `enabled` bool field:

| Sub-message | Proto # | Tool Controlled | Extra Fields |
|-------------|---------|-----------------|--------------|
| `FindToolConfig` | 1 | `find_file` | — |
| `RunCommandToolConfig` | 2 | `run_command` | — |
| `SubagentsConfig` | 3 | `start_subagent` | — |
| `UserQuestionsConfig` | 4 | `ask_question` | — |
| `FileEditToolConfig` | 5 | `edit_file` | — |
| `ViewFileToolConfig` | 6 | `view_file` | — |
| `WriteToFileToolConfig` | 7 | `create_file` | — |
| `GrepSearchToolConfig` | 8 | `search_directory` | — |
| `ListDirToolConfig` | 9 | `list_directory` | — |
| `GenerateImageToolConfig` | 10 | `generate_image` | `model_name` (string, default: "gemini-3.1-flash-image-preview") |
| `PermissionsConfig` | 11 | workspace validation | `enforce_workspace_validation` (bool) |

### Workspace

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `filesystem_workspace` | 1 | oneof | Currently only `FilesystemWorkspace` variant |
| `directory` | 1 (inner) | string | Absolute path to allowed directory |

## Runtime Messages: Harness to SDK

### OutputEvent (the main event envelope)

Every message from the harness to the SDK is wrapped in an `OutputEvent`.

| Field | Proto # | Type | Oneof Group | Description |
|-------|---------|------|-------------|-------------|
| `seq_num` | 1 | int64 | — | Monotonically increasing sequence number |
| `timestamp_micros` | 2 | int64 | — | Timestamp in microseconds since epoch |
| `step_update` | 10 | StepUpdate | `event` | Agent step update |
| `trajectory_state_update` | 11 | TrajectoryStateUpdate | `event` | Trajectory lifecycle state change |
| `tool_call` | 12 | ToolCall | `event` | Host-side tool invocation request |
| `usage_metadata` | 20 | UsageMetadata | — | Token usage (may accompany any event) |

The `event` oneof ensures each `OutputEvent` carries exactly one type of event.

### StepUpdate (the workhorse message)

The most complex message in the protocol. At ~1.7KB in the proto definition, it covers every possible agent action.

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `cascade_id` | 1 | string | Parent trajectory (conversation) ID |
| `trajectory_id` | 2 | string | This trajectory's ID (differs for subagents) |
| `step_index` | 3 | uint32 | Step number within the trajectory |
| `state` | 4 | State enum | UNSPECIFIED/ACTIVE/DONE/WAITING_FOR_USER/ERROR |
| `source` | 5 | Source enum | UNSPECIFIED/SYSTEM/USER/MODEL |
| `target` | 6 | Target enum | UNSPECIFIED/USER/MODEL/ENVIRONMENT |
| `error_message` | 7 | string | Error details (when state=ERROR) |
| `thinking` | 8 | string | Full thinking text (non-streaming) |
| `text_delta` | 9 | string | Incremental text token (streaming) |
| `thinking_delta` | 10 | string | Incremental thinking token (streaming) |
| `text` | 11 | string | Full accumulated text (non-streaming) |
| `request_text` | 12 | string | User-facing request text |
| `tool_confirmation_request` | 13 | ToolConfirmationRequest | Approval needed for tool execution |
| `questions_request` | 14 | UserQuestionsRequest | Questions for the user |

#### State Enum

| Value | Name | Meaning |
|-------|------|---------|
| 0 | UNSPECIFIED | Default/unset |
| 1 | ACTIVE | Step is currently executing |
| 2 | DONE | Step completed successfully |
| 3 | WAITING_FOR_USER | Waiting for user input (confirmation or question) |
| 4 | ERROR | Step failed |

#### Source Enum

| Value | Name | Meaning |
|-------|------|---------|
| 0 | UNSPECIFIED | Default |
| 1 | SYSTEM | System-generated step |
| 2 | USER | User-initiated step |
| 3 | MODEL | Model-generated step |

#### Target Enum

| Value | Name | Meaning |
|-------|------|---------|
| 0 | UNSPECIFIED | Default |
| 1 | USER | Directed at the user |
| 2 | MODEL | Directed at the model |
| 3 | ENVIRONMENT | Directed at the environment (tools) |

#### Action Sub-messages (oneof)

Each `StepUpdate` carries exactly ONE action sub-message:

| Action | Proto Field | Message Type | Key Fields |
|--------|-------------|--------------|------------|
| `list_directory` | 20 | `ActionListDirectory` | `directory_path`, `results[]` (name, is_directory, file_size) |
| `find_file` | 21 | `ActionFindFile` | `directory_path`, `query`, `output` |
| `search_directory` | 22 | `ActionSearchDirectory` | `directory_path`, `query`, `num_results` |
| `view_file` | 23 | `ActionViewFile` | `file_path`, `start_line`, `end_line` |
| `create_file` | 24 | `ActionCreateFile` | `file_path`, `contents` |
| `edit_file` | 25 | `ActionEditFile` | `file_path`, `diff_blocks[]` |
| `run_command` | 26 | `ActionRunCommand` | `command_line`, `working_dir`, `exit_code`, `combined_output` |
| `compaction` | 27 | `ActionCompaction` | (empty) |
| `invoke_subagent` | 28 | `ActionInvokeSubagent` | (empty) |
| `generate_image` | 29 | `ActionGenerateImage` | `prompt`, `image_paths[]`, `image_name` |
| `finish` | 30 | `ActionFinish` | `output_string` (contains structured output JSON if schema set) |
| `error` | 31 | `ActionError` | `error_message`, `http_code` |
| `mcp_tool` | 32 | `ActionMcpTool` | `server_name`, `tool_name`, `arguments_json` |

#### ActionEditFile Detail

The `edit_file` action carries a diff structure:

```protobuf
message ActionEditFile {
  string file_path = 1;
  repeated DiffBlock diff_blocks = 2;
}

message DiffBlock {
  uint32 start_line = 1;
  uint32 end_line = 2;
  repeated DiffLine lines = 3;
}

message DiffLine {
  string text = 1;
  LineAction action = 2;  // KEEP, INSERT, DELETE
}
```

#### ActionRunCommand Detail

```protobuf
message ActionRunCommand {
  string command_line = 1;
  string working_dir = 2;
  int32 exit_code = 3;
  string combined_output = 4;  // stdout + stderr merged
}
```

### TrajectoryStateUpdate

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `trajectory_id` | 1 | string | Trajectory identifier |
| `state` | 2 | TrajectoryState enum | UNSPECIFIED/RUNNING/IDLE |
| `error` | 3 | string | Error message if trajectory failed |

The SDK uses these to determine idle state. A connection is idle only when ALL trajectories (parent + subagents) are in IDLE state.

### ToolCall (host-side tool invocation)

Sent when the harness needs the SDK to execute a Python tool.

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `id` | 1 | string | Unique call identifier (correlates with ToolResponse) |
| `name` | 2 | string | Tool function name |
| `args` | 3 | map<string, string> | Serialized arguments |
| `server_name` | 4 | string | MCP server name (if MCP tool) |

### UsageMetadata

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `prompt_token_count` | 1 | uint64 | Input tokens consumed |
| `cached_content_token_count` | 2 | uint64 | Cached subset of input tokens |
| `candidates_token_count` | 3 | uint64 | Output tokens (excluding thinking) |
| `thoughts_token_count` | 4 | uint64 | Thinking/reasoning tokens |
| `total_token_count` | 5 | uint64 | Sum of all token categories |

## Runtime Messages: SDK to Harness

### InputEvent (the main input envelope)

Every message from the SDK to the harness is an `InputEvent` with a oneof payload:

| Variant | Proto # | Type | Description |
|---------|---------|------|-------------|
| `user_input` | 1 | string | Simple text prompt |
| `complex_user_input` | 2 | UserInput | Multimodal input (text + media) |
| `tool_confirmation` | 3 | ToolConfirmation | Approve/deny a tool call |
| `tool_response` | 4 | ToolResponse | Result of a Python tool execution |
| `question_response` | 5 | UserQuestionsResponse | Answer to agent questions |
| `halt_request` | 6 | bool | Cancel current model turn |
| `automated_trigger` | 7 | string | Trigger notification message |

### UserInput (multimodal input)

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `parts` | 1 | repeated Part | Content parts |

### Part (oneof content type)

```protobuf
message Part {
  oneof content {
    string text = 1;
    Media media = 2;
    SlashCommand slash_command = 3;
  }
}
```

### Media

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `mime_type` | 1 | string | MIME type (e.g., "image/png") |
| `description` | 2 | string | Optional description |
| `data` | 3 | bytes | Raw media data |

### SlashCommand

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `name` | 1 | string | Slash command name (without leading /) |

### ToolConfirmation

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `trajectory_id` | 1 | string | Target trajectory |
| `step_index` | 2 | uint32 | Target step |
| `accepted` | 3 | bool | true = approve, false = deny |

### ToolResponse

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `id` | 1 | string | Correlates with ToolCall.id |
| `response_json` | 2 | string | JSON-serialized result |
| `supplemental_media` | 3 | repeated Media | Attachments |
| `response` | 4 | Struct | Structured result (protobuf Struct) |

### UserQuestionsResponse

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `trajectory_id` | 1 | string | Target trajectory |
| `step_index` | 2 | uint32 | Target step |
| `cancelled` | 3 | bool | User cancelled (oneof) |
| `response` | 4 | QuestionsResponse | User's answers (oneof) |

## MCP Configuration Messages

### McpServerConfig

| Field | Proto # | Type | Oneof Group | Description |
|-------|---------|------|-------------|-------------|
| `name` | 1 | string | — | Server identifier |
| `stdio` | 2 | McpStdioTransport | `transport` | stdio-based transport |
| `http` | 3 | McpHttpTransport | `transport` | HTTP-based transport |
| `enabled_tools` | 4 | repeated string | — | Tool allowlist |
| `disabled_tools` | 5 | repeated string | — | Tool denylist |
| `auth_provider_type` | 6 | AuthProviderType enum | — | UNSPECIFIED/GOOGLE_CREDENTIALS |

### McpStdioTransport

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `command` | 1 | string | Executable path |
| `args` | 2 | repeated string | Command arguments |
| `env` | 3 | map<string, string> | Environment variables |

### McpHttpTransport

| Field | Proto # | Type | Description |
|-------|---------|------|-------------|
| `url` | 1 | string | HTTP endpoint URL |
| `headers` | 2 | map<string, string> | Request headers |

### AuthProviderType Enum

| Value | Name | Description |
|-------|------|-------------|
| 0 | UNSPECIFIED | No auth |
| 1 | GOOGLE_CREDENTIALS | Use Google OAuth2 credentials |

## Serialization Rules

### JSON Mapping

Protobuf JSON mapping rules apply:
- Field names: `snake_case` in proto → `camelCase` in JSON
- Enum values: Sent as string names (e.g., "ACTIVE" not 1)
- Bytes fields: Base64-encoded
- Default values: Omitted from wire format
- oneof: Only the set variant appears

### Content Type

All WebSocket messages use the default WebSocket text frame with JSON content. Binary frames are not used.

### Message Size

No explicit size limit is enforced by the protocol. Practical limits:
- Individual messages: Typically < 1MB
- `initial_trajectory`: Can be several MB (serialized conversation history)
- `ActionCreateFile.contents`: Can be large for file creation
- `combined_output` in `ActionRunCommand`: Can be large for command output

## Cross-References

- [[sdk-test-localharness]] — How these messages flow through the connection lifecycle
- [[sdk-test-antigravity-sdk]] — Python SDK types that map to these protobuf messages
- [[sdk-test-hooks-deep]] — How hooks intercept these messages
- [[sdk-test-auth-chain]] — Authentication headers carried alongside these messages
- [[sdk-test-grpc-protocol]] — The gRPC protocol used between harness and Gemini API
- [[sdk-test-architecture]] — JETSKY ecosystem architecture

---

*Source: `localharness_pb2.py` (430 lines), auto-generated from `localharness.proto`. Python SDK: `local_connection.py` (1792 lines).*
