Spin up a live local Omnigent server + runner and exercise the native Antigravity (agy) TUI harness (antigravity-native) end-to-end — launch the real `agy` CLI via `omnigent antigravity`, drive turns through the web UI, smoke-test, and bug-bash. Load when developing, testing, or debugging the antigravity-native harness (omnigent/inner/antigravity_native_executor.py, omnigent/antigravity_native.py, antigravity_native_bridge.py, antigravity_native_rpc.py, antigravity_native_reader.py, antigravi...
Install to Claude Code
npx -y skills add omnigent-ai/omnigent --skill antigravity-native-e2e-dev --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Antigravity Native E2e Dev?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/omnigent-ai-antigravity-native-e2e-dev)More formats (shields.io, HTML) on the badges page.
---
name: antigravity-native-e2e-dev
description: Spin up a live local Omnigent server + runner and exercise the native Antigravity (agy) TUI harness (antigravity-native) end-to-end — launch the real `agy` CLI via `omnigent antigravity`, drive turns through the web UI, smoke-test, and bug-bash. Load when developing, testing, or debugging the antigravity-native harness (omnigent/inner/antigravity_native_executor.py, omnigent/antigravity_native.py, antigravity_native_bridge.py, antigravity_native_rpc.py, antigravity_native_reader.py, antigravity_native_launch.py) or its agy launch / RPC mirror / tmux delivery / OAuth / MCP-relay behavior. NOT the in-process `antigravity` Gemini SDK harness.
---
# Antigravity native harness: end-to-end dev & testing (local server/runner)
The `antigravity-native` harness wraps the **real Antigravity `agy` TUI** (the
`agy` CLI, installed from `antigravity.google/cli/install.sh`). `omnigent
antigravity` ensures a host daemon, the daemon-spawned **runner** launches `agy`
in a runner-owned **tmux** terminal, and your TTY attaches to it. This is **not**
the in-process `antigravity` Gemini-SDK harness — that one runs `google-antigravity`
with a Gemini *API key*; this one drives the OAuth-only `agy` CLI and mirrors it
over **connect-RPC**. This skill is the proven recipe for running it **for real
against a live local server + runner** — not just the unit tests.
> Like the other native harnesses, the runner imports from your **current
> checkout**, so testing here exercises exactly the code you're on. (CWD/venv
> selects the code, not `PYTHONPATH`.)
## What actually runs where
```
your TTY ── (attach / pexpect) ──► omnigent antigravity (CLI, local)
│ ensures
▼
host daemon ──► local Omnigent server (AP)
│ spawns ▲
▼ connect-RPC │ HTTP
runner ── launches ──► agy (TUI, in tmux)
│ │
├── write path: type web turns into the TUI
│ (tmux bracketed paste → real USER_INPUT step)
└── read path: RPC read driver mirrors agy's
trajectory steps back into the session
```
Three transports, easy to confuse:
1. **Write path = typing into the TUI.** Every web/mobile turn is *typed* into the
agy pane via tmux (`inject_user_message_via_tui`), creating a real
`CORTEX_STEP_TYPE_USER_INPUT` step on the **same** cascade the TUI shows
(#1156/#1158). It is **not** delivered over `SendUserCascadeMessage` (that
headless RPC path was retired; the `antigravity_native.py` module header still
says "delivered via the RPC" — that's stale doc-lag, the executor is authoritative).
2. **Read path = RPC.** `antigravity_native_reader` polls/streams agy's connect-RPC
trajectory steps and mirrors them into the Omnigent session.
3. **Control = RPC.** Interrupt is `CancelCascadeSteps`; a tool/permission prompt
is answered via `HandleCascadeUserInteraction` (surfaced as an Omnigent
elicitation).
## Prerequisites (check these first)
1. **You're on the branch you want to test**, running from that checkout
(`.venv/bin/omnigent` / `.venv/bin/python` from this repo).
2. **The `agy` CLI is on PATH** (or at `~/.local/bin/agy`) — the harness can't
launch without it:
```bash
which agy || ls -l ~/.local/bin/agy
agy --version
# install if missing (shell installer, NOT npm):
# curl -fsSL https://antigravity.google/cli/install.sh | bash # then restart shell
.venv/bin/python -c "from omnigent.onboarding.harness_readiness import harness_is_configured; print('antigravity-native ready:', harness_is_configured('antigravity-native'))"
```
3. **`agy` is signed in (OAuth).** agy is **OAuth-only** — it has no `agy login`;
you authenticate by running bare `agy` once and completing the browser sign-in.
It **ignores `GEMINI_API_KEY`** (API-key auth belongs to the separate
`antigravity` SDK harness). Verify (no secrets printed):
```bash
.venv/bin/python -c "from omnigent.onboarding.gemini_auth import gemini_login_detected; print('agy oauth token present:', gemini_login_detected())"
agy models # exits 0 and lists models only when signed in; else 'Please sign in'
```
`False` / non-zero → run `agy` once and sign in. agy's token lives under
`~/.gemini` (`oauth_creds.json` on macOS through 1.0.10,
`antigravity-cli/antigravity-oauth-token` on Linux); agy 1.1.7+ on macOS
writes no token file and keeps the credential in the Keychain, which is why
`gemini_login_detected()` falls back to `agy models` there.
4. **`tmux` is on PATH.** The agy terminal is a runner-owned tmux pane; the CLI
attaches to it and the executor drives it via `tmux send-keys`
(`_preflight_local_tools` hard-fails without tmux).
5. **Network egress to Google's Antigravity backend.** A turn that hangs / fails
to connect on a locked-down host is usually egress, not a harness bug.
> No `node` and no provider/gateway config are needed here (unlike pi/cursor
> native): agy is a self-hosted binary and auth is the inherited Google OAuth.
## Step 1 — start a local server (real server + runner)
```bash
cd /path/to/omnigent
.venv/bin/omni server --background # detached managed server on a free loopback port
.venv/bin/omni server status # prints the URL, e.g. http://127.0.0.1:6767
SERVER=http://127.0.0.1:6767 # use the printed URL below
curl -s "$SERVER/health" # {"status":"ok"}
```
(`omnigent antigravity --server ""` also auto-spawns a persistent local server and
uses it — handy for a one-shot manual run, but a known `$SERVER` URL is better for
scripted API observation below.)
## Step 2 — launch the agy terminal against the local server
`omnigent antigravity` **attaches an interactive TUI**, so run it where you can
hold it open. Two patterns:
**A. Background terminal (recommended for scripted drives).** Launch in one
terminal, drive/observe from another:
```bash
.venv/bin/omnigent antigravity --server "$SERVER" 2>&1 # attaches the agy TUI; leave it running
# add a model: --model gemini-2.5-pro ; pass-through agy args go at the end
```
It prints `Web UI: <url>` and a resume hint to stderr — grab the conversation id
(the `…/c/<conv_…>` segment) for the API calls below:
```bash
CONV=conv_xxxxxxxx # from the "Web UI:" line / resume hint
```
**B. PTY driver (fully automated).** Drive it under `pexpect` like the
`claude-native-e2e-test` skill's `cuj_driver.py`: spawn `omnigent antigravity
--server <url>` in a PTY with `cwd=<checkout>`, capture the conv id from the
printed URL, then drive/poll the API, then **tear down the whole process tree**
(see Teardown — a pexpect Ctrl-C only *detaches* tmux).
> The runner **owns** the agy terminal: binding a runner auto-creates the
> antigravity terminal for the session, and the CLI *reattaches* rather than
> launching its own. Don't hand-launch a second `agy` against the same session —
> a double launch 500s and clobbers the runner's bridge state (web-turn injection
> then fails "bridge state is missing").
## Step 3 — drive a turn (and smoke-test)
**Via the web path (exercises `AntigravityNativeExecutor`).** Post a user message
to the running session; the runner routes it to the harness, whose `_deliver`
types it into the agy TUI (real `USER_INPUT` step):
```bash
curl -s -X POST "$SERVER/v1/sessions/$CONV/events" \
-H 'content-type: application/json' \
-d '{"type":"message","data":{"role":"user","content":[{"type":"input_text","text":"Reply with exactly the single word: PONG"}]}}'
```
Then **observe** the mirrored transcript (the RPC read driver posts agy's steps
back):
```bash
sleep 25
curl -s "$SERVER/v1/sessions/$CONV/items" | python -m json.tool | tail -40
```
A healthy run shows your `user` message **and** a non-empty `assistant` reply
(`PONG`) mirrored into the session — proving the full stack: server → runner →
executor → tmux paste → agy turn → connect-RPC read driver → transcript mirror.
You'll also see the prompt + reply render in the attached agy TUI (parity is the
whole point of the TUI-typing write path).
- **Type-driven smoke:** instead of the POST, type a prompt directly in the
attached agy TUI and confirm it answers + mirrors to `…/items`.
- **Model:** select a model with agy's TUI `/model`; the next web turn echoes that
choice (the executor reads it from the latest `USER_INPUT` step).
## Inspect the bridge (debugging)
Per-session bridge state lives under a hashed dir (keyed by *bridge id*, which
defaults to the Omnigent conversation id):
```bash
.venv/bin/python -c "from omnigent.antigravity_native_bridge import bridge_dir_for_bridge_id as d; print(d('$CONV'))"
# ~/.omnigent/antigravity-native/<sha256(bridge_id)[:32]>/
# state.json <- {session_id, conversation_id (agy's real UUID once minted), active_turn_id}
# tmux.json <- {socket_path, tmux_target} the executor types into (send-keys)
# bridge.json <- token for the Omnigent MCP relay (sys_* tools)
# agy-home/.gemini/... <- per-session ISOLATED HOME: a COPY of your OAuth token
# + onboarding markers + config/mcp_config.json (relay)
```
Key facts:
- agy mints its **own** UUID cascade; a fresh launch seeds an `agy_conv_*`
**placeholder** until cold-start `StartCascade`s the real id and writes it to
`state.json` (and PATCHes it as `external_session_id`). RPC calls against a
placeholder are skipped — "not ready yet".
- The **isolated HOME** (`agy-home/`) is why your real `~/.gemini` is never
touched: the relay's `mcp_config.json` and agy's per-session state live there.
agy's `/mcp` panel should show `✓ omnigent` with the `sys_*` tools.
- Env vars: `HARNESS_ANTIGRAVITY_NATIVE_BRIDGE_DIR`,
`HARNESS_ANTIGRAVITY_NATIVE_REQUEST_SESSION_ID`.
## Targeted scenarios
| Goal | How |
|------|-----|
| Web→TUI delivery | POST a message (Step 3); confirm it renders in the agy TUI AND mirrors to `…/items` |
| Native tools (shell/edit/read) | prompt agy to create→read→edit a file + run a command; confirm it touches disk |
| Omnigent MCP relay (`sys_*`) | in the agy TUI run `/mcp` → expect `✓ omnigent`; prompt agy to `sys_session_list` / spawn a sub-agent |
| Permission elicitation | with a tool that needs approval, agy's `request-review` surfaces as an **Omnigent elicitation** (interaction bridge); answer it in the web UI and confirm the tool runs |
| Interrupt | mid-turn, hit stop in the UI → `CancelCascadeSteps` (RUNNING cascades only; a step WAITING on an interaction is unblocked by a DENY, not cancel) |
| Model echo | `/model` in the TUI, then a web turn — confirm the new model is used (latest `USER_INPUT` step's `planModel`) |
| Resume | stop, `omnigent antigravity --server "$SERVER" --resume "$CONV"`; `--resume` (no value) opens the antigravity-native picker |
| Concurrency / leaks | drive several sessions; sweep for orphaned `agy` / tmux after teardown |
## Gotchas (these cost real time)
1. **It's a TUI, not `omni run`.** Use `omnigent antigravity`. The executor only
delivers into the live agy pane — agy must be running (attached) for a turn to
process.
2. **`config.yaml`'s `server:` defaults to a remote server.** Always pass
`--server "$SERVER"` (or `--server ""` for local). If a *local* server rejects
`antigravity-native`, it's stale — restart it from your checkout
(allowlist: `omnigent/spec/_omnigent_compat.py`).
3. **OAuth-only.** agy ignores `GEMINI_API_KEY`; if `agy models` says "sign in",
no web turn will get a real answer. Run bare `agy` once first.
4. **tmux must be reachable from the CLI process** for the direct attach; the
executor's send-keys run on the runner side against the advertised socket.
5. **Isolated HOME.** Don't expect your real `~/.gemini` to change — agy runs
under `<bridge_dir>/agy-home`. Look there (and `~/.gemini/antigravity-cli` for
agy's own conversation store) when debugging.
6. **Don't double-launch agy** for a session — the runner owns the terminal (see
Step 2).
7. **Turns take ~20–120s** — wrap scripted waits/`timeout` generously.
8. **Never print/echo the OAuth token.** Use the boolean/`agy models` probes.
## Code & tests
- **Executor (write path — types into the TUI):** `omnigent/inner/antigravity_native_executor.py`
- **Harness wrap (`harness: antigravity-native`):** `omnigent/inner/antigravity_native_harness.py`
- **CLI launch / daemon-runner / tmux attach:** `omnigent/antigravity_native.py`
(`run_antigravity_native`); CLI command `antigravity(...)` in `omnigent/cli.py`
- **agy argv / auth-mode / permission flag:** `omnigent/antigravity_native_launch.py`
- **Bridge (state, tmux delivery, isolated HOME, MCP relay):** `omnigent/antigravity_native_bridge.py`
- **connect-RPC client (port discovery, send/cancel/interaction):** `omnigent/antigravity_native_rpc.py`
- **RPC read driver (trajectory mirror):** `omnigent/antigravity_native_reader.py`
- **Steps / interactions / audit:** `omnigent/antigravity_native_steps.py`,
`omnigent/antigravity_native_interactions.py`, `omnigent/antigravity_native_audit.py`
- **OAuth detection:** `omnigent/onboarding/gemini_auth.py`
- **Design/plan docs:** `docs/antigravity-native-rpc-core-design.md`,
`docs/antigravity-native-rpc-core-plan.md`
```bash
.venv/bin/python -m pytest \
tests/test_antigravity_native.py \
tests/test_antigravity_native_bridge.py \
tests/test_antigravity_native_launch.py \
tests/test_antigravity_native_rpc.py \
tests/test_antigravity_native_reader.py \
tests/test_antigravity_native_steps.py \
tests/test_antigravity_native_interactions.py \
tests/test_antigravity_native_audit.py \
tests/inner/test_antigravity_native_executor.py -q
```
## Bug-bash (fan out)
Stress the harness against the same `$SERVER`: the web→TUI delivery path (lost /
duplicated turns, the attended-TUI paste race), the RPC read mirror (does every
agy step reach `…/items`? duplicates after a reader restart?), the MCP relay
(`sys_*` reachable + gated), permission elicitations, interrupt
(`CancelCascadeSteps`) vs. a WAITING-on-interaction step, model echo, resume, and
orphaned `agy`/tmux after teardown. Cross-check the API — a start failure can
leave the TUI empty while the session records an error.
## Watch-outs from the code (verify live)
- **Placeholder until cold-start.** Before agy mints its real cascade id, bridge
state holds an `agy_conv_*` placeholder and RPC is skipped; a turn fired too
early just queues into the TUI.
- **Permission gating is all-or-nothing + post-hoc.** agy honors only
`--dangerously-skip-permissions` (no firing pre-tool hook), so a headless launch
auto-bypasses and the genuine Omnigent gate is the elicitation + post-hoc audit
(`antigravity_native_audit`), not a per-tool pre-empt.
- **Stale module header.** `antigravity_native.py`'s top docstring says web turns
go over `SendUserCascadeMessage` RPC — the live executor types into the TUI
instead (#1156/#1158). Trust `antigravity_native_executor.py`.
## Teardown — non-negotiable
A pexpect Ctrl-C **detaches** from tmux; the runner, tmux server, and `agy` keep
running. Tear down the process tree from the child PID (`ps --ppid …` →
SIGTERM/SIGKILL) and separately `tmux -S <sock> kill-server`. Then verify:
```bash
.venv/bin/omni server stop # stop the managed server + local daemon
pgrep -af "(^|/)agy( |$)|harnesses\._runner|runner\._entry|tmux" # confirm no orphans
# clean a session's bridge dir (incl. its isolated agy HOME) if you want a reset:
# rm -rf "$(.venv/bin/python -c "from omnigent.antigravity_native_bridge import bridge_dir_for_bridge_id as d; print(d('$CONV'))")"
```
## Honesty
If you can't reach a ready agy TUI (missing `agy`, not signed in, no `tmux`,
headless limits, no egress), say so — don't claim a turn passed. The strongest
evidence is the round trip observed over the API: your `user` message **and** a
non-empty `assistant` reply mirrored into `GET /v1/sessions/$CONV/items`, plus the
turn rendering in the attached agy TUI.
Scanned 8/31/2026
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!