Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## [Unreleased]

### Documentation

- **PR #TBD** by @Michaelyklam (refs #1925) — Advance the runtime-adapter RFC to the Slice 4 runner/sidecar planning gate after #2560 shipped the queue-staging clarification. The RFC now marks queue routing as staged by default, defines Slice 4a as a docs/test contract before any runner code lands, and pins default-off feature-flagging, restart/reattach success criteria, control parity, profile/workspace payload isolation, and explicit non-goals for legacy-backend removal or server-side queue scheduler work.

## [v0.51.92] — 2026-05-19 — Release BP (stage-385 — 7-PR full sweep batch — RFC Slice 3c clarification + workspace tree icon alignment + project move cache refresh + auto-compression handoff metadata + Grok OAuth provider catalog + anonymous custom endpoint picker fallback + PWA standalone reload + pull-to-refresh)

Expand Down
85 changes: 74 additions & 11 deletions docs/rfcs/hermes-run-adapter-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ The immediate goal is not to build a sidecar. The immediate goal is to define th
browser contract, classify current runtime state, and gate the first reversible
journal slice.

## Current Gate State — 2026-05-18
## Current Gate State — 2026-05-19

Slice 1 is now past the first active validation gate:

Expand Down Expand Up @@ -90,14 +90,17 @@ adapter-seam work:
`HERMES_WEBUI_RUNTIME_ADAPTER=legacy-journal` is enabled, while preserving the
legacy-direct response shape and leaving post-turn goal evaluation in the
existing agent loop.

The next gate is still not the runner/sidecar by default. Slice 3c's goal route
is shipped, and `queue_message(...)` remains a staged protocol method. Queue /
continue routing needs an explicit follow-up contract because the legacy `/queue`
path is browser-side queue/drain behavior today; no new server-side queue endpoint
or queue scheduler should be added just for adapter symmetry. If maintainers want
queue/continue to move before Slice 4, that follow-up should specify the exact
legacy entry point, response shape, and ordering/idempotency contract first.
- #2560 shipped the queue-staging clarification in v0.51.92. The RFC now treats
`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.

The next gate is the runner/sidecar planning contract, not queue implementation
by default. Queue / continue routing should only move before Slice 4 if a future
maintainer decision identifies an existing server-side legacy entry point and
pins its response shape, ordering, and idempotency contract. Otherwise, keeping
`queue_message(...)` staged is the honest boundary while execution ownership
moves out of the main WebUI request process.

## Goals

Expand Down Expand Up @@ -670,8 +673,19 @@ Non-goals for Slice 3c:

### Slice 4: Runner process / sidecar boundary

Explicitly deferred until Slice 1 has worked in production for at least one
release cycle and the adapter surface has review approval.
Slice 4 is the first gate that may move active execution ownership out of the
main WebUI request process. It should start as a docs/test contract PR before any
runner code lands. Slice 1's journal/replay layer has shipped and passed active
validation, Slice 2's default-off adapter seam has shipped, and Slice 3's
cancel/approval/clarify/goal control routing has proven the protocol-translator
pattern. Queue remains staged unless maintainers explicitly ask for a separate
pre-runner queue route.

The Slice 4 implementation must not make the adapter a new runtime surrogate.
The runner boundary may own active execution, process supervision, run lifecycle,
and callback state, but those responsibilities must be centralized behind the
adapter/runner contract rather than recreated as scattered globals in the main
WebUI server.

Scope:

Expand All @@ -683,6 +697,55 @@ Scope:

Revert path: disable runner backend and fall back to journaled legacy backend.

#### Slice 4a: Runner contract gate

Before runner code lands, define a narrow contract that covers:

1. **Backend selection and rollback.** The existing `legacy-direct` and
`legacy-journal` paths remain available. Any new runner backend is
feature-flagged, default-off, and revertible by switching the adapter mode back
to `legacy-journal` without deleting sessions or journal files.
2. **Process ownership.** The runner, not the main WebUI request process, owns
`AIAgent` construction/reuse, active run execution, cancellation flags,
approval/clarify callback wait state, and post-turn continuation evaluation
for runs assigned to that backend.
3. **Durable observation.** The main WebUI server observes through
`RuntimeAdapter.observe_run(...)`, `get_run(...)`, and the journal cursor. A
WebUI restart must not be required for the runner to finish writing ordered
events and terminal state.
4. **Restart/reattach success criterion.** Start a long-running run, restart only
`hermes-webui.service`, reload the session, rediscover the active or terminal
runner-owned run, replay/catch up from cursor without duplicate transcript /
tool / reasoning state, and preserve cancel if the run is still active.
5. **Control parity.** Cancel, approval, clarify, goal status/control, and any
accepted queue/continue behavior route through adapter methods with stable
browser response shapes. Unsupported controls return bounded `ControlResult`
states instead of silently falling back to stale in-process state.
6. **Profile/workspace isolation.** Runner startup receives explicit profile,
workspace, attachments, model/provider, toolset, and source metadata rather
than relying on process-global environment mutation in the WebUI server.

Suggested contract tests before implementation:

- source/RFC tests proving Slice 4 remains feature-flagged and default-off;
- a fake-runner adapter test that simulates WebUI restart by discarding server
process-local state while preserving runner/journal state, then verifies
`get_run` and replay recover the same terminal state;
- a control-parity fixture proving unsupported runner controls return bounded
`ControlResult` values and do not fall back to legacy `STREAMS` /
`CANCEL_FLAGS` state;
- a profile/workspace payload test proving runner requests carry explicit context
fields without mutating global `os.environ` in the main WebUI process.

Non-goals for Slice 4a:

- no removal of the legacy in-process backend;
- no default-on runner mode;
- no public chat-start/status response-shape expansion;
- no new server-side queue endpoint or scheduler just for adapter symmetry;
- no dependency on Hermes Agent shipping `/v1/runs` before WebUI can validate the
local runner boundary.

## First Meaningful Success Criteria

The first meaningful milestones are deliberately split.
Expand Down
19 changes: 17 additions & 2 deletions tests/test_runtime_adapter_seam.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,22 @@ def test_rfc_distinguishes_goal_routing_from_queue_route_staging():
rfc = (routes.Path(__file__).parent.parent / "docs" / "rfcs" / "hermes-run-adapter-contract.md").read_text(encoding="utf-8")

assert "#2544 shipped the first Slice 3c implementation" in rfc
assert "#2560 shipped the queue-staging clarification" in rfc
assert "route now uses `RuntimeAdapter.update_goal(...)`" in rfc
assert "`queue_message(...)` remains a staged protocol method" in rfc
assert "`queue_message(...)` as a staged protocol method only" in rfc
assert "no new server-side queue endpoint" in rfc
assert "or queue scheduler should be added just for adapter symmetry" in rfc
assert "no server-side queue endpoint or queue\n scheduler should be added merely for adapter symmetry" in rfc


def test_rfc_defines_slice4_runner_contract_before_runner_code():
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 4a: Runner contract gate" in rfc
assert "docs/test contract PR before any\nrunner code lands" in rfc
assert "feature-flagged, default-off" in rfc
assert "The runner, not the main WebUI request process, owns" in rfc
assert "restart only\n `hermes-webui.service`" in rfc
assert "profile,\n workspace, attachments, model/provider, toolset, and source metadata" in rfc
assert "no removal of the legacy in-process backend" in rfc
assert "no default-on runner mode" in rfc
Loading