Run Antigravity on ACP via agy_acp_server
## Original requirement (epic 8783, verbatim)
> Review implementation of model provider interfaces:
>
> - Claude, Codex, Antigravity, Pi, ACP,
>
> Identify opportunity to unify, move more code from specific to common, make it more industry standard, more reliable. Do web research to potentially adopt a better architecture.
>
> Implement top ideas.
Settled inputs: PRD (BA task 8784) and architecture (task 8785) — read them with `task_read` 8784 / 8785 and their `prd` / `architecture` artifacts. Decisions there are settled; realize them, do not reopen them.
**Quality bar (PRD, verbatim).** A change that passes AC5 by moving code into a grab-bag module with vendor `if` branches inside it FAILS. Common code must be vendor-neutral, and vendor facts stay as data on the adapter or catalogue row. A review artifact that lists ideas without evidence or ranking FAILS AC1/AC2.
## Scope — R4: Antigravity on ACP (architecture child A; operator decision A1: Google's official ACP server)
**Requirement (PRD R4, verbatim):** Antigravity oauth and api_key runs execute on the ACP host through a provider shim, per S1.
**S1 (verbatim):** no TUI scraping for the shim. The shim must use a structured, non-interactive interface … If no pinnable `agy` has a structured interface, the shim is reported infeasible with the measurement as evidence and filed as a follow-up. Pretending it works, or scraping the TUI, is not acceptable.
The "shim" is Google's pinned upstream adapter `agy_acp_server` (ACP registry id `antigravity-acp`, v1.4.1), same pattern as `claude-agent-acp`/`codex-acp`. Jaah writes no stream-json shim. The `agy` CLI pin is NOT bumped (measured: no ACP at 1.2.11 or 1.4.4). Measurements already made are in architecture §2.2 (amd64 zip sha256 `2e37012ceb2a5c7fc7309b6e6596234fb637ee3c62a5199f65d4cbe777871384`, 336 MB → `agy_acp_server.par` 931 MB + `localharness_external` 131 MB; `--uid=` needed; auth methods; `_meta["api-key"]` works with nothing on disk; vendor API errors arrive as agent text with `end_turn`).
Depends on the I3 child (`AcpDialect` seam) — rebase on its merge.
0. **Accounts for measurement.** Measurements 1(b)/1(c) need dev Antigravity accounts. Inventory them yourself in the dev console (`https://app.dev.jaah.ai`, credential `jaah/debug/main/operator-login`, Settings › Accounts); if missing, copy from main (`https://app.jaah.ai`) **through the console UI** (operator A2: "Copy from main if they don't exist"), or sign one in on dev. Only if none can be obtained, record that and take the infeasible path (item 10) for the blocked measurement — never default to it untried.
1. **Measure first** and record in the PR body (version, commands, output): (a) `--uid=` as a non-root runner; (b) headless `authenticate oauth-personal`: does it print an authorize URL and accept a pasted code, or need a browser on the node? Does it adopt the CLI's `antigravity-oauth-token` or keyring `antigravity` entry?; (c) with a real dev Gemini key, whether a turn reports usage (`usage_update`, `_meta`, or nothing); (d) whether `session/load`/`resume` reattaches after the host restarts; (e) MCP: the control server passed in `session/new` `mcpServers` is visible to the model, and under what tool prefix.
2. **Pin.** New `[agy-acp-server]` table in `packaging/platform-pins.toml`: version 1.4.1; `release` URL per arch; zip `sha256` per arch (measure arm64 here); `bin_sha256` of `agy_acp_server.par`. Extend `runner_cli_pins.py` (`agy-shell`, or a sibling verb emitting `AGY_ACP_*`) and `packaging/postinst` `converge_runner_agy_cli` to install it beside `agy`, sha-verified, idempotent on `bin_sha256`, with a disk-headroom check that fails closed with a clear message (risk R2, ~1.06 GB/node; record in PR).
3. **Catalogue** (`apps/jaahd/agent_providers.py` ANTIGRAVITY, about `:805-840`): `acp_binary` = installed server path; `runtime` `(oauth, acp)`, `(api_key, acp)` — unless (b) fails, then oauth stays `legacy_only` (D4); drop the `no-acp` gap; `sessions`, `auth.key_delivery="acp_authenticate"` and `usage` set to what was measured; MicroVM: apply Gemini's rule (if the key has no host home on a guest, add `microvm-api-key-legacy-runtime`).
4. **Adapter** (`apps/jaahd/agents/antigravity_runner.py`): add `acp_launch(ctx) -> AcpLaunch` modelled on `gemini_runner.py:573-632` (seam 3 unchanged: `AcpLaunch(argv, env, session_meta, mcp_servers, authenticate=None)`): `argv=(server, "--uid=")`; env `GEMINI_HOME=<config_dir>/.gemini` plus the keyring overlay from `antigravity_keyring.account_env`, omit-never-empty; `authenticate={"method_id": "gemini-api-key", "key_meta": "api-key", "account": ctx.api_key_account}` for an API-key run; control server in `mcp_servers`; mode `yolo` through `acp/modes.py`; charter via ACP `systemPrompt` append (`_meta.antigravity.systemPrompt.append`). Keep the legacy launch (D5).
5. **Login:** if (b) needs a new sign-in, the login adapter (`antigravity.py`) mints the ACP server's token through ACP `authenticate`, as `gemini.py:81-188` does; retire the PTY scraper only if nothing else uses it. If the server adopts the CLI token, login is unchanged.
6. **Control plane / operator switch:** `apps/api/app/acp_runtime.py` needs no logic change (`LEGACY_ONLY`/`NO_ACP_GAP` derive from the catalogue; `_effective` defaults unstored to ON). Add API unit tests: both pairs resolve `acp` with nothing stored; both resolve `legacy` with the switch stored OFF; `dispatch_runtime(..., node_os=<non-Linux>)` resolves `legacy`; `microvm_runtime` follows the gap. The API catalogue re-export follows. No new UI.
7. **Tests:** replace `tests/test_antigravity_adapter.py`'s "no ACP at pin" test with a pin-coupled `[agy-acp-server]` test; an `acp_launch` unit test asserting no key in env or `launch.json`; an ACP replay fixture under `apps/jaahd/tests/acp_replay/` recorded from the real server; `tools/tests/test_provider_capabilities.py` stays green.
8. **Docs:** rewrite `apps/jaahd/README.md` §"Antigravity: measured, no ACP" (`:1066-1072`) to the present (name the server version measured); fix `:552` "No ACP host exists yet"; edit ADR-0135/ADR-0165 entries in place if their text names the gap.
9. **Dialect:** set `ANTIGRAVITY.capabilities.acp_dialect` `error_text_patterns` for the measured "API key not valid" text and any other vendor error text found in step 1. The replay fixture asserts such a turn records a break, not a success. Add no literal to `apps/jaahd/acp/`.
10. **Infeasible path:** if 1(b) fails, the oauth pair stays `legacy_only` with the measurement in its gap consequence and the PR body, and you file one follow-up Task under epic 8783 (`task_create_follow_up`): "Antigravity OAuth on ACP: headless `authenticate oauth-personal` against `agy_acp_server` <version> failed (<observed>); find a sign-in route or an upstream fix", evidence `apps/jaahd/agents/antigravity.py`. Same for any other step-1 measurement that blocks a capability (missing steer or reattach).
11. **Turns:** set `Turns(steer, interrupt)` from what the server measures. `session/cancel` maps to interrupt. Claim steer only if a mid-turn prompt is accepted; otherwise steer stays False and AC4 step 4 is reported as a follow-up under item 10.
The live AC4 steps 1–4 are verified by the dev-verification child; this child delivers the code side and records the measurements.
Credentials & reach:
Architecture §8 rows this child needs (verbatim):
- Child A pin download (`dl.google.com` agy-extensions zips) — Public https; node `h4-2-gmind` — verified: HTTP 200 for the arm64 zip; the amd64 zip was downloaded and hashed.
- AC4 / AC8 dev console (`https://app.dev.jaah.ai`) — `jaah/debug/main/operator-login` (operator, covers dev and main) — verified: `credential_get` succeeded; HTTPS 200 from `h4-2-gmind`.
- AC4/AC8 Antigravity oauth + Gemini api_key accounts on dev — Dev org accounts (Settings › Accounts) — deferred — operator answer A2: "Not sure — child checks; copy from main if they don't exist" (child V).
- A.1c real-key measurement — A dev Gemini API key bound to an Antigravity account — deferred — measured in child A on a dev run once V/A2 provides the account; until then usage stays `tokens=False`.
- Planner note: this child obtains the dev Antigravity accounts itself (scope item 0) rather than waiting on child V, because its own measurements need them.
- AC1–AC3, AC5, AC7, AC9, AC10 (repo tests, `./dc check full`, `check_context.py`) — Nothing beyond the worktree — verified (worktree present).
Out of scope:
From PRD §6 (verbatim) — do NOT touch:
- **Do NOT** move Claude subscription (oauth) runs onto ACP, or change `Capabilities.runtime` for `claude` oauth (ADR-0167).
- **Do NOT** delete the legacy terminal launch path (`fleet_launch`/`fleet_dispatch` exec lines, `answer_delivery` tmux paste, `ComposerTui`). It still serves Claude subscriptions and non-Linux nodes.
- **Do NOT** replace or wrap ACP with A2A, an LLM gateway, LiteLLM-style proxies or direct vendor SDKs (D3).
- **Do NOT** rename agent verbs, MCP tool names or ACP extension methods (ADR-0166 table), lifecycle states, `terminal_type` values or DB columns.
- **Do NOT** drive `agy`'s TUI (S1, ADR-0165 anti-pattern), and do not fork an upstream ACP adapter unless no option closes the gap (ADR-0167 rule).
- **Do NOT** add a new provider, or bump any vendor CLI pin other than `agy` (and only if S1 requires it).
- **Do NOT** touch the cloud compute "provider" work of epic 8760 (AWS/GCP/Azure node providers, `NodeLink` storage adapters). That is a different "provider".
- **Do NOT** redesign console pages (Settings › Providers, Brains roster, task page, Audit › Sessions). The only console delta is Antigravity runs gaining ACP chat/live-control views and their catalogue gap text.
- **Do NOT** make ACP available on Windows or macOS nodes (`acp_runtime.py:75` Linux gate stays).
- **Do NOT** build ideas ranked below the top 3–5. File them (AC6).
- **Do NOT** change the API key handling invariant: no model key in an engine's environment or `launch.json` (ADR-0165 self-check).
- Do NOT bump the `agy` CLI pin; do NOT write a Jaah stream-json shim or wrap `agy -p` (rejected, architecture §2.3).
- Do NOT delete the legacy Antigravity launch (D5).
- Do NOT add vendor literals to `apps/jaahd/acp/` (use the dialect).
Done means:
**AC4 — Antigravity runs on ACP on dev (R4)** (PRD, verbatim).
States:
| Lane | Expected |
|---|---|
| (a) oauth on a Linux static node | `acp` |
| (b) api_key on a Linux static node | `acp` |
| (c) any non-Linux node | `legacy`, unchanged and excluded |
| (d) MicroVM node | same rule as the other ACP providers' MicroVM handling; whatever applies is named in the catalogue `gaps` |
| (e) operator switch OFF | `legacy` |
Pass for (a) and (b):
- The catalogue pairs are no longer `legacy_only` and the `no-acp` gap is gone.
- The adapter has `acp_launch`, and the switch defaults ON in the real dev deployment.
- The shim is installed by the node package from a pin in `packaging/platform-pins.toml`.
- The shim does not drive the TUI.
→ verify:
1. Open the dev console (dev.jaah.ai). Create a task in a project, bound to an Antigravity account (oauth; then repeat with api_key), and Start it.
2. Observe the task page's Chat view streaming normalised events (turns, tool calls) and the task reaching its settled outcome.
3. Audit › Sessions shows the session with runtime ACP, a transcript and usage.
4. Send a steer from the chat view and observe the agent receive it.
5. `./dc test` the Antigravity adapter tests and `tools/tests/test_provider_capabilities.py`: green.
If S1 makes the shim infeasible, AC4 passes only as: the architecture artifact records the measurement (`agy` version, commands run, output), a follow-up Task is filed, and Antigravity's catalogue row is unchanged.
(Steps 1–4 deferred — executed live by the dev-verification child after merge + Jaah Deploy; this child must deliver step 5 and the code side, plus the API switch tests for (c)–(e).)
**AC7 — Behaviour preserved, by test (R7, S2)** (PRD, verbatim).
Pass, all green on the merged develop:
- `apps/jaahd/tests/test_agent_adapters.py` (Claude launch.sh + settings byte-identical to the goldens);
- the ACP replay fixtures under `apps/jaahd/tests/acp_replay/`;
- `tools/tests/test_provider_capabilities.py`;
- every provider's adapter tests;
- `./dc check full`.
→ verify: the merge lane's `./dc check full` GATE_RESULT `status=pass` on each PR. Any golden that had to be regenerated is called out in that PR with the reason. A golden change for any lane other than Antigravity is a failure unless it is byte-neutral in behaviour.
**AC9 — No new vendor branches (R5)** (PRD, verbatim).
Pass:
- No new `provider ==` / vendor-literal branch is introduced in launch sites, `apps/jaahd/acp/` or `apps/jaahd/provider_health.py` dispatch.
- The existing census still passes.
- Vendor literals in `acp/` (C9) are either removed or kept with a recorded reason.
→ verify: `git diff develop...<merge> -- apps/jaahd/acp apps/jaahd/fleet_launch.py apps/jaahd/fleet_dispatch.py` shows no added vendor names. `./dc test apps/jaahd/tests/test_agent_adapters.py` is green.
**AC10 — Docs current (R8)** (PRD, verbatim).
Pass:
- `apps/jaahd/README.md` describes the present. The Antigravity section no longer says "no ACP" if AC4 shipped. The stale line `:551` "No ACP host exists yet" is fixed. New common helpers are named in the adapter-seam section.
- ADR-0135 / ADR-0165 entries are edited in place if a decision changed.
- `python3 tools/check_context.py` passes.
→ verify: run `python3 tools/check_context.py`, which prints no failure. Grep the README for "No ACP host exists yet": 0 hits.
- The replay fixture recorded from the real `agy_acp_server` proves an "API key not valid" turn records a break, not a success (risk R3), output cited.
- `acp_launch` test proves no key in env or `launch.json` (ADR-0165 self-check), output cited.
- PR body carries the step-1 measurements (a)–(e) with version, commands and output; any infeasible capability has its follow-up task id named.
Outcome
Antigravity API-key runs now on ACP via pinned agy_acp_server 1.4.1 (installer, catalogue, acp_launch, dialect system_prompt_meta, replay fixture); OAuth stays legacy (headless sign-in infeasible, draft #8841); steer/prefix #8842. PR #6573 merged, Jaah Deploy green. Memory: proposed 1 gotcha, 1 recipe.
Artifacts
Activity
- Draft
- Ready
- Runner started
- Runner ended (done)