@estebanforge/pi-antigravity-bridge 1.3.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,460 @@
1
+ # ACP Protocol Reference — `antigravity-acp` (verified live)
2
+
3
+ Everything below was verified LIVE on 2026-09-03 against the official Google
4
+ ACP server, build `agy_acp_server_20260818_01_RC01` (registry id
5
+ `antigravity-acp`, v1.0.0), linux-x86_64, on a headless Fedora box. Raw
6
+ traffic: `probe-logs/acp-traffic-run5.jsonl` (600 clean JSONL lines,
7
+ gitignored). Gate verdicts: [ACP-ADOPTION-PLAN.md](./ACP-ADOPTION-PLAN.md)
8
+ section 8.1. Raw SQLite conversation store: `probe-logs/acp-server-conversations/`.
9
+
10
+ Transport: newline-delimited JSON-RPC 2.0 over the server's stdio. The server
11
+ is fully silent until it receives a request. Cold start to `initialize`
12
+ response measured ~5-6 s across three runs (1.5 GB binary). Startup flags:
13
+ only `--debug` and `--notices` exist. No model/effort/conversation flags:
14
+ everything moves through the protocol or `settings.json`.
15
+
16
+ Data provenance: the captures are SERVER STDOUT only. Requests quoted here
17
+ are author-attested — we composed and sent those exact bytes (source: our
18
+ stdin writes, preserved in `probe-logs/MANIFEST.md` references and the pi
19
+ session) and correlate them with the success/error responses on the capture.
20
+ No request echo exists on the wire. Gate F's accepted `mcpServers` shape is
21
+ verified this way: those exact bytes were sent and `session/new` returned a
22
+ session.
23
+
24
+ ## Method index (verified against this build)
25
+
26
+ | Method | Status on RC01 |
27
+ | --- | --- |
28
+ | `initialize` | works |
29
+ | `authenticate` | works (param is `methodId`) |
30
+ | `session/new` | works |
31
+ | `session/set_config_option` | works (param is `configId`) |
32
+ | `session/prompt` | works |
33
+ | `session/cancel` | **-32601 Method not found. NOT IMPLEMENTED on RC01** |
34
+ | `session/load` | works (run 6: `{sessionId, cwd, mcpServers}`; full-text history replay) |
35
+ | `session/close` | works (run 6; per-session, connection survives) |
36
+ | `session/set_mode` | works (run 6; `modeId`; redundant with `set_config_option("mode")`) |
37
+ | `session/request_permission` | observed live (run 6, mode `default` tool probe; agent-to-client REQUEST, once-only options) |
38
+ | `session/list`, `session/resume`, `session/fork`, `session/delete` | advertised; not yet probed |
39
+ | `fs/read_text_file`, `fs/write_text_file`, `terminal/*` | not observed (our client capabilities were `fs:false`, `terminal:false`) |
40
+
41
+ ## initialize
42
+
43
+ Request:
44
+
45
+ ```json
46
+ {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false}}}
47
+ ```
48
+
49
+ Result (verbatim, capability names):
50
+
51
+ ```json
52
+ {"protocolVersion":1,
53
+ "agentCapabilities":{
54
+ "loadSession":true,
55
+ "promptCapabilities":{"image":true,"audio":true,"embeddedContext":true},
56
+ "mcpCapabilities":{"http":true,"sse":true},
57
+ "sessionCapabilities":{"list":{},"resume":{}},
58
+ "auth":{"logout":{}}},
59
+ "authMethods":[
60
+ {"description":"Log in with your Google account","id":"oauth-personal","name":"Log in with Google"},
61
+ {"description":"Log in with your Gemini Enterprise account","id":"oauth-business","name":"Log in with Gemini Enterprise"},
62
+ {"description":"Use an API key with Gemini Developer API","id":"gemini-api-key","name":"Gemini API key"},
63
+ {"description":"Use Gemini Enterprise Agent Platform (formerly Vertex AI) with Application Default Credentials or an API key","id":"agent-platform","name":"Gemini Enterprise Agent Platform"}],
64
+ "agentInfo":{"name":"antigravity-acp","title":"Google Antigravity","version":"agy_acp_server_20260818_01_RC01"}}
65
+ ```
66
+
67
+ Types: `promptCapabilities.image|audio|embeddedContext: bool`;
68
+ `mcpCapabilities.http|sse: bool`; `sessionCapabilities.list|resume: object`;
69
+ `auth.logout: object`; `authMethods[].id` is the string passed to
70
+ `authenticate`.
71
+
72
+ ## authenticate
73
+
74
+ Request (param name is `methodId`, NOT `method` — `method` yields -32602 with
75
+ `errors:[{"loc":["methodId"],"msg":"field required","type":"value_error.missing"}]`):
76
+
77
+ ```json
78
+ {"jsonrpc":"2.0","id":1,"method":"authenticate","params":{"methodId":"oauth-personal"}}
79
+ ```
80
+
81
+ Success result: `{}` (empty object). Failures (verified live):
82
+
83
+ ```json
84
+ {"jsonrpc":"2.0","id":2,"error":{"code":-32000,"message":"Onboarding failed: The authentication flow did not complete successfully.","data":{"reason":"onboarding_failed"}}}
85
+ {"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"Onboarding failed: Timed out waiting for the authentication flow to complete.","data":{"reason":"onboarding_failed"}}}
86
+ ```
87
+
88
+ Unauthenticated `session/new` (before any auth) returns:
89
+
90
+ ```json
91
+ {"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"Authentication required","data":{"message":"No authentication method selected. Either call the `authenticate` method (supports oauth-personal, gemini-api-key, agent-platform), or set `auth.type` in settings.json (/home/<u>/.gemini/antigravity-acp/settings.json) to one of: oauth-personal, gemini-api-key (requires GEMINI_API_KEY env var), oauth-business (Gemini Enterprise; requires gcp.project/location), agent-platform (formerly 'vertex-ai', still accepted; requires GOOGLE_API_KEY, or a project and location from GOOGLE_CLOUD_PROJECT/GOOGLE_CLOUD_LOCATION or gcp.project/gcp.location with Application Default Credentials)."}}}
92
+ ```
93
+
94
+ ### settings.json shapes (official Zed docs + error message)
95
+
96
+ ```jsonc
97
+ // ~/.gemini/antigravity-acp/settings.json
98
+ {"auth": {"type": "oauth-personal"}}
99
+ {"auth": {"type": "gemini-api-key"}} // + GEMINI_API_KEY env
100
+ {"auth": {"type": "oauth-business"}, "gcp": {"project": "<ID>", "location": "<REGION>"}}
101
+ {"auth": {"type": "agent-platform"}, "gcp": {"project": "<ID>", "location": "<REGION>"}}
102
+ ```
103
+
104
+ settings.json is read at server STARTUP: writing it while a server process
105
+ runs has no effect on that process (verified: the error persisted until
106
+ restart).
107
+
108
+ ### oauth-personal flow (headless, verified end-to-end)
109
+
110
+ On `authenticate` the server: starts a loopback HTTP listener on
111
+ `127.0.0.1:<random port>` (observed 44603, 34653, 59563, 35293 — random per
112
+ run), composes a Google OAuth URL, and tries to open a browser. HEADLESS: it
113
+ never prints the URL on stdout or stderr (`--debug` stderr stays empty) and
114
+ execs nothing (strace-verified). The URL exists only in process memory.
115
+ Capture trick (works): set `BROWSER` to a script that appends its argument to
116
+ a file, and/or prepend a fake `xdg-open` to `PATH` (see
117
+ `probe-logs/capurl.sh`). Remote login: `ssh -N -L <port>:127.0.0.1:<port>
118
+ <host>` on the user's machine, then open the URL there; Google's redirect
119
+ lands on the server's loopback through the tunnel. NEVER curl the loopback
120
+ yourself: a `GET /` without the code param is treated as a failed callback
121
+ and aborts the flow ("Authentication failed" page).
122
+
123
+ Captured URL anatomy (run 5):
124
+
125
+ ```
126
+ https://accounts.google.com/o/oauth2/v2/auth
127
+ ?response_type=code
128
+ &client_id=1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com
129
+ &redirect_uri=http%3A%2F%2F127.0.0.1%3A35293%2F
130
+ &scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform
131
+ +https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email
132
+ +https%3A%2F%2Fwww.googleapis.com%2Fauth%2Faicode
133
+ &state=<random>
134
+ &access_type=offline
135
+ &prompt=consent
136
+ &code_challenge=<S256>
137
+ &code_challenge_method=S256
138
+ ```
139
+
140
+ Timing: the onboarding window is minutes-scale (one flow timed out at ~8.5
141
+ minutes; a second completed within ~1.5 minutes). Tokens persist at
142
+ `~/.gemini/antigravity-acp/acp_token.json` (0600; 510 bytes; credential —
143
+ never copy). A second server run after successful auth has NOT yet been
144
+ tested (token-reuse across restart is a remaining probe).
145
+
146
+ ## session/new
147
+
148
+ Request:
149
+
150
+ ```json
151
+ {"jsonrpc":"2.0","id":3,"method":"session/new","params":{"cwd":"/abs/cwd","mcpServers":[]}}
152
+ ```
153
+
154
+ Result (verbatim, this is also the model catalog + config schema):
155
+
156
+ ```json
157
+ {"sessionId":"4d5a6c89-0be5-4d98-ac4d-dede75c780fe",
158
+ "modes":{"currentModeId":"default","availableModes":[
159
+ {"id":"default","name":"Default","description":"Default permission prompt flow"},
160
+ {"id":"auto_edit","name":"Auto Edit","description":"Auto-approve file edit tools"},
161
+ {"id":"yolo","name":"YOLO","description":"Auto-approve all tools"}]},
162
+ "models":{"availableModels":[
163
+ {"modelId":"gemini-3.8-flash-high","name":"Gemini 3.8 Flash (High)","description":"gemini-3.8-flash-high"},
164
+ {"modelId":"gemini-3.8-flash-medium","name":"Gemini 3.8 Flash (Medium)","description":"gemini-3.8-flash-medium"},
165
+ {"modelId":"gemini-3.8-flash-low","name":"Gemini 3.8 Flash (Low)","description":"gemini-3.8-flash-low"},
166
+ {"modelId":"gemini-3.7-flash-high","name":"Gemini 3.7 Flash (High)","description":"gemini-3.7-flash-high"},
167
+ {"modelId":"gemini-3.7-flash-medium","name":"Gemini 3.7 Flash (Medium)","description":"gemini-3.7-flash-medium"},
168
+ {"modelId":"gemini-3.7-flash-low","name":"Gemini 3.7 Flash (Low)","description":"gemini-3.7-flash-low"},
169
+ {"modelId":"gemini-3.6-flash-high","name":"Gemini 3.6 Flash (High)","description":"gemini-3.6-flash-high"},
170
+ {"modelId":"gemini-3.6-flash-medium","name":"Gemini 3.6 Flash (Medium)","description":"gemini-3.6-flash-medium"},
171
+ {"modelId":"gemini-3.6-flash-low","name":"Gemini 3.6 Flash (Low)","description":"gemini-3.6-flash-low"},
172
+ {"modelId":"gemini-pro-agent","name":"Gemini 3.1 Pro (High)","description":"gemini-pro-agent"},
173
+ {"modelId":"gemini-3.1-pro-low","name":"Gemini 3.1 Pro (Low)","description":"gemini-3.1-pro-low"}],
174
+ "currentModelId":"gemini-3.7-flash-high"},
175
+ "configOptions":[
176
+ {"currentValue":"gemini-3.7-flash-high","id":"model","name":"Model","category":"model","type":"select","options":[{"value":"gemini-3.8-flash-high","name":"Gemini 3.8 Flash (High)","description":"..."}, "...all 11..."]},
177
+ {"currentValue":"default","id":"mode","name":"Session Mode","category":"mode","type":"select","options":[{"value":"default","name":"Default","description":"Default permission prompt flow"},{"value":"auto_edit","name":"Auto Edit","description":"Auto-approve file edit tools"},{"value":"yolo","name":"YOLO","description":"Auto-approve all tools"}]}]}
178
+ ```
179
+
180
+ Key facts:
181
+
182
+ - `sessionId` is a UUID, chosen by the server.
183
+ - The FULL model catalog ships in `session/new` (the `agy models` CLI is not
184
+ needed for the ACP engine). Default model: `gemini-3.7-flash-high`.
185
+ - Model ids are FULL slugs with the effort tier baked in
186
+ (`gemini-3.8-flash-low` = Flash, low effort). One `--model`-style split is
187
+ NOT needed here. `gemini-pro-agent` is the 3.1 Pro High slug (differs from
188
+ the CLI's `gemini-3.1-pro-high` print).
189
+ - Modes map onto our config knobs: `default` = permission prompt flow,
190
+ `auto_edit` ≈ `--mode accept-edits` for edit tools, `yolo` ≈
191
+ `--dangerously-skip-permissions`.
192
+ - `mode` and `model` are both `configOptions` (`type: select`), settable via
193
+ `session/set_config_option`.
194
+ - `availableCommands` arrive right after via `session/update`
195
+ (`available_commands_update`): observed commands `plan` ("generates an
196
+ implementation plan artifact and awaits user approval") and `logout`.
197
+
198
+ ## session/set_config_option
199
+
200
+ Request (param is `configId`, NOT `configOptionId` — wrong name gives -32602
201
+ `errors:[{"loc":["configId"],"msg":"field required"}]`):
202
+
203
+ ```json
204
+ {"jsonrpc":"2.0","id":5,"method":"session/set_config_option","params":{"sessionId":"<uuid>","configId":"model","value":"gemini-3.8-flash-low"}}
205
+ ```
206
+
207
+ Result: the full `configOptions` array with `currentValue` updated
208
+ (`"currentValue":"gemini-3.8-flash-low"` observed). LIVE VERIFIED: model and
209
+ effort switch per session with no process recycle. Mode switch
210
+ (`configId:"mode"`) is expected to work identically (same option shape) but
211
+ was not explicitly exercised.
212
+
213
+ ## session/prompt
214
+
215
+ Request (`prompt` is an array of typed content blocks):
216
+
217
+ ```json
218
+ {"jsonrpc":"2.0","id":6,"method":"session/prompt","params":{"sessionId":"<uuid>","prompt":[{"type":"text","text":"Reply with exactly: HELLO ACP. Do not use any tools."}]}}
219
+ ```
220
+
221
+ Streaming: `session/update` NOTIFICATIONS (no id), then the `session/prompt`
222
+ response. Discriminator is `update.sessionUpdate`.
223
+
224
+ `agent_message_chunk` (verbatim):
225
+
226
+ ```json
227
+ {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"<uuid>","update":{"content":{"text":"HELLO ACP.","type":"text"},"sessionUpdate":"agent_message_chunk"}}}
228
+ ```
229
+
230
+ Terminal response:
231
+
232
+ ```json
233
+ {"jsonrpc":"2.0","id":6,"result":{"stopReason":"end_turn"}}
234
+ ```
235
+
236
+ Observed stop reasons so far: `end_turn` only. Schema enumerates
237
+ `max_tokens`, `max_turn_requests`, `refusal`, `cancelled` (cancelled
238
+ unreachable on RC01 — no cancel method).
239
+
240
+ `available_commands_update` (verbatim):
241
+
242
+ ```json
243
+ {"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"<uuid>","update":{"availableCommands":[{"name":"plan","description":"Plan carefully before executing a task (generates an implementation plan artifact and awaits user approval)."},{"name":"logout","description":"Log out and clear stored credentials."}],"sessionUpdate":"available_commands_update"}}}
244
+ ```
245
+
246
+ ### Streaming behavior (300-line and 3000-line stress writes)
247
+
248
+ - Chunks are pure DELTAS. Verified by content: numbers split mid-token across
249
+ chunk boundaries (`"...\n39\n40\n4"` then `"1\n42\n43..."`), and no chunk
250
+ ever contained a full prefix of the accumulated text. The legacy
251
+ cumulative-resend failure mode was NOT observed over ACP (defensive guard
252
+ stays cheap insurance).
253
+ - Throughput: 300 numbers (~600 tokens) completed within one 25 s poll;
254
+ 3000 numbers streamed continuously at roughly 100 numbers per 1.5 s.
255
+ - Ordering: notifications strictly before the prompt response; no
256
+ interleaving with other responses observed.
257
+
258
+ ### Usage
259
+
260
+ NO usage/token fields appeared anywhere in `initialize`, `session/new`,
261
+ `set_config_option`, `agent_message_chunk`, prompt results, or tool flows
262
+ (run 6). Gate B FINAL: ABSENT on RC01. Consequence in the plan: stream-json
263
+ is retained as a secondary engine until upstream ships usage (phase-4
264
+ deletion conditioned on Gate B lifting).
265
+
266
+ ## session/cancel
267
+
268
+ **NOT IMPLEMENTED on RC01** (despite appearing in the ACP schema source and
269
+ our plan):
270
+
271
+ ```json
272
+ {"jsonrpc":"2.0","id":12,"method":"session/cancel","params":{"sessionId":"<uuid>"}}
273
+ {"jsonrpc":"2.0","id":12,"error":{"code":-32601,"message":"Method not found","data":{"method":"session/cancel"}}}
274
+ ```
275
+
276
+ An in-flight `session/prompt` CANNOT be stopped over the protocol on this
277
+ build (verified: a 3000-number stream ran to completion after cancel
278
+ returned -32601). Abort fallback candidate: kill the connection and
279
+ `session/load` on the next server (probe pending).
280
+
281
+ ## Error shapes (verified)
282
+
283
+ ```jsonc
284
+ // -32602 Invalid params (wrong/missing param name) — includes field location
285
+ {"code":-32602,"message":"Invalid params","data":{"errors":[{"loc":["methodId"],"msg":"field required","type":"value_error.missing"}]}}
286
+ // -32000 server-state errors — message + data{message|reason}
287
+ {"code":-32000,"message":"Authentication required","data":{"message":"..."}}
288
+ {"code":-32000,"message":"Onboarding failed: ...","data":{"reason":"onboarding_failed"}}
289
+ // -32601 method not implemented
290
+ {"code":-32601,"message":"Method not found","data":{"method":"session/cancel"}}
291
+ ```
292
+
293
+ Note the schema-mismatch pattern: two live params differ from the obvious
294
+ names (`methodId` not `method`; `configId` not `configOptionId`). Expect
295
+ thirds; code against captured examples, and treat -32602 `loc` as the
296
+ correction oracle.
297
+
298
+ ## Server-side persistence (on-disk formats)
299
+
300
+ `~/.gemini/antigravity-acp/` layout:
301
+
302
+ | Path | Purpose |
303
+ | --- | --- |
304
+ | `settings.json` | auth config (world-readable 0644); read at startup only |
305
+ | `acp_token.json` | OAuth tokens (0600). CREDENTIAL. Never copy or print. |
306
+ | `conversations/<uuid>.db` | SQLite 3 conversation DB (WAL mode; 163 KB main + 4 MB WAL for our probe session) |
307
+ | `conversations/<uuid>.db-wal`, `.db-shm` | WAL pair |
308
+ | `conversations/<uuid>.meta` | 46-byte JSON: `{"cwd":"<session cwd>"}` |
309
+
310
+ Session ids match conversation DB file stems (our `sessionId`
311
+ `4d5a6c89-0be5-4d98-ac4d-dede75c780fe` → `conversations/4d5a6c89-....db`).
312
+
313
+ Snapshot DDL (`probe-logs/acp-server-conversations/schema.sql`; 7 tables):
314
+
315
+ ```sql
316
+ CREATE TABLE `trajectory_meta` (`trajectory_id` text,`cascade_id` text,`trajectory_type` integer,`source` integer,PRIMARY KEY (`trajectory_id`));
317
+ CREATE TABLE `steps` (`idx` integer,`step_type` integer NOT NULL DEFAULT 0,`status` integer NOT NULL DEFAULT 0,`has_subtrajectory` numeric NOT NULL DEFAULT false,`metadata` blob,`error_details` blob,`permissions` blob,`task_details` blob,`render_info` blob,`step_payload` blob,`step_format` integer NOT NULL DEFAULT 0,PRIMARY KEY (`idx`));
318
+ CREATE TABLE `gen_metadata` (`idx` integer,`data` blob,`size` integer NOT NULL DEFAULT 0,PRIMARY KEY (`idx`));
319
+ CREATE TABLE `executor_metadata` (`idx` integer,`data` blob,PRIMARY KEY (`idx`));
320
+ CREATE TABLE `parent_references` (`idx` integer,`data` blob,PRIMARY KEY (`idx`));
321
+ CREATE TABLE `trajectory_metadata_blob` (`id` text DEFAULT "main",`data` blob,PRIMARY KEY (`id`));
322
+ CREATE TABLE `battle_mode_infos` (`idx` integer,`data` blob,PRIMARY KEY (`idx`));
323
+ ```
324
+
325
+ Two facts matter for us:
326
+
327
+ 1. `steps.step_payload` is an OPAQUE BLOB — the same two-phase-write SQLite
328
+ blob family that silently broke the pre-1.3.2 legacy engine (issue #1).
329
+ Never poll these DBs; that decision is re-validated by this schema.
330
+ 2. WAL quirk: a read-only open of the live `.db` shows an EMPTY
331
+ `sqlite_master` (schema lives in the WAL until checkpoint); copying
332
+ `db+wal+shm` yields a copy that also reads empty (stale `-shm` suppresses
333
+ replay). To snapshot: `VACUUM INTO` from an opened connection (that is how
334
+ `probe-logs/acp-server-conversations/probe-session-4d5a6c89.db` was made).
335
+
336
+ ## Client capability posture (phase 1 decision, verified harmless)
337
+
338
+ `initialize` sent `fs:false`, `terminal:false`. Effects observed: `session/new`
339
+ and pure-text prompts work; the agent self-executes its own tools and asks
340
+ via `session/request_permission` (verified in run 6, tool probe). No `fs/*`
341
+ or `terminal/*` delegation occurred with capabilities off.
342
+
343
+ ## Timing summary (Gate E evidence)
344
+
345
+ | Event | Observation |
346
+ | --- | --- |
347
+ | Spawn → initialize response | ~5-6 s (three runs, consistent; 1.5 GB binary, page cache warm) |
348
+ | session/new response | < 2 s |
349
+ | set_config_option response | < 1 s |
350
+ | Prompt first chunk | ~1-2 s (Flash, low effort) |
351
+ | OAuth onboarding window | minutes-scale; one timeout observed at ~8.5 min |
352
+ | Steady RSS | ~327 MB (5 min mixed load; VSZ ~5.3 GB is TCMalloc reservation) |
353
+
354
+ ## Run 6 findings (2026-09-03, post-restart session; raw traffic
355
+ `probe-logs/acp-traffic-run6-restart-load-tools.jsonl`, 28 clean JSONL lines)
356
+
357
+ All remaining probes executed. Results:
358
+
359
+ 1. **Token persistence: PASS.** New server process, `session/load` with NO
360
+ `authenticate` call succeeded. `acp_token.json` + `settings.json` carry
361
+ auth across restarts.
362
+ 2. **`session/load`: PASS.** Params `{sessionId, cwd, mcpServers}`. On
363
+ success the server REPLAYS the whole conversation as `session/update`
364
+ notifications: `user_message_chunk` / `agent_message_chunk` PAIRS per
365
+ turn, in order. Replay re-sends prior replies as FULL TEXT (one giant
366
+ chunk), not the original deltas. Driver rule: on load, incoming chunks
367
+ are HISTORY replay, never live generation. Then:
368
+ `available_commands_update`.
369
+ 3. **Config does NOT persist across restart.** The loaded session came back
370
+ `currentModelId: gemini-3.7-flash-high` (default) although it had been
371
+ switched to `gemini-3.8-flash-low` before the kill. Model/effort/mode
372
+ must be re-applied by the driver after every restart/load.
373
+ 4. **`session/request_permission` verified live.** Agent-to-client REQUEST:
374
+ `{sessionId, toolCall{...full tool_call object incl. diff content...},
375
+ options:[{optionId:"allow",name:"Allow",kind:"allow_once"},
376
+ {optionId:"deny",name:"Deny",kind:"reject_once"}]}`. Client answers:
377
+ `{"outcome":{"outcome":"selected","optionId":"allow"}}`. Only once-kinds
378
+ observed (no allow_always in this payload).
379
+ 5. **`tool_call` / `tool_call_update` shapes verified.** Fields:
380
+ `toolCallId`, `title` ("Run create_file?"), `kind` (`edit`/`read`),
381
+ `status` lifecycle `pending → in_progress → completed|failed`, `content`
382
+ with `{type:"diff", path, newText, _meta:{kind:"add"}}` (the edit arrives
383
+ AS A DIFF), `locations:[{path}]`, `rawInput` (full args object), optional
384
+ `rawOutput` on completion. Gate C PASS: enough content to retire
385
+ wrapper-replay rendering later.
386
+ 6. **toolCallId formats are inconsistent:** `33eb71a0...` (32-hex),
387
+ `<sessionId>:7` (composite), `call_859008` (OpenAI-style). Treat as
388
+ opaque strings.
389
+ 7. **Approved-id supersede quirk.** After `allow`, the approved
390
+ `create_file` call reported `status:"failed",
391
+ rawOutput:"Tool call was approved but never executed."` while the actual
392
+ edit executed under a different toolCallId (`<sessionId>:7`,
393
+ `edit_file`) and succeeded (file created, read back, confirmed).
394
+ Never assume approved toolCallId == executed toolCallId; track effects,
395
+ not ids.
396
+ 8. **Image prompt: PASS end-to-end.** 1x1 red PNG as `{type:"image",
397
+ data:<base64>, mimeType:"image/png"}` + text question → answer "Red".
398
+ (Community reports said the agy CLI was broken for images; the ACP path
399
+ works.)
400
+ 9. **Gate F: PASS at shape level.** Accepted entry:
401
+ `{"name":"pi-bridge","type":"http","url":"http://...","headers":[]}`.
402
+ `headers` is a LIST. Permitted types: `http`, `sse` (same headers
403
+ shape), stdio (`command`/`args`/`env`), and an `acp` type (needs `id`).
404
+ A DEAD url is accepted at `session/new` (connection is lazy). Phase-1
405
+ TODO: point at the real bridge (MCP SDK Streamable HTTP) and confirm the
406
+ agent's http client completes `tools/list` + `tools/call` (G9
407
+ round-trip end-to-end).
408
+ 10. **`session/set_mode` (`modeId`) and `session/close` work** (`{}`
409
+ results). `close` is per-session; the connection survives it.
410
+ `set_mode` and `set_config_option("mode")` are redundant paths to the
411
+ same state.
412
+ 11. **Usage: ABSENT on RC01 (Gate B final).** No token fields in any
413
+ payload across both runs, including tool flows. Accept zero-usage
414
+ (existing documented fallback); re-check on future builds.
415
+ 12. **Steady RSS ~327 MB** under 5 minutes of mixed load.
416
+
417
+ Probe session closed cleanly (`session/close` then kill). Final gate status:
418
+ plan section 8.1.
419
+
420
+ ## Phase-2 probe findings (2026-09-03; raw traffic
421
+ `probe-logs/acp-phase2-traffic.jsonl`, one session, four labeled prompts,
422
+ all `end_turn`)
423
+
424
+ 1. **`agent_thought_chunk` is SPARSE on RC01.** A step-by-step math prompt
425
+ produced ZERO thought chunks - the reasoning shipped as plain
426
+ `agent_message_chunk` text. The only thought chunk in the capture came
427
+ from the `/plan` flow. Shape is identical to the message chunk
428
+ (`content:{type:"text",text}`), already mapped. Consequence: the
429
+ thinking-block pipeline rarely renders on flash-low; do not build UI
430
+ assumptions on frequent thought deltas.
431
+ 2. **Image prompts: PASS at real resolution.** 64x64 two-tone PNG (top
432
+ red / bottom blue) sent as `{type:"image",data,mimeType}` blocks ahead
433
+ of the text block answered "Top: Red, Bottom: Blue". Mixed
434
+ image+text prompt arrays work; no 1x1-only limitation.
435
+ 3. **Tool frames: three mappings the phase-1 driver got wrong.**
436
+ - MCP tools wrap args in an envelope: `rawInput:{arguments:{text}}`.
437
+ Native `edit_file` carries args directly (`rawInput:{file_path}`).
438
+ - MCP tool titles are "<server>_<tool>" ("pi-bridge_bridge_echo"); the
439
+ clean name hides in `_meta.mcp.tool`.
440
+ - A completed `tool_call_update`'s `rawOutput` is the server's DISPLAY
441
+ string, often just the tool title ("Call bridge_echo"), NOT the
442
+ result. `content[]` entries wrap their payload
443
+ (`{type:"content",content:{type:"text",text}}`); for edits the same
444
+ array carries the real diff (`newText`...). The MCP result itself
445
+ reaches the model out-of-band: the client answered ECHO:PROBE-9
446
+ correctly without the result ever appearing in an update.
447
+ 4. **`/plan` command flow (F.7 answered: the server INTERCEPTS leading
448
+ slash commands).** "/plan Organize a birthday party" produced: one
449
+ `agent_thought_chunk`, a `create_file` tool_call with the full plan
450
+ diff in `content[]`, an `edit_file` into the server's brain dir
451
+ (`~/.gemini/antigravity-acp/brain/<sessionId>/...`), and a closing
452
+ message asking for decisions. It WRITES under the auto permission
453
+ policy. **Verdict: ACP has NO review-only mode.** The three modes
454
+ (default/auto_edit/yolo) are permission modes only; the CLI's
455
+ `--mode plan` has no ACP equivalent. The committed exception stands:
456
+ plan delegations keep `agy -p --mode plan`.
457
+ 5. **No `plan` sessionUpdate type exists** on RC01. Plan artifacts arrive
458
+ as ordinary edit `tool_call`s (rendered as tool cards), not as a
459
+ distinct update stream. The phase-2 idea "plan updates as thinking
460
+ labels" is observed-absent, not deferred.