From 680d0cbc9261d3121f048259f9930957c963d9d5 Mon Sep 17 00:00:00 2001 From: Michael Lam Date: Wed, 20 May 2026 04:04:52 -0700 Subject: [PATCH] docs(runtime): define runner backend harness gate --- CHANGELOG.md | 3 ++ docs/rfcs/hermes-run-adapter-contract.md | 69 ++++++++++++++++++++++-- tests/test_runtime_adapter_seam.py | 15 ++++++ 3 files changed, 82 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 185f15f1..91f6fa9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unreleased] +### Documentation + +- **PR #2627** by @Michaelyklam (refs #1925) — Advance the RuntimeAdapter RFC after the Slice 4b `RunnerRuntimeAdapter` facade shipped in v0.51.94. The RFC now defines the next Slice 4c runner-backend harness gate: feature-flagged runner backend selection, explicit start payload validation, durable status/event observation across WebUI adapter recreation, bounded controls, and a restart/reattach harness before any live chat route is switched to a runner backend. ## [v0.51.95] — 2026-05-20 — Release BS (stage-388 — 5-PR batch — live tool callback event dedup + browser-only dashboard links + messaging transcript merge alignment + Geist Contrast skin + SSE runtime diagnostics) diff --git a/docs/rfcs/hermes-run-adapter-contract.md b/docs/rfcs/hermes-run-adapter-contract.md index 50ddb097..1796ba7e 100644 --- a/docs/rfcs/hermes-run-adapter-contract.md +++ b/docs/rfcs/hermes-run-adapter-contract.md @@ -94,11 +94,16 @@ adapter-seam work: `queue_message(...)` as a staged protocol method only; `/queue` remains browser-side queue/drain behavior, and no server-side queue endpoint or queue scheduler should be added merely for adapter symmetry. -- #2575 shipped the Slice 4a runner/sidecar contract gate in v0.51.93. The next - implementation step can add runner-backend adapter plumbing, but it must stay - default-off, keep legacy fallback intact, pass explicit profile/workspace/model - payloads instead of mutating WebUI process globals, and avoid recreating - `STREAMS` / `CANCEL_FLAGS` / approval queues / clarify queues under new names. +- #2575 shipped the Slice 4a runner/sidecar contract gate in v0.51.93. +- #2599 shipped the Slice 4b `RunnerRuntimeAdapter` facade in v0.51.94. The + facade normalizes an injected runner client's start / observe / status / + control responses without owning `AIAgent`, streams, cancellation flags, + approval queues, clarify queues, goal state, or cached-agent tables. +- The next implementation gate is a feature-flagged runner backend plus + restart/reattach harness. It must stay default-off, keep legacy fallback + intact, pass explicit profile/workspace/model payloads instead of mutating + WebUI process globals, and avoid recreating `STREAMS` / `CANCEL_FLAGS` / + approval queues / clarify queues under new names. The next gate is runner-backend plumbing, not queue implementation by default. Queue / continue routing should only move before Slice 4 if a future @@ -753,6 +758,8 @@ Non-goals for Slice 4a: #### Slice 4b: Runner adapter client facade +Status as of 2026-05-20: shipped in v0.51.94 via #2599. + The first code slice after the Slice 4a contract should be a small `RunnerRuntimeAdapter` facade that delegates to an injected runner client. This is still not the runner process itself. Its job is to pin the adapter-facing @@ -771,6 +778,58 @@ normalization rules before route wiring or process supervision lands: The implementation remains default-off until a later slice adds an actual runner client/backend and explicit route selection. +#### Slice 4c: Feature-flagged runner backend and restart/reattach harness + +After the facade exists, the next narrow implementation slice should add a real +runner-client/backend selection point and a synthetic restart/reattach harness, +without routing normal browser chat to that backend yet. + +Scope: + +- add a concrete runner-client factory behind an explicit mode such as + `HERMES_WEBUI_RUNTIME_ADAPTER=runner-local`, while preserving `legacy-direct` + and `legacy-journal` as the default/revert paths; +- validate that `StartRunRequest` carries explicit session, profile, workspace, + attachments, provider/model, toolset, source, and metadata fields into the + runner boundary without relying on WebUI process-global environment mutation; +- prove a recreated WebUI adapter can rediscover runner-owned status and replay + ordered events from the runner/journal surface after process-local state is + discarded; +- keep controls bounded through `ControlResult` values, with unsupported controls + returning `unsupported` / `not-active` rather than falling back to stale legacy + `STREAMS` or callback queues; +- keep the live `/api/chat/start` path on the legacy backend until the runner + backend has a passing restart/reattach harness and maintainer approval to wire + route selection. + +Acceptance tests for Slice 4c: + +1. **Default-off selection.** `legacy-direct` remains the default; `runner-local` + or any later runner mode is selected only by an explicit feature flag. +2. **No route-shape drift.** Adding the runner backend does not expand public + `/api/chat/start`, cancel, approval, clarify, goal, or status response shapes + while the route remains legacy-backed. +3. **Restart/reattach harness.** A fake or local runner fixture can start a run, + discard the first WebUI adapter instance, recreate the adapter, and still + observe ordered events plus terminal/live status from durable runner-owned + state. +4. **Control bounds.** Cancel / approval / clarify / queue / goal controls route + through the runner client only when the runner backend is selected, and + unsupported controls return bounded `ControlResult` values without consulting + legacy process-local state. +5. **No runtime-surrogate globals.** The main WebUI server must not gain new + module-level maps for runner-owned streams, cancellation flags, pending + approval/clarify callbacks, cached agents, or goal/queue schedulers. + +Non-goals for Slice 4c: + +- no default-on runner backend; +- no removal of the legacy in-process backend; +- no public response-shape expansion; +- no live chat route switch to the runner backend before the restart/reattach + harness is reviewed; +- no server-side queue endpoint or queue scheduler just for adapter symmetry. + ## First Meaningful Success Criteria The first meaningful milestones are deliberately split. diff --git a/tests/test_runtime_adapter_seam.py b/tests/test_runtime_adapter_seam.py index 378c232e..26bd36de 100644 --- a/tests/test_runtime_adapter_seam.py +++ b/tests/test_runtime_adapter_seam.py @@ -404,10 +404,25 @@ def test_rfc_defines_slice4_runner_contract_before_runner_code(): assert "no removal of the legacy in-process backend" in rfc assert "no default-on runner mode" in rfc assert "#### Slice 4b: Runner adapter client facade" in rfc + assert "Status as of 2026-05-20: shipped in v0.51.94 via #2599" in rfc assert "delegates to an injected runner client" in rfc assert "without relying on process-local `STREAMS`" in rfc +def test_rfc_defines_slice4c_runner_backend_harness_gate(): + routes = importlib.import_module("api.routes") + rfc = (routes.Path(__file__).parent.parent / "docs" / "rfcs" / "hermes-run-adapter-contract.md").read_text(encoding="utf-8") + + assert "#### Slice 4c: Feature-flagged runner backend and restart/reattach harness" in rfc + assert "`HERMES_WEBUI_RUNTIME_ADAPTER=runner-local`" in rfc + assert "`legacy-direct` remains the default" in rfc + assert "No route-shape drift" in rfc + assert "Restart/reattach harness" in rfc + assert "discard the first WebUI adapter instance" in rfc + assert "No runtime-surrogate globals" in rfc + assert "no live chat route switch to the runner backend before the restart/reattach" in rfc + + def test_runner_runtime_adapter_passes_explicit_start_payload_without_env_mutation(monkeypatch): runtime = importlib.import_module("api.runtime_adapter") captured = []