@estebanforge/pi-antigravity-bridge 1.6.2 → 1.6.4

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.
@@ -1,940 +0,0 @@
1
- # ACP Adoption Plan: Official Google `antigravity-acp` Server
2
-
3
- Status: PROPOSED. Phase 0 COMPLETE + Phase 1 COMPLETE (2026-09-03): ACP engine
4
- implemented behind `config.engine` (default off), full suite green, live smoke
5
- PASS through the full stack. Gate F VERIFIED live (`scripts/smoke-acp-bridge.mjs`):
6
- the real server lists bridge tools and completes a tool call through the
7
- registered mcpServers entry. §6 parity run LIVE on both engines: 14/14
8
- (`scripts/parity-live.mjs`). Phase-1 acceptance: ALL `[x]` (docs included).
9
- Gates: A PASS, B ABSENT (managed — streaming engine retained
10
- until upstream ships usage), C PASS, D FAIL on RC01 (kill+reload fallback
11
- VERIFIED live, incl. the stale-exit race fix), E PASS, F PASS (verified
12
- live 2026-09-03). Verdicts in section 8.1; full captured protocol data in
13
- [ACP-PROTOCOL-REFERENCE.md](./ACP-PROTOCOL-REFERENCE.md); raw traffic
14
- gitignored in `probe-logs/`.
15
- Peer review 1: Claude, 2026-09-03. Verdict PROCEED WITH CHANGES. Findings 1-6
16
- integrated: third round-trip kind for permissions (P1), parked-overall-timer
17
- pause (P1), cumulative-resend guard port, deferred-capability dispositions,
18
- capability-posture prose, concurrency note.
19
- Peer review 2: Antigravity (agy), 2026-09-03. Verdict PROCEED WITH CHANGES.
20
- 7 findings integrated: Gate A made a blocking gate for the default flip (the
21
- ACP binary takes no model flags, so the recycle fallback could not carry a
22
- model), sessions re-keyed per engine (`@acp` suffix) after the engine-field
23
- design was shown to erase streaming bindings on rollback, parked-timer pause
24
- changed to remaining-budget semantics, acp-permission plumbing specified
25
- through a TurnDriver interface, session/cancel + session/close pulled into
26
- phase 1, AskAntigravity migration moved from phase 3 to phase 4.
27
- Peer reviews 3-4 (plan-stage second rounds, Claude and agy): findings folded
28
- into the two entries above and the section 8/9 design; no separate deltas.
29
- Peer review 5 (implementation): Antigravity (agy), 2026-09-03. Verdict
30
- PROCEED WITH CHANGES. 7 findings, all valid, all fixed: P0 jsonrpc line
31
- buffering (stdio chunks are not newline-aligned; partial frames were dropped),
32
- P1 tool args preserved through tool_done, P1 typed protocol errors survive
33
- translation (cancelSupported stuck null), P1 parked-deadline nulled on pause,
34
- P2 tilde expansion, P2 per-turn config read dedup, P2 coverage gaps filled.
35
- Peer review 6 (post-build): Claude, 2026-09-03, isolated and read-only.
36
- Verdict PROCEED WITH CHANGES. 6 findings, 5 valid, all fixed: P1 bridge 403
37
- gate had no vitest coverage (HTTP-level test added: no header / wrong-length /
38
- same-length wrong token → 403, correct → 200), P1 sessionKey recorded the
39
- config engine instead of the selected driver's (fixed), P2 arm-time park
40
- left a stale deadline (fixed: paused-from-birth invariant), P2 duplicated
41
- timeout body (extracted), P2 abort-before-prompt probed cancel (now guarded
42
- by promptStarted). Sixth finding (untracked parity script) resolved by
43
- commit.
44
-
45
- Adopt the official Google ACP server (`agy_acp_server.par`, registry id
46
- `antigravity-acp`) as a second turn engine for the bridge, behind a config
47
- switch. The existing stream-json engine stays the default until the ACP
48
- engine proves parity. Every change is an improvement or a one-to-one
49
- replacement.
50
-
51
- STANDING DECISION (user, 2026-09-07): BOTH engines are permanently
52
- maintained peers. The streaming engine is never deleted - no phase removes
53
- it, regardless of upstream progress (including Gate B).
54
-
55
- Cross-references: [ARCHITECTURE.md](./ARCHITECTURE.md),
56
- [PI-BRIDGE-GAPS.md](./PI-BRIDGE-GAPS.md), [DEVELOPMENT.md](./DEVELOPMENT.md).
57
-
58
- ## 1. Decision summary
59
-
60
- | Question | Answer |
61
- | --- | --- |
62
- | Adopt? | Yes, phased, behind `config.engine: "acp"` |
63
- | Why | Protocol-native permissions, image prompts, thought text, graceful cancel, session resume/list, sanctioned auth path, stable public protocol |
64
- | Why not a rewrite | The provider layer (G1 digest, G10 system prompt, G9 park/resume, pi event mapping) is engine-agnostic and already proven. The driver is the only engine-coupled part |
65
- | Default flip | Phase 4, after a soak release with both engines |
66
- | Regression guard | Parity checklist (section 6) must be all `[x]` before the default flips |
67
-
68
- ## 2. Verified evidence
69
-
70
- All facts below were verified live on 2026-09-03, not taken from docs. Probe
71
- artifacts live in `~/tmp/agy-acp-probe/` (binary, handshake transcripts).
72
-
73
- ### 2.1 The registry entry
74
-
75
- Source: `agentclientprotocol/registry`, path `antigravity-acp/agent.json`
76
- (folder contains only `agent.json` + `icon.svg`).
77
-
78
- | Field | Value |
79
- | --- | --- |
80
- | id | `antigravity-acp` |
81
- | name / publisher | Google Antigravity / Google LLC, proprietary |
82
- | version | 1.0.0 |
83
- | build | `agy_acp_server_20260818_01_RC01` |
84
- | binaries | darwin-aarch64, linux-x86_64, linux-aarch64, windows-x86_64, windows-aarch64 |
85
- | linux cmd | `./agy_acp_server.par`, registry args `["--uid="]` |
86
- | flags | `--[no]debug`, `--[no]notices` only. No model/effort/conversation flags |
87
-
88
- The registry README states agents are CI-verified to return valid
89
- `authMethods` in the handshake, and the index is re-fetched hourly.
90
-
91
- ### 2.2 Live initialize response
92
-
93
- Sent:
94
-
95
- ```json
96
- {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"fs":{"readTextFile":true,"writeTextFile":true},"terminal":false}}}
97
- ```
98
-
99
- Got (abridged to capability names):
100
-
101
- ```json
102
- {"protocolVersion":1,
103
- "agentCapabilities":{
104
- "loadSession":true,
105
- "promptCapabilities":{"image":true,"audio":true,"embeddedContext":true},
106
- "mcpCapabilities":{"http":true,"sse":true},
107
- "sessionCapabilities":{"list":{},"resume":{}},
108
- "auth":{"logout":{}}},
109
- "authMethods":[
110
- {"id":"oauth-personal","name":"Log in with Google"},
111
- {"id":"oauth-business","name":"Log in with Gemini Enterprise"},
112
- {"id":"gemini-api-key","name":"Gemini API key"},
113
- {"id":"agent-platform","name":"Gemini Enterprise Agent Platform"}],
114
- "agentInfo":{"name":"antigravity-acp","title":"Google Antigravity",
115
- "version":"agy_acp_server_20260818_01_RC01"}}
116
- ```
117
-
118
- ### 2.3 Unauthenticated session/new
119
-
120
- `session/new` returned `-32000 Authentication required` with the full recipe:
121
- call `authenticate`, or set `auth.type` in
122
- `~/.gemini/antigravity-acp/settings.json`. Accepted settings types:
123
- `oauth-personal`, `gemini-api-key` (reads `GEMINI_API_KEY`), `oauth-business`
124
- (needs `gcp.project` + `gcp.location`), `agent-platform` (ADC or
125
- `GOOGLE_API_KEY`, project/location from `GOOGLE_CLOUD_PROJECT` /
126
- `GOOGLE_CLOUD_LOCATION`). Confirmed by the official Zed setup page
127
- (`antigravity.google/docs/ide/extensions/zed/`), which publishes exactly these
128
- `settings.json` shapes.
129
-
130
- ### 2.4 Binary
131
-
132
- 1,529,513,909 bytes ELF x86-64, plus a 117 MB `localharness_external`
133
- sibling. Cold start measured at a few seconds on this machine (handshake
134
- answered well inside the probe timeout).
135
-
136
- ### 2.5 ACP v1 method surface
137
-
138
- From `agentclientprotocol/agent-client-protocol` schema source (`v1/agent.rs`,
139
- `v1/client.rs`):
140
-
141
- - Client to agent: `initialize`, `authenticate`, `session/new`, `session/load`,
142
- `session/prompt`, `session/cancel`, `session/list`, `session/resume`,
143
- `session/fork`, `session/close`, `session/delete`, `session/set_mode`,
144
- `session/set_config_option`.
145
- - Agent to client (requests): `session/request_permission`,
146
- `fs/read_text_file`, `fs/write_text_file`, `terminal/create`, `terminal/kill`,
147
- `terminal/output`, `terminal/release`, `terminal/wait_for_exit`.
148
- - Agent to client (notifications): `session/update`.
149
- - Extension namespace observed in schema: `nes/*` (IDE edit surfaces). Ignore
150
- for this plan.
151
-
152
- ### 2.6 Governance
153
-
154
- `zed-industries/zed#57221` records the chain: Zed maintainers declined a
155
- standalone SDK integration, waited for Google, and Google shipped the official
156
- server. The same thread documents the Antigravity FAQ position that
157
- third-party access to an Antigravity login violates ToS. The community
158
- adapters carry that risk. Google's own server is the sanctioned client
159
- channel. Our undocumented stream-json driver carries the same class of risk
160
- today. Adoption retires it.
161
-
162
- Also dated in that thread: Gemini CLI / Code Assist stop serving 2026-06-18.
163
- `agy` is the only first-party CLI going forward, and its stream-json dialect
164
- is an unmaintained contract. ACP is the maintained one.
165
-
166
- ## 3. Current architecture inventory and disposition
167
-
168
- | Module | Role today | Disposition under ACP |
169
- | --- | --- | --- |
170
- | `src/provider.ts` | streamSimple: pi Context to agy turn to pi events; G9 round-trips; G1 digest; G10 system prompt | KEEP unchanged (additive thought-delta `if` only). Engine-agnostic by contract |
171
- | `src/driver.ts` | persistent stream-json process, turn queue, recycle, timers | KEEP permanently - both engines are maintained peers |
172
- | `src/stream-events.ts` | NDJSON parser + usage mapping | KEEP permanently - both engines are maintained peers |
173
- | `src/sessions.ts` | pi session to agy conversation store | KEEP. Add `engine` tag (section 9.4) |
174
- | `src/config.ts` | runtime config | KEEP. Add engine + acp block (section 9.5) |
175
- | `src/models.ts` | `agy models` to pi Model projection | KEEP. Catalog source stays the CLI until ACP exposes one (probe A.6) |
176
- | `src/mcp-server.ts` | bridge HTTP MCP server; G9 park | KEEP unchanged. Registered via `mcpServers` under ACP |
177
- | `src/skills.ts` | `activate_skill` bridge | KEEP unchanged |
178
- | `src/native-tools.ts` | agy read-only steps to pi builtins | KEEP through phase 2. Phase 3 re-evaluates against `tool_call` content (gate C) |
179
- | `WrapperReplay` (provider.ts) | display-only `antigravity` wrapper tool | Same: re-evaluate in phase 3 (gate C) |
180
- | `src/diff-render.ts` | git-sourced diffs for agy edits | KEEP while agy self-executes edits (our fs capabilities off) |
181
- | `src/ask-tool.ts` | AskAntigravity one-shot via `agy -p` | MIGRATE to ACP one-shot in phase 3 |
182
- | `src/discovery.ts` | snapshot-diff + `/proc` fd-scan conversation binding | DELETE in phase 3 (ACP returns real session ids) |
183
- | `src/patch-cleanup.ts` | pre-1.3.0 patch cleanup | KEEP unrelated |
184
- | `extensions/index.ts` | registration, `/agy` command, notices | EXTEND: engine switch, acp auth command, doctor rebuild |
185
-
186
- ## 4. Capability matrix
187
-
188
- Verdicts: IMPROVEMENT (strictly better), PARITY (equal), RISK (may regress,
189
- mitigation required). Originally assessed pre-probe; verdict column SYNCED to
190
- the Phase 0 results (§8.1, 2026-09-03). Pre-probe reasoning for any row lives
191
- in git history.
192
-
193
- | Dimension | Bridge today (stream-json) | Official ACP | Verdict |
194
- | --- | --- | --- | --- |
195
- | Wire contract | Undocumented NDJSON, shapes captured live, cumulative-resend heuristic, `OK`/`SUCCESS` mapping | Public versioned protocol, negotiated handshake | IMPROVEMENT |
196
- | Text streaming | `text_delta` + dedupe guard | `agent_message_chunk` deltas, native | PARITY |
197
- | Thinking | Token count only, floor 64, no text body | `agent_thought_chunk` carries text | IMPROVEMENT |
198
- | Tool call visibility | start/done/error + native re-exec + wrapper replay | `tool_call` / `tool_call_update` with kind, status, content, locations, rawInput/output | IMPROVEMENT (VERIFIED: diffs arrive in content; Gate C PASS — machinery retirement confirmed for phase 3) |
199
- | Permissions | `skipPermissions: true` default, `--dangerously-skip-permissions`, commands run unreviewed | `session/request_permission` flows to the client | PARITY under adoption (`auto` policy, 9.3). The primitive exists for whoever gates later; per-tool gating stays extension-owned via G9 |
200
- | Images in prompt | Dropped in `extractUserPrompt` | `promptCapabilities.image: true` | IMPROVEMENT (VERIFIED end-to-end, run 6: "Red") |
201
- | Audio | Dropped | `promptCapabilities.audio: true` | IMPROVEMENT (low priority) |
202
- | Session resume | `sessions.json` + `--conversation` flag, per pi session only | `session/load`, `list`, `resume`, server-side | IMPROVEMENT (load VERIFIED across restart, run 6; full-text replay rules in 9.2) |
203
- | Cancel | Kill process group; in-flight turn dies | `session/cancel` to `stopReason: cancelled`, process survives | **REGRESSION-MANAGED (Gate D FAIL on RC01): cancel NOT implemented.** Fallback kill + `session/load` VERIFIED; re-check per build |
204
- | Process lifecycle | One agy child per provider; recycle on model/effort/mode/cwd/conversation drift | One server hosts N sessions | IMPROVEMENT |
205
- | Model + effort selection | CLI flags + recycle | `session/set_config_option` (`configId`) | PASS (VERIFIED live, Gate A): per-session switching, no recycle |
206
- | Usage tokens | `toPiUsage` from step events | Not in any payload (Gate B FINAL) | **ABSENT on RC01.** Zero-usage documented and ACCEPTED for the flip (user decision 2026-09-07: Gate B no longer blocks the default). Both engines are permanently maintained - no deletion |
207
- | MCP tools (G9) | Our HTTP bridge server via `--add-dir` config | `mcpServers` param (`{name,type:"http",url,headers:[]}`) | PARITY at shape level (verified). Phase-1 acceptance: bridge `tools/list`+`tools/call` end-to-end |
208
- | Skills | `--disable-slash-commands`; bridge owns skills | Unknown slash behavior; `available_commands_update` exists | PARITY. Keep our bridge; probe F.7 |
209
- | G1 digest, G10 system prompt | Provider-side prompt assembly | Same (we compose the prompt either way) | PARITY. `embeddedContext` is a later enhancement |
210
- | Diagnostics | `/agy doctor`: snapshot, lifecycle, recycle stats | Nothing equivalent | PARITY work: rebuild on ACP events (section 10) |
211
- | Auth | Piggybacks `agy` CLI login state | First-class: 4 methods, `authenticate`, `logout`, settings.json | IMPROVEMENT. One-time re-login cost |
212
- | One-shot delegation (AskAntigravity) | `agy -p` + snapshot-diff + `/proc` fd-scan | `session/new` + `prompt`, real ids | IMPROVEMENT. Deletes discovery.ts |
213
- | ToS exposure | Undocumented CLI driving | Sanctioned Google client channel | IMPROVEMENT |
214
-
215
- ## 5. Improvements in detail
216
-
217
- 1. **Permission flow.** Today any agy `run_command` executes unreviewed; the
218
- config comment says so in plain words. ACP makes approval a protocol
219
- primitive (`session/request_permission`, verified live). Under the single
220
- `auto` policy (9.3) adoption is parity; the primitive is in place for
221
- whoever wants to gate later.
222
- 2. **Thought text.** `agent_thought_chunk` delivers the model's actual
223
- thinking text. Today pi renders `[thinking]` labels and token counts only.
224
- Provider change is additive: extend the `thought` activity with an optional
225
- `delta` field (9.2).
226
- 3. **Images.** `promptCapabilities.image` lets us forward pi image blocks as
227
- ACP image content instead of dropping them in `extractUserPrompt`.
228
- Community reports say the `agy` CLI itself had broken image upload, so the
229
- live probe (A.4) verifies end-to-end delivery before we claim this.
230
- 4. **Graceful cancel.** `session/cancel` converts to a clean
231
- `stopReason: cancelled` prompt response. Today abort kills the process
232
- group and orphans the turn. Maps onto pi's `aborted` stop reason.
233
- 5. **Server-side sessions.** `session/load` restores a conversation after a
234
- pi restart without our store being the source of truth, and
235
- `session/list` / `session/resume` make `/agy conversations`-style tooling
236
- possible later. Sessions outlive the process; no recycle on drift.
237
- 6. **Protocol stability.** `initialize` negotiates `protocolVersion`. Unknown
238
- fields degrade instead of silently misparsing (compare: the 1.3.2 sqlite
239
- incident, issue #1).
240
- 7. **Sanctioned channel.** Section 2.6. Risk of account action and of silent
241
- dialect drift both drop.
242
- 8. **One-shot cleanup.** `discovery.ts` exists only because `agy -p` never
243
- prints its conversation id. ACP returns real session ids. The
244
- snapshot-diff + `/proc` fd-scan machinery and its ambiguity heuristics get
245
- deleted.
246
- 9. **Enterprise auth.** `oauth-business` / `agent-platform` bring VPC-SC and
247
- data-residency posture the CLI path never exposed to us.
248
- 10. **Deferred, deliberately.** `session/fork` (future multi-branch feature),
249
- `session/close` / `session/delete` (candidates for a later
250
- `/agy conversations` cleanup command), `terminal/*` and client-side `fs/*`
251
- delegation declined: agy keeps executing its own commands and file ops as
252
- today (parity), and answering them would put execution inside our process
253
- for no parity gain; our terminal and fs client capabilities stay false.
254
- `nes/*` IDE-edit extension (ignore), `audio` prompts (phase 2, optional).
255
-
256
- ## 6. Parity contract (no-regression checklist)
257
-
258
- Every box must be `[x]` on the ACP engine before phase 4 flips the default.
259
- Tested with the parity suite (section 11) and `scripts/parity-live.mjs`
260
- (live, both engines: 14/14, 2026-09-03).
261
-
262
- - [x] Text streams token-adjacent to streaming (no full-text re-sends) — live
263
- both engines, delta stream + cumulative-resend guard held
264
- - [x] Multi-turn conversation continuity via `session/load` or our store —
265
- live: ACP `session/load` (history suppressed, clean reply), streaming
266
- `--conversation` resume; the provider's session store supplies the id
267
- - [x] Turn serialization: two concurrent streamSimple calls never interleave
268
- — live both engines (queue releases only on settle) + driver tests
269
- - [x] Bridge tool round-trip (G9) works: bridge MCP `tools/call` parks, pi
270
- executes, `toolResult` resumes the same agy turn — live both engines
271
- through the real bridge server; the pi-side park/resume is engine-
272
- independent (proven live on stream-json; the ACP parity run answers
273
- deps directly to isolate the agy-side HTTP client)
274
- - [x] Idle timer suspends while a G9 park is open, resumes on `kickIdle` —
275
- fake-server tests (park/kickIdle remaining-budget deadline)
276
- - [x] Overall turn timeout (10m) and inactivity timeout (5m) still fire and
277
- fail the turn visibly — driver tests (deadline fires with remaining
278
- budget after resume); inactivity shares the timer machinery
279
- - [x] Abort: pi user abort produces `aborted`, the server process survives,
280
- the next turn on the session works — live both engines (aborted=true,
281
- recovery turn OK). "Process survives" holds on NEITHER engine and is
282
- the documented Gate D behavior: streaming kills the child; RC01 has no
283
- cancel, ACP tears down after the -32601 probe and reloads next turn
284
- - [x] pi session restart resumes the right conversation (engine-tagged) —
285
- engine-scoped key tests + live `session/load`
286
- - [x] Engine rollback preserves the other engine's conversation bindings
287
- (engine-scoped keys, 9.4) — sessions tests (per-entry engine field
288
- rejected precisely because set() would erase the streaming binding)
289
- - [x] Model catalog: same `antigravity/*` model ids and effort tiers resolve
290
- — shared provider catalog, engine-independent by construction
291
- - [x] Model/effort switch takes effect (gate A mechanism) — live: ACP
292
- `set_config_option` on a LOADED session; streaming recycle+resume
293
- - [x] G1 digest and G10 system prompt reach the prompt identically — shared
294
- prompt assembly in the provider; the driver receives the final prompt
295
- - [x] Skills `activate_skill` answered by the bridge as today — bridge
296
- server dep, same server serves both engines (ACP transport live)
297
- - [x] Usage counters: present (mapped) or documented-absent (zero-usage),
298
- per gate B — live: streaming mapped, ACP absent (Gate B row in the
299
- parity matrix)
300
- - [x] `/agy doctor` shows connection state, session id, prompt/session stats,
301
- lifecycle log — implemented engine-aware (phase 1); re-verify in pi
302
- before the phase 4 default flip
303
- - [x] Error paths: spawn failure, auth failure, protocol error all surface as
304
- visible turn errors, never as silent empty messages — fake tests
305
- (auth-required, load-fails) + live: a bad-shape registration surfaced
306
- as a visible `ACP session failed: Invalid params` turn error in the
307
- first parity attempt, exactly as designed
308
- - [x] `ask-tool` one-shot returns text, duration, abort/timeout states, and a
309
- resumable session id — unchanged `agy -p` path (phase-2 probe
310
- decides whether it moves to ACP)
311
- - [x] Overall-turn timer pauses while any round-trip is parked; a slow human
312
- permission decision cannot kill the turn — fake tests (park/kickIdle
313
- remaining budget)
314
- - [x] Cumulative-resend defensive guard on message chunks (port of
315
- `isCumulativeResend`), with a fake-server test. Note: provably inert
316
- on live RC01 (run-5 stress showed pure mid-token deltas); the fake
317
- server is the only surface that can exercise it — confirmed inert
318
- again in the live parity run
319
-
320
- ## 7. Regression risks and mitigations
321
-
322
- | Risk | Impact | Mitigation |
323
- | --- | --- | --- |
324
- | Usage tokens absent (CONFIRMED, Gate B) | Cost/token display zeros on ACP | ACCEPTED 2026-09-07 (user decision): Gate B no longer blocks the default flip - zero-usage is documented behavior on ACP. Both engines are permanently maintained; no deletion (standing decision), so zero-usage is opt-in per user forever (review 4, finding 2, as amended) |
325
- | ~~No model/effort switch per session~~ RESOLVED: Gate A PASS | — | `session/set_config_option` verified live; per-session switching, no recycle |
326
- | Slash commands expanded server-side | Could double-expand with our prompt assembly | Probe F.7. If present: keep our prefixes out of command-looking lines, or ignore server command list |
327
- | `request_permission` with no pi-side permission UI | Cannot render a native dialog | Single `auto` policy (parity with `skipPermissions: true`); per-tool gating for pi tools belongs to the tools/extensions and is preserved end-to-end by G9 (9.3). Never hang |
328
- | Human decision latency on parked G9 tool round-trips exceeds the 10m overall-turn cap | Turn killed mid-wait | `AcpDriver` pauses the overall timer while parked, re-armed with REMAINING budget; every park carries its own timeout (9.3) |
329
- | Binary size (1.5 GB + 117 MB) | Disk + update churn | Pinned install layout (section 12), registry pin by build id |
330
- | aarch64 TCMalloc startup failure (forum report, Chromebook) | arch-specific | x86_64 unaffected; gate install per-arch, link the forum issue in README |
331
- | Separate auth state from `agy` CLI | First-run auth friction | `/agy auth-manual` prints exact instructions; `gemini-api-key` path works headless via `GEMINI_API_KEY`. The agent never writes credentials |
332
- | Thought text changes rendering volume | Noisy transcript | Route through the existing thinking block pipeline, close-on-switch unchanged; no new block types |
333
- | Two engines drift on behavior | Confusing support surface | Shared provider layer + shared parity suite run against both engines in CI |
334
-
335
- ## 8. Phase 0: probes and decision gates
336
-
337
- One authenticated live session answers every UNKNOWN. No product code in this
338
- phase. Deliverable: findings appended to this doc as section 8.1 (or
339
- `docs/ACP-PROBE-NOTES.md`), and each gate marked PASS / FALLBACK.
340
-
341
- Probe scripts (JSON-RPC over stdio, one object per line):
342
-
343
- ```jsonc
344
- // P1 handshake with MINIMAL client capabilities (our phase-1 posture)
345
- {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false}}}
346
-
347
- // P2 auth (pick one; api-key path is headless)
348
- // export GEMINI_API_KEY=... first
349
- {"jsonrpc":"2.0","id":1,"method":"authenticate","params":{"method":"gemini-api-key"}}
350
-
351
- // P3 session + bridge registration (exact mcpServers param shape is itself a probe item)
352
- {"jsonrpc":"2.0","id":2,"method":"session/new","params":{"cwd":"/abs/cwd","mcpServers":[{"name":"pi-bridge","type":"http","url":"http://127.0.0.1:<port><path>","headers":[]}]}}
353
-
354
- // P4 model/effort switch attempts, in order until one works
355
- {"jsonrpc":"2.0","id":3,"method":"session/set_config_option","params":{"sessionId":"<sid>","configOptionId":"model","value":"<slug>"}}
356
- {"jsonrpc":"2.0","id":4,"method":"session/set_config_option","params":{"sessionId":"<sid>","configOptionId":"effort","value":"high"}}
357
- {"jsonrpc":"2.0","id":5,"method":"session/set_mode","params":{"sessionId":"<sid>","modeId":"<id>"}}
358
-
359
- // P5 prompt: stream a simple prompt; capture every session/update verbatim
360
- {"jsonrpc":"2.0","id":6,"method":"session/prompt","params":{"sessionId":"<sid>","prompt":[{"type":"text","text":"Say hello and list the files in this directory."}]}}
361
-
362
- // P6 mid-stream cancel: send during P5, observe prompt response stopReason
363
- {"jsonrpc":"2.0","id":7,"method":"session/cancel","params":{"sessionId":"<sid>"}}
364
-
365
- // P7 image prompt (small png data URL), verify the model actually sees it
366
- {"jsonrpc":"2.0","id":8,"method":"session/prompt","params":{"sessionId":"<sid>","prompt":[{"type":"image","data":"<base64>","mimeType":"image/png"},{"type":"text","text":"What does this image show?"}]}}
367
-
368
- // P8 session lifecycle: list, resume, load across server restart
369
- {"jsonrpc":"2.0","id":9,"method":"session/list"}
370
- {"jsonrpc":"2.0","id":10,"method":"session/resume","params":{"sessionId":"<sid>","cwd":"/abs/cwd"}}
371
-
372
- // P9 tool exercise: prompt that forces an edit + a command run; capture
373
- // tool_call / tool_call_update shapes and any session/request_permission
374
- ```
375
-
376
- As executed, live param corrections were applied against these planned
377
- scripts: `methodId` (not `method`), `configId` (not `configOptionId`), and
378
- mcpServers http entries require `headers: []` (a LIST). See 8.1 and the
379
- reference doc.
380
-
381
- Capability posture, fixed for phases 1-3 and for every probe: `fs` false,
382
- `terminal` false in the initialize client capabilities. Rationale (corrected
383
- during the skeptical re-check of the peer review): parity, not philosophy.
384
- agy keeps executing its own tools exactly as it does today; answering `fs/*`
385
- or `terminal/*` would put file and process execution inside our process (new
386
- machinery to build, secure, and hold at parity with agy's native behavior)
387
- for no phase-1 gain. Diff-render and the permission surface stay meaningful.
388
- This is a decision, not a default; revisit only as a phase 3+ enhancement
389
- with its own design pass.
390
-
391
- Concurrency note: the driver design is one active turn per connection (the
392
- `#active` singleton). No probe exercises a second concurrent session while a
393
- permission request is pending. Unverified by design, not by omission.
394
-
395
- What to record for each gate:
396
-
397
- | Gate | Question | PASS means | FALLBACK (still parity) |
398
- | --- | --- | --- | --- |
399
- | A | Model + effort switch per session? | `set_config_option` (or equivalent) switches both; next prompt uses them | NONE viable: the ACP binary takes no model flags, so respawn-per-switch cannot carry a model; a settings.json rewrite per switch is the only theoretical path and is rejected (unproven, racy). Gate A FAIL blocks the phase 4 default flip; ACP stays opt-in; the gap goes upstream |
400
- | B | Usage anywhere? | Any update or `_meta` carries token counts | Zero-usage documented; README notes it |
401
- | C | `tool_call` content richness | rawInput/output/locations enough to retire WrapperReplay + native-tools | Keep current replay machinery unchanged |
402
- | D | Cancel latency + state | `stopReason: cancelled` within ~2s; session reusable after | Keep kill semantics on streaming; on ACP keep kill as escalation path |
403
- | E | Startup cost | Cold start < 5s, steady RSS sane for a long-lived process | Spawn per turn-group only (like the streaming recycle cadence) |
404
- | F | MCP `mcpServers` registration | Bridge `tools/list` visible to the agent mid-turn | Keep bridge wired some other protocol-sanctioned way; if none, G9 parking breaks and phase 1 is BLOCKED |
405
- | F.7 | Slash behavior | Commands not auto-expanded against our prompt text | Prefix-safe prompt assembly unchanged |
406
-
407
- Also verify in P5 whether permission requests arrive at all with `auto`
408
- capabilities off, and whether `fs/write_text_file` requests appear for edits
409
- (they should NOT, with our fs capabilities false; if they do, we answer them
410
- with real local writes, same trust domain).
411
-
412
- ### 8.1 Phase 0 findings (COMPLETE, 2026-09-03)
413
-
414
- Auth completed live: oauth-personal via the BROWSER-capture trick plus an
415
- `ssh -L` loopback tunnel (the server never surfaces the URL headless; full
416
- flow documented in [ACP-PROTOCOL-REFERENCE.md](./ACP-PROTOCOL-REFERENCE.md)).
417
- Run 5 (authenticated, P1-P6) and run 6 (post-restart: load, tools, image,
418
- Gate F) complete. Raw traffic: `probe-logs/acp-traffic-run5.jsonl` (600
419
- JSONL lines) and `acp-traffic-run6-restart-load-tools.jsonl` (28 lines);
420
- conversation store snapshot + schema: `probe-logs/acp-server-conversations/`.
421
-
422
- | Gate | Verdict | Evidence |
423
- | --- | --- | --- |
424
- | A: model/effort switch | **PASS (live)** | `session/set_config_option` `{configId:"model", value:"gemini-3.8-flash-low"}` changed `currentValue`. Effort baked into full slugs; full catalog + default (`gemini-3.7-flash-high`) ships in `session/new` — the `agy models` CLI is not needed on the ACP engine |
425
- | B: usage | **ABSENT on RC01** | zero token fields in every payload across both runs, including tool flows. Accept zero-usage (existing documented fallback); re-check per build |
426
- | C: tool_call content | **PASS** | `tool_call`/`tool_call_update` carry kind, status lifecycle, `content` with `{type:"diff", path, newText}` (edits arrive AS DIFFS), `locations`, `rawInput`, optional `rawOutput`. Enough to retire wrapper-replay rendering in phase 3 |
427
- | D: cancel | **FAIL on RC01** | `session/cancel` → `-32601 Method not found` (verified). Abort fallback **kill + `session/load` VERIFIED WORKING** (run 6): session restored with full history after process death |
428
- | E: startup cost | **PASS** | initialize ~5-6 s cold (three runs); steady RSS ~327 MB (5 min mixed load) |
429
- | F: bridge via mcpServers | **PASS (shape)** | `{name, type:"http", url, headers:[]}` accepted; dead URL tolerated at creation (lazy connect). Phase-1 TODO: real bridge `tools/list`+`tools/call` end-to-end (G9) |
430
-
431
- Additional live-verified facts (details in the reference doc): token
432
- persistence across restart PASS; `session/load` replays history as FULL-TEXT
433
- chunks (driver must treat as replay, not live); config does NOT persist
434
- across restart (driver re-applies model/effort/mode after every restart);
435
- `session/request_permission` flow verified (options `allow`/`deny`,
436
- kinds `allow_once`/`reject_once`; answer `{outcome:{outcome:"selected",
437
- optionId}}`); image prompts PASS end-to-end; `set_mode` and per-session
438
- `session/close` work; approved tool call may be superseded by another
439
- toolCallId (track effects, not ids).
440
-
441
- Schema corrections captured live (all in the reference doc): `authenticate`
442
- takes `methodId` (not `method`); `set_config_option` takes `configId` (not
443
- `configOptionId`); `settings.json` is read at server STARTUP only; -32602
444
- error bodies carry `data.errors[].loc` field paths (use as correction
445
- oracle). Streaming is pure-delta on live prompts (mid-token chunk splits; no
446
- cumulative resends). Modes `default`/`auto_edit`/`yolo` map to our
447
- `mode`/`skipPermissions`. The ACP conversation store is per-session SQLite
448
- with opaque `steps.step_payload` blobs — same two-phase-write family as
449
- legacy issue #1; never poll these DBs (re-validated decision).
450
-
451
- Probe list CLOSED. Phase 0 go/no-go: GO for phase 1, with Gate D's kill+
452
- reload abort path and the Gate F bridge end-to-end check as phase-1
453
- acceptance items.
454
-
455
- ## 9. Target architecture
456
-
457
- ### 9.1 New modules
458
-
459
- ```
460
- src/acp/jsonrpc.ts newline-delimited JSON-RPC 2.0 framing: request/response
461
- correlation map, concurrent requests, error responses,
462
- notification + server-request dispatch, malformed-line
463
- tolerance. Pure transport, no agy knowledge.
464
- src/acp/connection.ts process lifecycle: spawn AGY_ACP_BIN, initialize
465
- handshake, authenticate detection (-32000), session/new
466
- / load / prompt / cancel / set_config_option, client-side
467
- method handlers (request_permission policy, fs, terminal),
468
- outbound event queue.
469
- src/acp/driver.ts AcpDriver: implements the StreamDriver surface (section 9.2)
470
- so provider.ts keeps working unchanged. State machine,
471
- timers, parks, snapshot, close.
472
- src/acp/events.ts session/update to DriverActivity mapping + stopReason
473
- mapping. Pure functions, unit-tested against captured
474
- probe transcripts.
475
- scripts/smoke-acp.mjs live smoke, gated by AGY_ACP_LIVE=1 (spends quota),
476
- mirrors smoke-stream-json.mjs.
477
- ```
478
-
479
- ### 9.2 Driver contract (unchanged surface, additive only)
480
-
481
- `AcpDriver` matches the existing `StreamDriver` public surface: `state`,
482
- `activeHandle`, `run()`, `reentry()`, `kickIdle()`, `set onTurnEnd()`,
483
- `snapshot()`, `close()`. `DriverActivity` gains one additive variant; the
484
- streaming engine never emits it:
485
-
486
- ```ts
487
- // stream-events/driver today: { type: "thought"; tokens: number }
488
- // ACP addition:
489
- | { type: "thought"; tokens?: number; delta?: string }
490
- ```
491
-
492
- `provider.ts` change: when `delta` is present call `appendThinking`, else keep
493
- the current token-count behavior. One `if`, additive, streaming untouched.
494
-
495
- Interface extraction (review 2, finding 7): `provider.ts` imports the
496
- concrete `StreamDriver` class today; without a neutral type the two engine
497
- wirings would each drag the other's module in. Phase 1 extracts a
498
- `TurnDriver` interface into a neutral module
499
- (`src/driver-types.ts`); both drivers implement it; `provider.ts` and
500
- `ToolRoundTrips` depend on the interface only. `/agy engine` changes require
501
- a pi restart (driver wiring happens at extension load); a live engine router
502
- is explicitly deferred as speculative.
503
-
504
- Teardown contract (review 3, finding 1 — the Gate D kill+reload path needs
505
- an explicit answer for in-flight JSON-RPC state): when the connection dies
506
- or is killed, `AcpConnection.abortAll()` rejects every outstanding
507
- correlation-map entry (the pending `session/prompt` request, any unanswered
508
- client-side requests), the driver settles the turn outcome as
509
- `ERROR`/`aborted` (never left pending), and `onTurnEnd` fires so the
510
- provider's `roundTrips.failAll()` fails parked round-trips exactly as the
511
- streaming driver's `failTurn` path does. Nothing is ever written to a dead
512
- socket; no promise survives the kill. Mirrors `ToolRoundTrips.failAll()`.
513
-
514
- Event mapping (`src/acp/events.ts`):
515
-
516
- | ACP | DriverActivity | pi mapping (provider.ts, existing) |
517
- | --- | --- | --- |
518
- | `agent_message_chunk` | `{type:"text", delta}` | text block deltas |
519
- | `agent_thought_chunk` | `{type:"thought", delta}` | thinking block deltas |
520
- | `tool_call` (first) | `{type:"tool_start", name, args}` | buffered; render on completion |
521
- | `tool_call_update` completed | `{type:"tool_done", name, args, output}` | thinking label / native re-exec / wrapper (gate C) |
522
- | `tool_call_update` failed | `{type:"tool_error", name, message}` | thinking label |
523
- | `plan` | (phase 2+) thinking label `[agy plan]` | no pi plan block exists yet |
524
- | `available_commands_update` | snapshot field only | `/agy doctor` |
525
- | usage (if found, gate B) | `{type:"usage", usage}` | `toPiUsage` |
526
- | prompt response `end_turn` | TurnOutcome OK | `stopReason: "stop"` |
527
- | `cancelled` | TurnOutcome aborted | `stopReason: "aborted"` |
528
- | `refusal` / `max_turn_requests` / `max_tokens` | TurnOutcome ERROR / OK+note | `error` / `stop` + `errorMessage` |
529
- | `user_message_chunk` (during `session/load` only) | suppressed (history replay) | never emitted to pi as live text |
530
-
531
- Defensive guard ported from the streaming engine: if an `agent_message_chunk`
532
- repeats the full accumulated text instead of a delta, detect and slice (the
533
- `isCumulativeResend` lesson; same agy binary lineage, same quirk class).
534
- Guard plus test ship in phase 1. Honest labeling: the ACP spec defines chunks
535
- as deltas, so on a compliant server the guard stays inert; it earns its ten
536
- lines because this exact failure mode was observed in the wild on the same
537
- backend's private protocol, and its steady-state cost is one prefix
538
- comparison per chunk.
539
-
540
- Load-replay rules (run 6, verified): `session/load` replays the full
541
- conversation as `user_message_chunk`/`agent_message_chunk` PAIRS carrying
542
- FULL TEXT, not the original deltas. `AcpDriver` marks replay chunks as
543
- history and swallows them; none reach pi as live generation. Two more
544
- restart rules: config does NOT persist across restart (model/effort/mode are
545
- re-applied via `set_config_option` after every restart/load), and
546
- `session/load` takes the same `mcpServers` param as `session/new` — the
547
- bridge registration is passed on both.
548
-
549
- ### 9.3 Permission policy (simplified per decision 2026-09-03)
550
-
551
- pi has no native permission-gate concept; extensions that want gating
552
- implement it inside their own tools. G9 preserves that end-to-end: a pi tool
553
- called by agy executes in pi's loop, where the tool's own gating runs. The
554
- bridge-side `bridge` policy (routing `request_permission` through
555
- `ask_user_question` with a third round-trip kind and a decision cache) was
556
- REJECTED by decision: it built permission UX pi does not have, for agy-native
557
- tools, at real complexity cost. The full design lives in review-3 history if
558
- upstream ever ships allow-always kinds and the calculus changes.
559
-
560
- One policy ships:
561
-
562
- - `auto`: answer `session/request_permission` in-connection with the allow
563
- option. Byte-for-byte parity with today's `skipPermissions: true`. No
564
- round-trip, no provider involvement, no third kind; the once-only options
565
- quirk (run 6) is irrelevant under auto. Hard rule unchanged: the handler
566
- never hangs and never blocks the `session/prompt` response path.
567
-
568
- Consequence: `provider.ts` is truly UNCHANGED by adoption (its only additive
569
- line is the thought-delta `if`). Gate D teardown (9.2) still applies: on
570
- connection death the unanswered permission request dies with the socket and
571
- `abortAll()` settles everything.
572
-
573
- Timer interaction (reviews 1-2, design retained; scope narrowed by the 9.3
574
- decision to G9 tool parks): the overall-turn timer is armed once per turn and
575
- only the idle timer pauses while a round-trip is parked. A human answer
576
- through a parked pi tool (e.g. `ask_user_question`) can legitimately outrun
577
- 10 minutes. `AcpDriver` pauses the overall timer while any round-trip is
578
- parked and, on settle, re-arms it with the REMAINING budget (`deadline -
579
- now`), never a fresh cap: the overall timer is a turn DEADLINE, not an
580
- inactivity guard. Every park carries its own timeout (`BRIDGE_TIMEOUT_MS`),
581
- so a paused deadline cannot hang forever. Driver-local (AcpDriver only);
582
- streaming behavior untouched; ships in phase 1.
583
-
584
- ### 9.4 Sessions schema (engine-scoped keys)
585
-
586
- The first design (a per-entry `engine` field) was rejected in review 2:
587
- `set()` overwrites the single `sid:<pi-session>` key, so one ACP turn ERASES
588
- the streaming conversation binding, and a rollback loses continuity, breaking
589
- the section 13 guarantee. Engines scope at the KEY level instead:
590
-
591
- ```jsonc
592
- // ~/.pi/agent/antigravity-bridge/sessions.json
593
- {
594
- "sid:<pi-session>": { "conversationId": "<streaming id>", "lastStepIdx": -1, "lastMessageCount": 42 },
595
- "sid:<pi-session>@acp": { "conversationId": "<ACP sessionId>", "lastStepIdx": -1, "lastMessageCount": 42 }
596
- }
597
- ```
598
-
599
- - Un-suffixed keys belong to the streaming engine, byte-compatible with every
600
- persisted store; ACP keys carry an `@acp` suffix appended by
601
- `sessionKey()` when `config.engine === "acp"`.
602
- - Engines never touch each other's keys: rollback keeps both continuations,
603
- `narrowStoreMap` needs no change (keys pass through untouched), and no
604
- migration runs.
605
- - Under ACP the stored id is the ACP sessionId; `session/load` is attempted
606
- first on resume, our store only remembers which id to load.
607
-
608
- ### 9.5 Config schema
609
-
610
- ```jsonc
611
- // ~/.pi/agent/antigravity-bridge/config.json (additive)
612
- {
613
- "engine": "stream-json", // "stream-json" | "acp"; default stream-json
614
- "acp": {
615
- "bin": "", // path to agy_acp_server.par; empty = resolve
616
- "permissions": "auto" // auto-approve request_permission; single policy (9.3)
617
- }
618
- }
619
- ```
620
-
621
- - Env: `AGY_ENGINE`, `AGY_ACP_BIN`, `AGY_ACP_PERMISSIONS` (file < env, the
622
- documented precedence pattern).
623
- - History note: an `engine` key existed before 1.3.2 (sqlite engine) and was
624
- removed; stale values in old config files are already dropped by narrowing.
625
- Values narrow to `"stream-json" | "acp"`, anything else falls back to the
626
- default. The sqlite engine is not coming back.
627
- - Binary resolution order: `AGY_ACP_BIN` > `acp.bin` > `agy_acp_server.par`
628
- on PATH > install layout default (section 12). Resolution failure is a
629
- visible turn error naming what was tried.
630
-
631
- ## 10. Phase plan
632
-
633
- ### Phase 0: probes, gates, install, auth (no product code) — ✅ COMPLETE (2026-09-03)
634
-
635
- - [x] Run probes P1-P9 (section 8). Gate verdicts recorded in 8.1; captured
636
- protocol in ACP-PROTOCOL-REFERENCE.md; raw traffic in `probe-logs/`.
637
- - [x] Install layout + pin (section 12): binary at
638
- `~/.local/opt/agy-acp/<build>/` + `current` symlink + zip.sha256.
639
- - [x] Auth onboarded: oauth-personal (token persisted, survives restart —
640
- verified run 6).
641
- - [x] Deliverable: gates marked — A PASS, B ABSENT (managed), C PASS,
642
- D FAIL + fallback VERIFIED, E PASS, F PASS-shape. GO for phase 1.
643
-
644
- ### Phase 1: ACP transport engine, parity only — 🔨 BUILT (2026-09-03), acceptance open
645
-
646
- Files: new `src/acp/*`, `src/config.ts` (engine + acp block), `src/sessions.ts`
647
- (engine tag → shipped as engine-scoped KEYS, see 9.4), `extensions/index.ts`
648
- (engine selection + doctor additions), `src/provider.ts` (driver injection
649
- point; `createStreamSimple` already takes the driver as a dep; no other
650
- provider change).
651
-
652
- - [x] `jsonrpc.ts` + `connection.ts` + `events.ts` + `driver.ts` as specified
653
- (+ `TurnDriver` extraction to `src/driver-types.ts`).
654
- - [x] `extractUserPrompt` unchanged (text only in phase 1; images in phase 2).
655
- - [x] `mcpServers` registration of the bridge server at `session/new` AND
656
- `session/load` (run 6: load takes the same param; resumed sessions must
657
- keep bridge access). REGISTERED — live end-to-end still pending, see
658
- acceptance below.
659
- - [x] Permission policy locked to `auto` (single policy; no bridge policy, see
660
- 9.3).
661
- - [x] `session/cancel` wired to pi abort and `session/close` sent on shutdown
662
- (pulled up from phase 2 by review 2, finding 6: abort must not kill the
663
- server process, and shutdown should not leak session state server-side).
664
- RC01 has no cancel: probe-once → -32601 → teardown+kill+reload, probed
665
- state shown in doctor.
666
- - [x] Overall-turn timer pause while parked, REMAINING-budget semantics
667
- (9.3), covered by tests (park/kickIdle deadline test).
668
- - [x] Fake-server suites: jsonrpc framing (incl. partial-line buffer), events
669
- mapping, driver flows (happy, load-replay suppression, permission auto,
670
- Gate D abort, auth error), engine config narrowing — suite green.
671
- - [x] `/agy engine acp|stream-json` command + `/agy doctor` shows engine,
672
- server version, session counts, cancel support.
673
- - [x] Live smoke through the full stack (`scripts/smoke-acp.mjs`, quota-
674
- gated): PASS.
675
- - [x] Round-5 implementation review (agy): P0 framing buffer + 3 P1 + 3 P2
676
- all fixed.
677
-
678
- Open acceptance (blocking "phase 1 done"):
679
-
680
- - [x] Bridge `tools/list` + `tools/call` end-to-end on the ACP engine (Gate F
681
- live) — VERIFIED 2026-09-03, `scripts/smoke-acp-bridge.mjs`, 3/3 runs:
682
- real server accepted the registration, listed the bridge catalog, called
683
- `bridge_echo`, and finished its turn with the result (`ECHO:BRIDGE-E2E-777`).
684
- The check also found and fixed a real defect: the bridge 403s any request
685
- without the `x-bridge-token` shared secret, but the ACP registration sent
686
- `headers: []`. Fix: `McpServerHandle` now exposes `token` (and
687
- `TOKEN_HEADER` is exported); the extension builds the registration headers
688
- from the handle. Known cosmetic gap (phase 2): `tool_done.output` carries
689
- agy's tool title ("Bridge echo call"), not the result content — the model
690
- DID receive the result; only the activity display lacks it. The pi-side G9
691
- park/resume is engine-independent (proven live on stream-json); the smoke
692
- answers deps directly to isolate the agy-side HTTP client, the only part
693
- no fake server could stand in for.
694
- - [x] §6 parity checklist executed LIVE on both engines — 2026-09-03,
695
- `scripts/parity-live.mjs`, 14/14 (7 scenarios × 2 engines). The run
696
- caught a REAL driver bug: a killed connection's late exit (RC01's
697
- signal handler intercepts SIGTERM and outlives its replacement)
698
- clobbered `#conn` and failed the recovery turn with the old stderr.
699
- Fixed: exit handling is connection-scoped (`stale-connection-exited`
700
- logged and ignored); regression test reproduces the race
701
- deterministically via `ACP_FAKE_SLOW_DEATH_MS`.
702
- - [x] README / DEVELOPMENT / CHANGELOG updates — README (two engines,
703
- `/agy engine`, `/agy acp-auth`, `AGY_ENGINE`/`AGY_ACP_BIN`, ToS
704
- wording covers both official binaries), DEVELOPMENT (ACP suites +
705
- 3 live scripts), CHANGELOG `[Unreleased]` (engine, token fix,
706
- stale-exit fix, RC01 limitations).
707
-
708
- Acceptance gate: every section 6 box `[x]` on the ACP engine, or explicitly
709
- marked with its gate fallback.
710
-
711
- ### Phase 2: protocol-native wins — ✅ COMPLETE (2026-09-03)
712
-
713
- Probe evidence: `probe-logs/acp-phase2-traffic.jsonl` + shapes recorded in
714
- ACP-PROTOCOL-REFERENCE.md "Phase-2 probe findings".
715
-
716
- - ~~`permissions: "bridge"` policy~~: REMOVED by decision 2026-09-03 (pi has no
717
- native permission concept; per-tool gating belongs to the tools themselves
718
- and is preserved by G9; see 9.3).
719
- - [x] Plan-mode equivalence probe: ACP modes are permission modes only
720
- (default/auto_edit/yolo) — NO review-only mode exists. The server's
721
- `/plan` command is intercepted server-side (F.7) and WRITES the plan
722
- artifact under auto policy. Verdict: plan delegations keep
723
- `agy -p --mode plan` (the committed exception); do not route mode:plan
724
- through ACP.
725
- - [x] Image prompt blocks forwarded from pi content — driver `images`
726
- param, `connection.prompt` builds typed blocks ahead of the text,
727
- provider extracts image blocks from the user message, models advertise
728
- `input: ["text","image"]` on the ACP engine only (engine read at load;
729
- engine switches require a restart). Verified live: 64x64 two-tone PNG
730
- through the full driver stack (`scripts/smoke-acp-image.mjs`) and in the
731
- probe.
732
- - [x] Thought text deltas through the thinking pipeline — already wired in
733
- phase 1 (provider renders `thought` deltas into the thinking block);
734
- probe finding: chunks are SPARSE on RC01 (a step-by-step prompt produced
735
- zero; reasoning ships as plain message text). No further work justified.
736
- - [x] `session/cancel` parity — landed in phase 1 (Gate D fallback,
737
- abort-recover live on both engines).
738
- - [x] Model/effort switching via the gate A mechanism — PASS (live,
739
- including on a loaded session; parity scenario).
740
- - [x] Tool-frame display fixes (found by the probe, shipped with this
741
- phase): `tool_call_update` output prefers `content[]` text over the
742
- display-title `rawOutput`; MCP args unwrap the `arguments` envelope;
743
- tool name prefers `_meta.mcp.tool` over the "<server>_<tool>" title.
744
- - [x] "Plan updates rendered as thinking labels" — OBSERVED-ABSENT on
745
- RC01: no `plan` sessionUpdate type exists; artifacts arrive as ordinary
746
- edit tool_calls (already rendered as cards). Item closed as not
747
- applicable, not deferred.
748
- - Acceptance: no parity regression (14/14 re-run after the mapping
749
- changes); cancel probe done (phase 1); image probe end-to-end (probe +
750
- live smoke).
751
-
752
- ### Phase 3: consolidation (immediate priority, opt-in only)
753
-
754
- Hard rule: Keep both engines in the extension. Default engine remains `stream-json`. All items below are non-breaking opt-in enhancements for the ACP engine that do NOT alter default behavior or touch streaming paths.
755
-
756
- Queue:
757
- 1. [x] Gate C evaluation: `native-tools.ts` + `WrapperReplay` retired on the ACP engine (retained for the streaming (`stream-json`) engine). ACP turns emit thinking labels for tool executions without parking turns for synthetic re-execution or wrapper cards; bridge MCP tools (`bridge_call`) continue parking natively. Verified: live parity 14/14 held.
758
- 2. [x] Retire `diff-render.ts` (git path) on the ACP engine — DONE 2026-09-04. The phase-2 probe proved ACP surfaces edit diffs natively (`tool_call` `content[]` `{type:"diff", path, oldText?, newText}`), which is richer AND cheaper than the git-sourced path: implemented as (a) `events.ts` extracts the first diff entry into the `tool_done` activity (`AcpEditDiff`); (b) `provider.ts` renders it on ACP turns via the new `formatInlineDiff` (in-memory `generateDiffString`, same line-numbered format, ZERO git subprocesses) instead of `TurnDiffContext.diffEdit`; (c) `diff-render.ts` git machinery remains solely for the `stream-json` engine. Tests: diff extraction (with/without `oldText`), ACP thinking-stream rendering (`+2 B` line-numbered), label-only fallback, no parking, streaming path unchanged. Verified: 165/165 vitest, tsc clean, live parity 14/14.
759
- 3. [x] `/agy doctor` diagnostics expansion — DONE 2026-09-04. The ACP
760
- snapshot now carries `reconnects` (connections beyond the first =
761
- Gate D kills + stale-exit replacements) and the handshake `agentInfo`
762
- name/title; `/agy doctor` surfaces both (reconnects in the stats line,
763
- agent title next to the server version). Regression test pins
764
- `reconnects = 1` on the stale-exit flow.
765
- 4. [x] G1 digest via `embeddedContext` resource blocks — DONE 2026-09-04
766
- (the optional enhancement, adopted as the ACP delivery path). On the
767
- ACP engine the digest ships as a native `{type:"resource"}` block in
768
- the prompt array (`connection.prompt` builds images → resource →
769
- text) instead of inline prompt text; `stream-json` keeps the inline
770
- default. Verified live: a resource block with a secret word was read
771
- and answered correctly by RC01 (`promptCapabilities.embeddedContext`
772
- is functional, not just advertised).
773
-
774
- Acceptance: parity suite remains 14/14, doctor parity, no breakage to stream-json default.
775
-
776
- Status: 🚧 Phase 3 remaining items are COMPLETE (2026-09-04): 168/168 tests,
777
- tsc clean, live parity 14/14, embeddedContext verified live (resource block
778
- with a secret word answered correctly). Phase-3 acceptance: nothing deletes
779
- in phase 3 — the streaming modules stay (permanently, see the standing
780
- decision at the top and Phase 4).
781
-
782
- ### Phase 4: default flip (deferred)
783
-
784
- All items below are deferred until a full soak cycle of both engines has completed and upstream conditions are met:
785
-
786
- 1. AskAntigravity migration to ACP one-shot (migrating while `stream-json` is default breaks streaming-only users who haven't onboarded ACP auth; streaming conversation ids cannot resume under ACP). `mode: "plan"` delegations keep the `agy -p --mode plan` path (committed exception).
787
- 2. Delete `src/discovery.ts` (`/proc` fd-scan) once AskAntigravity migration completes.
788
- 3. Default flip: `config.engine` default becomes `"acp"` after one full release soak cycle with both engines shipping. Gate A PASS is verified. Gate B is NOT a precondition (lifted as a blocker 2026-09-07); the soft items in section 17 ship in the same release.
789
- 4. Upstream Gate B resolution (optional, informational): Google ships usage counters in ACP payloads. The `/agy doctor` watch reports it when it happens. Mapping it in is a small additive job; the streaming engine stays regardless.
790
-
791
- NO deletion phase: the streaming engine is a permanently maintained peer (standing user decision, 2026-09-07). The old phase-4 "streaming deletion" item is void.
792
-
793
- Acceptance: the default-flip release ships with the parity suite as acceptance evidence.
794
-
795
- ## 11. Test strategy
796
-
797
- - **Framing** (`jsonrpc.ts`): correlation, concurrency, error responses,
798
- notifications vs requests, malformed lines, backpressure. Pure unit tests.
799
- - **Mapping** (`events.ts`): every session/update variant from captured probe
800
- transcripts to DriverActivity; stopReason table; unknown-variant tolerance
801
- (the `parseAgyLine` lesson, re-applied).
802
- - **Driver** (`driver.ts`): scripted fake ACP server (a tiny stdio JSON-RPC
803
- node script under `tests/helpers/`) covering: handshake, session/new fail
804
- auth, streaming prompt, park suspends idle timer, kickIdle rearm, cancel
805
- mid-stream, connection death mid-turn, recycle fallback path.
806
- - **Provider**: the existing `provider-streaming` / `provider-digest` /
807
- `provider-sysprompt` suites run against both engines via the shared driver
808
- contract. New case: thought delta path.
809
- - **Sessions**: engine-tag matching, streaming-file compatibility (no tag),
810
- cross-engine miss starts fresh.
811
- - **Config**: narrowing of stale/garbage `engine` values, env precedence.
812
- - **Parity suite**: the section 6 checklist encoded as an integration run
813
- against the fake server for both engines; live parity runs stay
814
- `AGY_ACP_LIVE=1` gated (quota).
815
-
816
- ## 12. Binary management, pinning, auth onboarding
817
-
818
- Install layout:
819
-
820
- ```
821
- ~/.local/opt/agy-acp/<build>/
822
- agy_acp_server.par
823
- localharness_external
824
- ~/.local/opt/agy-acp/current -> <build> # symlink; AGY_ACP_BIN default
825
- ```
826
-
827
- - Pin by build-stamped registry URL (`...20260818_01_RC01-...zip`), not by
828
- the moving registry index. The registry updates hourly; a float could
829
- change the engine under a running install mid-release.
830
- - The registry publishes no checksums. Record the zip SHA-256 at install time
831
- in the install dir for drift detection. Propose an upstream checksum
832
- request (issue) once we depend on this.
833
- - `--uid=` from the linux registry entry: the server ran without it here.
834
- Treat as a launcher artifact; revisit if a future build refuses to start.
835
- - Auth onboarding: `/agy auth-manual` prints the four methods with exact
836
- `settings.json` shapes (source: official Zed docs, section 2.3). Headless
837
- path: `GEMINI_API_KEY` + `auth.type: "gemini-api-key"`. The agent NEVER
838
- writes or reads credentials; it prints instructions and verifies by
839
- re-running `session/new` until the error clears.
840
-
841
- ## 13. Rollback
842
-
843
- At any phase: `AGY_ENGINE=stream-json` (or `/agy engine stream-json`) returns
844
- to the streaming engine. Sessions are keyed per engine (9.4), so the streaming
845
- engine resumes its own conversations untouched. The streaming engine is only deleted in
846
- phase 4, one release after the flip, so rollback stays possible throughout.
847
-
848
- ## 14. Documentation updates
849
-
850
- | Doc | Change | Phase |
851
- | --- | --- | --- |
852
- | `README.md` | engine section, binary install, auth, arch note | 1, 4 |
853
- | `docs/ARCHITECTURE.md` | ACP engine section alongside stream-json | 1 |
854
- | `docs/DEVELOPMENT.md` | smoke-acp, fake server, probe workflow | 1 |
855
- | `docs/ACP-ADOPTION-PLAN.md` | this file; gate results appended | 0 |
856
- | `docs/PI-BRIDGE-GAPS.md` | G1 note: per-tool gating stays extension-owned; G9 preserves it under ACP | 1 |
857
- | `CHANGELOG.md` | per phase, house style | each |
858
-
859
- ## 15. Risk register
860
-
861
- | # | Risk | L | I | Response |
862
- | --- | --- | --- | --- | --- |
863
- | R1 | Gate A fails (no model switch) | M | H | default flip blocked; ACP stays opt-in; escalate upstream. No ship-anyway: model switching is not optional |
864
- | R2 | Gate F fails (bridge not registrable) | L | H | BLOCKS phase 1; escalate upstream, keep streaming |
865
- | R3 | Usage absent (CONFIRMED, Gate B) | H | M | zero-usage documented on ACP; stream-json retained as secondary engine; phase-4 deletion conditioned on Gate B lift (review 4) |
866
- | R4 | Binary churn (hourly registry, RC builds) | M | M | pin by build id, record sha256 |
867
- | R5 | Server rejects second simultaneous session | L | M | one session per connection (today's model), N connections instead |
868
- | R6 | Thought text floods transcript | M | L | thinking pipeline unchanged, same close-on-switch |
869
- | R7 | ToS posture shifts again | L | H | official channel is Google's own; monitor registry + FAQ |
870
- | R8 | aarch64 breakage | L | L | per-arch gating |
871
- | R9 | ~~`bridge` permission throughput~~ removed with the bridge policy (9.3) | - | - | n/a |
872
-
873
- ## 16. Open questions
874
-
875
- CLOSED 2026-09-03 — every item was answered by the Phase 0 probes (section
876
- 8.1, [ACP-PROTOCOL-REFERENCE.md](./ACP-PROTOCOL-REFERENCE.md)). Notable
877
- answers: model catalog ships in `session/new` (CLI catalog unnecessary on
878
- ACP); edits surface as diffs inside `tool_call` content even with fs
879
- capabilities off; no usage fields anywhere; available_commands_update carries
880
- `plan` and `logout`; cold start ~5-6 s, steady RSS ~327 MB.
881
-
882
- ## 17. Default-flip readiness (phase 4) — analysis as of 2026-09-07
883
-
884
- Standing analysis so nobody re-derives it: what blocks flipping the default
885
- engine from stream-json to ACP. Re-verify only the dated facts (Gate B
886
- status via `/agy doctor`); the structural conclusions hold.
887
-
888
- **Verdict: nothing blocks the flip. Gate B was REMOVED as a blocker on
889
- 2026-09-07 (user decision: token usage does not matter for the extension's
890
- purpose - agy runs on subscription quota, cost stays zero either way).
891
- Zero-usage on ACP is accepted as documented behavior. Remaining before the
892
- flip: the four soft items below, shipped in the same release.**
893
-
894
- Already green:
895
-
896
- - Parity contract (section 6) is ALL `[x]`, live-verified on both engines
897
- (parity suite 14/14).
898
- - Everything shipped since 1.4.9 is engine-aware: bridge registration is
899
- ACP-native (`session/new` mcpServers) AND stream-json-native
900
- (`~/.gemini/config/mcp_config.json`); tool-result images ride both engines
901
- (probe-verified on each); the approval gate's ACP hook firing is the
902
- LIVE-verified side (stream-json is docs-attested only), so the flip does
903
- not weaken it.
904
- - Rollback is one config flip; sessions are engine-scoped so bindings never
905
- cross.
906
-
907
- Former blocker — Gate B, LIFTED 2026-09-07:
908
-
909
- - RC01 sends no usage/token fields in ANY payload, so ACP shows zero
910
- tokens. This used to block the default flip; the user lifted it: agy
911
- runs on subscription quota and cost is zero regardless, so the display
912
- gap is cosmetic for this extension. Zero-usage on ACP is documented
913
- behavior.
914
- - Both engines are PERMANENTLY maintained (standing user decision
915
- 2026-09-07): no deletion phase exists, regardless of Gate B. stream-json
916
- is the supported fallback/secondary forever; zero-usage is opt-in per
917
- user by choosing the engine.
918
- - The `/agy doctor` Gate B watch stays armed as INFORMATIONAL: it prints
919
- "acp tokens: AVAILABLE" if upstream ever ships usage, at which point the
920
- mapping is a small additive job.
921
-
922
- Soft items to ship alongside the flip (not blockers):
923
-
924
- 1. Clean-machine first-run: ACP as DEFAULT means fresh installs hit the
925
- self-setup path (binary install + auth bootstrap) immediately. Self-heal
926
- exists; never tested as the first-run experience. Test once on a clean
927
- env.
928
- 2. User-visible abort change: Esc on ACP = kill + reconnect + reload (RC01
929
- has no `session/cancel`). Document in README and `/agy status`.
930
- 3. Pull soak evidence from the daily logs (warn rates per engine, 1.4.9+)
931
- and attach to this section.
932
- 4. Note that `AskAntigravity` stays on `agy -p` regardless (phase 4+).
933
-
934
- Mechanics of the flip (minutes): default in `src/config.ts` + the
935
- `acp-config` test default + README engine table + AGENTS.md + beta wording.
936
- `AGY_ENGINE` env override keeps an escape hatch either way.
937
-
938
- Recommendation on record: Gate B is no longer a condition. Remaining
939
- pre-flip work is the soft items above; the flip itself is a small,
940
- mechanical change whenever the soak cycle is judged complete.