Per-provider ACP runtime switch
Child of epic 7191 "Converge every runner on one agent contract: ACP-native". Settled inputs: PRD (task 7189), architecture (task 7190). Architecture boundary C7.
## Original requirement (PRD, verbatim)
**R14 — Per-provider runtime switch, default ON (D4).** Each provider has a switch selecting the new runtime vs the legacy path. Each switch ships **ON by default** in the real deployment (dev and prod), visible and changeable by the operator in the console. The legacy path stays intact behind OFF until the operator deletes it in a separate task (D5).
## Approach (architecture, verbatim)
**C7: Runtime switch.** `acp_runtime` operator setting (D-SW-1), resolution at dispatch, `runtime` dispatch fact. The node launch path picks `jaah-acp-host` or the legacy path (`fleet_dispatch.launch_runner`, `fleet_launch.build_launch_sh`, `microvm_session.model_launch_files`). *Seam:* the host CLI `jaah-acp-host --launch <launch.json>` with `launch.json` exactly as pinned in §3.4b. It is buildable against a stub host.
**D-SW-1.** The runtime switch is an operator setting in `operator_settings` (`system_state` row, `apps/api/app/operator_settings.py:34`). The key is `acp_runtime` → `{"<provider>:<auth_kind>": true|false}`, default true for every pair except `claude:oauth` (absent = legacy_only). It is rendered on the console Settings → Providers page (the web child). The CP resolves it at dispatch and sends `runtime: "acp" | "legacy"` in the dispatch facts, and the node obeys. Defaults are in code, never a deploy variable. **The runtime is fixed per attempt.** OAuth-first spillover (R10) that moves a Claude task from an OAuth account (legacy) to an API-key account (ACP) ends the attempt and dispatches a new attempt on the target account's runtime, exactly as spillover re-dispatches today. A native session is never carried across runtimes. A provider/auth pair whose catalogue `runtime` is `legacy_only`, or whose adapter has no `acp_launch`, resolves to `legacy` regardless of the switch.
**§3.4b launch.json** = `{task_id, session_id, provider, auth_kind, worktree, additional_dirs, charter_path, prompt_path, runtime, argv, env, session_meta, mcp_servers}`, the last four copied from the adapter's `AcpLaunch` under the same names. `AcpLaunch.prepare` is a callable and never goes into `launch.json`; it runs before spawn on both node kinds where `adapter.prepare` runs today: `fleet_dispatch.launch_runner` (`fleet_dispatch.py:1247`) for static nodes and `microvm_session.model_launch_files` (`apps/jaahd/microvm_session.py:939`) for MicroVM. The host is the tmux pane root (static) or started by the guest's `launch.sh` (MicroVM); `runner_registry.record(task_id, pane_pid, …)` is unchanged. Build against a stub `jaah-acp-host` (the real one is built by the host child; if it has landed, use it).
Planner note: this child does not write `sessions.runtime` (the CP-ingest child owns that column and sets it from the first ingested event); it only carries `runtime` in the dispatch facts and launch.json.
Credentials & reach: AC-1–AC-4, AC-13–AC-18 | `jaah/debug/main/operator-login` (operator on dev), dev console `app.dev.jaah.ai` | verified. AC-2 prod defaults | same login (prod `app.jaah.ai`) | verified (read-only). Gates | worktree only.
Out of scope: Do NOT touch the Settings UI (web child) or per-provider grammar (provider children). PRD §5 verbatim:
- MUST NOT delete the legacy paths (v1 SDK `apps/jaahd/vendor/agent-sdk`, `tools/jaah_agent_run.py` v1 mode, per-vendor transcript parsers in `apps/jaahd/agents/*.py`, Claude-shaped hook scripts, `clauded_wt` TUI driving). MUST NOT file that task (D5).
- MUST NOT publish (D2). MUST NOT change runner permission posture (D6). MUST NOT drop or regress any current Claude Code capability (R10). MUST NOT remove the console terminal/TUI view (D3). MUST NOT submit an upstream ACP RFD (D8). MUST NOT integrate with T3 Code's RPC or MCP API.
- MUST NOT change the task lifecycle states or the merge lane. MUST NOT change billing. MUST NOT add vendors. MUST NOT touch `apps/mobile`.
- MUST NOT put a token in the agent's environment or weaken process-ancestry identity (ADR-0083); MUST NOT add a second verb table.
Done means:
- AC-2 (R14) Switch OFF still works. → verify: on dev, flip one provider's switch OFF in the console, dispatch a coder task, observe it completes on the legacy path with today's behaviour; flip back ON. Defaults: a fresh dev and prod deploy shows every switch ON (check the console provider settings page). (This child: tests prove resolution — unset → `acp`, OFF → `legacy`, `claude:oauth` → `legacy`, no `acp_launch` → `legacy` — and that legacy launch output is byte-identical to today's for every provider.)
- AC-13 (R14, D5) Legacy dormant, not deleted (switch half). → verify: with the switch ON, the static and MicroVM launch paths start `jaah-acp-host --launch <launch.json>` (stub-host tests on both node kinds) and no per-vendor transcript parser or TUI driver is invoked; legacy code still exists.
- Spillover test: an OAuth→API-key spillover ends the attempt and re-dispatches on the target runtime; no native session crosses runtimes.
Outcome
Per-provider ACP runtime switch (acp_runtime setting, all ON; claude:oauth legacy-only) resolved at dispatch; node runs jaah-acp-host --launch launch.json or byte-identical legacy. PR #5915 merged (822eb455); quick gate + review lane green; deploy superseded. Live dev oracle deferred to 7281.
Artifacts
Activity
- Draft
- Ready
- Runner started
- Runner ended (done)