ACP host runs lifecycle for Antigravity

Task 9945 · Done · Coder · created · updated

Original request (verbatim): "Close the gap between Claude, Codex, Antigravity and Pi. More universal code, less provider specific" Settled inputs: PRD (Task 9932) and Architecture (Task 9933) of this epic. Approach (architecture §1): lifecycle events run in Jaah-owned code in front of every session (ACP: `jaah-acp-host`, `apps/jaahd/acp/host.py`; terminal: node daemon + vendor CLI events); tool events run in-band through ONE neutral dispatcher `tools/hook_bridge_core.py::dispatch`. Adapters contribute only declarations (event names, tool-name map, placement literals, error text patterns, question prefix), never a copy of the logic. Requirement R1 (verbatim): "Each of these mechanisms has exactly ONE provider-neutral implementation home that every provider uses; an adapter contributes only vendor literals and capability declarations, never its own copy of the logic: (a) hook/guard rendering & chaining, (b) hook-context injection into the model, (c) charter placement as system/built-in instructions, (d) session-start rule list (guard brief), (e) read-once guard reset signal, (f) prompt-submit and session-end lifecycle events, (g) StopFailure / API-error self-heal, (h) project-memory hooks, (i) token/cost (spend) reading, (j) usage-window (quota) reading, (k) the operator question / structured interview channel." R5 (verbatim): "Availability: every closed capability is ON by default on the dev control plane (`dev.jaah.ai`) — no new switch that ships OFF; if a switch is unavoidable it defaults ON and is wired in the real deployment." Closing-test naming contract (architecture §4): every degradation key this child closes gets a test named `test_closes_<key with - as _>` (e.g. `test_closes_no_hook_context`); C9 refuses to remove a key without it. Do NOT remove catalogue degradation keys in this child — the close-out child removes them; this child delivers the behaviour and the closing test. ## Scope (this child) Architecture child C2 — ACP host as the lifecycle runner (Antigravity ACP; generic for any row declaring `host_lifecycle`). Builds on C1's S1–S3. Mechanisms (b) spool consumer, (d), (e) ACP, (f) ACP, (h) Antigravity. - New `apps/jaahd/acp/lifecycle.py::HostLifecycle` runs the row's `host_lifecycle` events through `dispatch`; new `acp/events.py::compaction_observed` turns the `usage_update` sequence into a compaction signal. - SessionStart output, including the guard brief (`fleet_launch.py::guard_chains` → `on_session_start`), joins the system-prompt payload at `session/new` (for a row with `system_prompt_file_env`, that payload is the charter file — keep the generic join so Pi's later declaration works). - `Host._drain_hook_context`: the S2 spool drains before every `session/prompt` (leading text block `Platform guidance:`; empty spool sends nothing). - `events.compaction_observed` triggers `tools/read_once_guard.py --reset`. - Antigravity row: `host_lifecycle=("SessionStart","UserPromptSubmit","Stop","SessionEnd","PreCompact")`, `context_channel="turn_boundary"`, `project_memory_hooks=True` (owned here for both runtimes: memory rows reach agy through `harness_dispatch` — tool rows via the bridge, lifecycle rows via `HostLifecycle` on ACP and via C3a's derivations on terminal). - Codex is NOT touched: it keeps `host_lifecycle=()` (native `_EVENTS`, rendered by C6). - MEASURE the compaction signal first: force a compaction in an `agy-acp-server` 1.4.1 session on dev account `gem2` (API key, ACP) with a long context and record the `usage_update` sequence as a replay fixture (open the session through the console on `https://app.dev.jaah.ai`; capture the ACP event stream from the Audit > Sessions transcript download, redacted). If `used` does not drop on compaction, reset read-once at each turn boundary instead and record that in `apps/jaahd/README.md` §Antigravity. PRD AC6 (verbatim): "run a Task on an Antigravity account (ACP) and a Pi account; open its session transcript in Audit > Sessions; observe the charter delivered as system/built-in instructions (not as the first user message), the project's guard rule list present at session start, and a hook-context note (e.g. context-architecture check after an edit) visible in the model context." AC7 (verbatim): "in an Antigravity console session, re-read a file twice → re-read guard fires; type into an idle chat → prompt-submit event reopens it; quit at the prompt → the session completes." (Live checks run in the close-out child.) Credentials & reach: - C1–C7 unit and replay tests, AC1–AC4 | nothing beyond the worktree (`./dc check full`) | verified (worktree) - AC6/AC7/AC10 Antigravity ACP | dev account `gem2` (Antigravity API key, available, explicit-only) | verified (Accounts page row "available") - Console (open the `gem2` session, download its transcript from Audit > Sessions): AC5, AC9, AC11 browser checks | `org/console/dev-login` (on roster, `credential_get` ok; its `base_url` field is stale, so use `https://app.dev.jaah.ai`) on dev | verified (probe: GET / → 200; Accounts page read in browser) Out of scope (PRD, verbatim — do NOT touch): - MUST NOT retire, rewrite or change the runtime selection of the legacy terminal runtime; Claude `oauth` stays `legacy_only` (ADR in `docs/adrs/README.md`, ACP Claude entry). - MUST NOT remove or reword any "stay published" degradation (list above), including Pi `no-subagents` and `acp-runtime-only`. - MUST NOT build Pi subagents or a Jaah-side subagent lane. - MUST NOT add Jaah-side workarounds for vendor-intrinsic DeepSeek/Codex gaps (PDF conversion, model remap changes, throttling avoidance). - MUST NOT do live-verification work for Antigravity `not-yet-observed`, nor touch `home-is-account-dir` or `microvm-signed-in-legacy-runtime`. - MUST NOT change `tools/provider_branches.toml` patterns/allowlist scope beyond what removing branches requires (gate tightening was not selected), and set no line-count target. - MUST NOT add a new provider or retire one; no change to login/sign-in flows, model-tier resolution, or the plan gate (`non_claude_engines`). Stay-published keys (must stay byte-identical): DeepSeek `document-blocks-unsupported`, `images-flash-only`, `prompt-cache-controls-ignored`, `beta-features-ignored`, `web-search-extra-calls`, `claude-model-names-remapped`, `model-quality-differs`; Codex `device-code-login-workspace-gated`, `subscription-concurrency-throttling`; Pi `no-subagents`, `acp-runtime-only`. I1-escaped (stay, unchanged): Antigravity `no-spend-reading`; Pi and DeepSeek `no-quota-reading`. Done means: - ACP replay fixtures under `apps/jaahd/tests/acp_replay/` prove for Antigravity: charter and guard brief in the `session/new` payload (`test_closes_guard_brief_at_first_denial`). - END TO END: a PostToolUse hook in the hooks file → bridge → `dispatch --spool` → spool → the head of the next prompt (`test_closes_no_hook_context`; a fixture that only writes the spool directly does NOT count). - prompt-submit and session-end chains run exactly once each (`test_closes_no_prompt_submit_event`, `test_closes_no_session_end_event` — ACP half). - Read-once reset, conditional on the measurement: a recorded `usage_update` drop resetting it if the measurement shows one, otherwise a turn-boundary reset fixture plus the README §Antigravity note recording the measurement (`test_closes_no_read_once_guard` — ACP half). - `test_closes_project_memory_hooks` for Antigravity — ACP half (memory lifecycle rows run via `HostLifecycle`); disjointness test green. - Quick gate green; `python3 tools/check_provider_branches.py` exits 0.

Outcome

The ACP host now runs Antigravity's lifecycle (guard brief in the system prompt, hook context at the next prompt, prompt-submit/Stop/session-end, compaction-driven read-once reset) through the neutral dispatcher. Quick gate green; review lanes clean; PR #6906 queued. Compaction was measured from the 1.4.1 source, not live. Memory: proposed 1 recipe.

Pull request

Artifacts

Activity

  • Draft
  • Ready
  • Runner started
  • Runner ended (done)