# Antigravity-native harness: RPC core rework **Status:** Design (approved direction; pending implementation plan) **Date:** 2026-06-22 **Supersedes:** the runtime core of PR #892 (antigravity-native). Periphery from #892 is reused. **Source of truth for wire shapes:** live-verified against agy 1.0.10 — see memory `agy-rpc-interaction-bridge.md`. ## 1. Motivation The current native harness (PR #892) drives the `agy` CLI through a tmux terminal with two indirect channels: - **Read path** — tails agy's JSONL transcript (`~/.gemini/antigravity-cli/brain//.system_generated/logs/transcript_full.jsonl`) and mirrors steps into the session. - **Write path** — types web turns into the agy TUI via tmux `send-keys`. This works for plain text turns but has a **fragility class** and **functional gaps**: 1. **Out-of-order / non-contiguous `step_index`** in the JSONL forced a durable SET resume cursor, gap-free-prefix delivery, and audit gating — and still produced the **live double-render** (delta preview vs committed message) plus a **user-message duplication** (direct `/events` post vs the forwarder mirroring agy's `USER_INPUT`). Root-caused as inherent to the transcript-mirror approach; *predates* the out-of-order change (verified by running both revisions). 2. **No interactive-prompt support.** agy's `ask_question` (multi-select) and tool `request-review` approvals are TUI widgets with no web response path; the session sits `idle` while agy blocks. 3. **No real interrupt** (`interrupt_session` is stubbed to `False`). A spike found agy exposes a **structured connect-RPC surface** that replaces all three channels cleanly. This design reworks the harness core onto that RPC, eliminating the transcript-mirror fragility and adding interactions + interrupt, while keeping the terminal-first UX (agy still runs in a tmux terminal). ## 2. Validated RPC surface connect-RPC service `exa.language_server_pb.LanguageServerService`, TLS HTTP/2 on a loopback port, self-signed (`verify=False`), JSON accepted (`Content-Type: application/json`). All of the following are live-verified except where noted. - **Identity:** `cascadeId == conversationId == brain-dir UUID` (one id). `GetCascadeId`/`GetCascadeStatus`/`ListCascadeIds` do **not** exist (404). - **Port discovery:** reuse `omnigent/antigravity_native_rpc.py` — `discover_language_server_port(pid)` / `_candidate_agy_rpc_ports()` + `_conversation_matches` (Heartbeat → 200; `GetConversationMetadata` echoes `rootConversationId`). - **Read / detect:** - `GetCascadeTrajectorySteps {cascadeId}` (unary) → `steps[]` — one-shot. - `StreamAgentStateUpdates {conversationId}` (connect server-stream, `application/connect+json`, 5-byte `[flag][BE-len]` framing) → first frame `update.mainTrajectoryUpdate.stepsUpdate.steps[]`, then long-polls — live. - (`StreamCascadeReactiveUpdates` is **deprecated** — returns `{"error":{"message":"reactive state is deprecated"}}`. Do not use.) - **Step shape:** `status` ∈ `CORTEX_STEP_STATUS_{RUNNING,WAITING,DONE,ERROR}`; `type` ∈ `CORTEX_STEP_TYPE_{PLANNER_RESPONSE,RUN_COMMAND,...}`; `requestedInteraction` (`askQuestion` | `permission`) when `WAITING`; `runCommand.{commandLine, proposedCommandLine, cwd, exitCode, combinedOutput.full}`; `metadata.sourceTrajectoryStepInfo.{trajectoryId, stepIndex, cascadeId}`; `completedInteractions[].response` (echoes the delivered answer). - **Answer interaction:** `HandleCascadeUserInteraction {cascadeId, interaction:{trajectoryId, stepIndex, }}` (unary) → `200 {}`. - **Question:** `interaction.askQuestion.responses:[{question:"", selectedOptionIds:["