Skip to content

Phase 10.2B: planned-stop runtime correction (stdlib-only marker protocol + H3 standalone stop) - #2

Closed
yshen92 wants to merge 4 commits into
v2/runtime-v0.20-basefrom
phase10.2b/planned-stop-protocol
Closed

Phase 10.2B: planned-stop runtime correction (stdlib-only marker protocol + H3 standalone stop)#2
yshen92 wants to merge 4 commits into
v2/runtime-v0.20-basefrom
phase10.2b/planned-stop-protocol

Conversation

@yshen92

@yshen92 yshen92 commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Phase 10.2B planned-stop runtime correction

This PR provides the minimal Phase 10.2B correction needed for a standalone H3 stop to cleanly stop the immutable deployed R0 gateway before the candidate checkout.

It keeps the ordinary planned-stop wire format intact and reuses R0's existing takeover marker as the standalone stop intent channel. That marker is intentionally invisible to R0's planned-stop filesystem watcher, so the actual service-manager SIGTERM is the first shutdown-handler invocation and R0 consumes the intent as planned. The candidate runtime also retains a compact monotonic planned-shutdown classification so a planned shutdown cannot be downgraded by a duplicate later signal after upgrade.

Runtime identities

  • Phase base runtime identity: fe085446b864cb7d31f57499e407ce0f7275c511
  • Immutable deployed R0 consumer: 01edcadbd194f81bd7eceb9ca267737830ce24c0
  • Current candidate / PR head: e6982c8b7e25367c9cfb15d8b95da185feb7fc23
  • Commit count on the Phase base: 4
    • 2bc81537c1 — extract the planned-stop protocol into a stdlib-only module
    • 287f132ec8 — suppress the Windows-footgun lint on the Linux-gated liveness probe
    • bd3c22d29e — preserve planned classification across duplicate shutdown notifications in the candidate runtime
    • e6982c8b7e — make the standalone stop cleanly stop immutable exact R0 without the watcher race
  • Standalone helper: gateway/planned_stop_protocol.py
  • Helper blob OID: 246e57e3e9a341b7343fc0b4067efba89b18ac5e
  • Helper file SHA-256: 433022971acac19388eff86a07c6ca2037e772b82be121563c41f5ccbaa1c73f

Exact failure and correction

Live H3 qualification was run against the earlier candidate 287f132ec8410bff19a8cfd553ab1b0bf7f90dd1. Step E wrote a valid ordinary planned-stop marker and systemctl --user stop hermes-gateway.service returned success, but immutable R0's watcher invoked shutdown_signal_handler(None) first and consumed that marker. About 6.9 ms later, systemd's real SIGTERM invoked the handler again with no marker remaining, which latched an unexpected shutdown and produced Result=exit-code, ExecMainStatus=1. The transaction stopped at Step E; the candidate was not checked out or deployed.

The standalone helper now writes R0's already-supported .gateway-takeover.json record before issuing the same single systemctl --user stop. R0's watcher does not observe that filename. The subsequent real SIGTERM consumes the takeover record and follows R0's clean planned/takeover exit path. No sleep, retry, service-policy change, or H3 expectation workaround is involved.

Protocol and safety guarantees

  • The ordinary four-field planned-stop marker remains target_pid, target_start_time, stopper_pid, written_at; PID/start-time and TTL checks are unchanged.
  • The takeover record uses R0's existing schema and validation. Its shared builder was moved into the stdlib-only protocol module, with gateway/status.py delegating to it to avoid parallel implementations.
  • The helper remains stdlib-only and suitable for <absolute-python> -I -S -B - over stdin.
  • Linux host/user/service identity, manager-reported HERMES_HOME, /proc start-time, path-containment, and stale-marker protections remain fail-closed.
  • There is no filesystem mutation before the intent write, followed by exactly one /usr/bin/systemctl --user stop hermes-gateway.service invocation.
  • Windows candidate-runtime watcher behavior and ordinary planned-stop behavior remain covered and unchanged.

Qualification and review history

  • The failed live H3 run above exposed the immutable-R0 watcher/SIGTERM race in the prior candidate.
  • New deterministic tests execute the exact historical R0 gateway/run.py blob (24d501b5b752fe37d201843990847d1bbd306a8d) and gateway/status.py blob (ce02648a958f9a281303dd825ad45b2fdc8eb046). They first reproduce the old watcher-first failure, then drive the current standalone helper through the exact R0 shutdown handler and assert the clean manager-equivalent result: ActiveState=inactive, Result=success, ExecMainCode=1, ExecMainStatus=0.
  • Candidate-runtime regression coverage still proves handler(None) -> handler(SIGTERM) remains planned, while a standalone unmarked SIGTERM remains unexpected/non-zero.
  • Relevant planned-stop, standalone, exact-R0 compatibility, shutdown/signal, restart, takeover/replace, status, and Windows suites pass locally: 185 passed, 0 failed. One Linux /proc parity test is skipped on macOS; its exact-R0 compatibility siblings execute against the pinned historical blobs.
  • ruff, py_compile, and git diff --check pass.
  • A Luna adversarial review found no remaining lifecycle, immutable-R0, protocol, takeover, or scope defect in the corrected diff.
  • CI for the current head is running; completed checks are green at the time of this update.

No hermes-config, H3 runner, Phase 10.2B binding, or deployment files are changed by this PR. A fresh adversarial review, authority rebind, readiness/authorization cycle, and live H3 rerun are still required.

Do not merge. Keep this PR open as input to the next Phase 10.2B binding.

…module

The H3 deployment transaction needs to stop a running gateway from the exact
bytes of a reviewed Git blob, executed via "python -I -S -B -" on stdin,
without importing the broad Hermes package graph (hermes_cli.main bootstrap,
hermes_cli.config provider discovery, user plugins, dotenv).

gateway/planned_stop_protocol.py is now the single definition of the
planned-stop marker protocol (filename, 60s TTL, record fields, staleness,
PID/start-time matching, /proc start-time observation, atomic wire-compatible
write). gateway/status.py and hermes_cli/update_cmd.py delegate to it via
thin wrappers; marker bytes and consumer semantics are unchanged, so a marker
written by the new helper is consumed by the currently deployed R0 gateway
(01edcad; its gateway/status.py blob ce02648 is byte-identical at this
commit's base).

The standalone half is hard-bound to the production shape: Linux, the hermes
account (uid resolved from passwd and required to match euid), verified
HOME/HERMES_HOME/XDG_RUNTIME_DIR/DBUS_SESSION_BUS_ADDRESS, the user-scope
hermes-gateway.service at its expected fragment path, /usr/bin/systemctl
--user only (never system scope), MainPID + Environment-reported HERMES_HOME
from "systemctl --user show" as the sole authority, no filesystem mutation
before the marker write, then exactly one "systemctl --user stop" with a 90s
timeout. Everything else fails closed.

Tests cover R0 compatibility by executing the exact historical status.py
blob, stdlib-only stdin execution under -I/-P -S -B with fake managers,
import/bootstrap closure, adversarial plugin and sitecustomize/.pth fixtures,
scope/PID/start-time rejection matrices, pre-marker mutation freedom, and
stop-failure reporting. No test can reach a real service manager.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@yshen92
yshen92 marked this pull request as ready for review August 14, 2026 13:29
yshen92 and others added 3 commits August 14, 2026 21:43
…robe

The standalone planned-stop path fails closed at the platform stage
unless the host is Linux, so the signal-0 probe can never execute on
Windows; psutil is unavailable by design in this stdlib-only module.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@yshen92

yshen92 commented Aug 17, 2026

Copy link
Copy Markdown
Owner Author

PR #2: APPROVED for Phase 10.2B planned-stop runtime-correction scope.

yshen92 pushed a commit that referenced this pull request Aug 18, 2026
Two independent bugs let a deleted profile reappear / leave orphaned
resources on next launch:

1. hermes_cli/profiles.py's backend-process scanner required argv[0] to
   resolve to an executable literally named "hermes". Electron's
   pool-backend spawn resolves the hermes console-script shim's path and
   execs it via the interpreter directly (python3 /path/to/hermes ...), so
   argv[0] reports as "python3" and the scanner never matched the running
   backend -- delete removed the profile's files but left its live backend
   process running (still bound to a port via uvicorn), which
   accumulates across repeated delete/recreate cycles.
2. The desktop sidebar's ProfileRail only refreshed its cached profile
   list once, on mount, so a delete/create/rename from another surface
   (another window, or the CLI) left a stale ghost entry until something
   unrelated triggered a refetch. Note: a delete via this window's own
   Manage-Profiles view already refreshes the shared $profiles atom
   ProfileRail subscribes to (confirmed by reading refreshProfiles() and
   handleConfirmDelete()) -- this fix only covers the cross-window/cross-
   process staleness gap, not a duplicate of the already-merged
   NousResearch#57329's Manage-Profiles rail-refresh work.

Fix 1: recognize a python-interpreter argv[0] exec'ing a hermes-named
console-script shim via argv[1]. Fix 2: refresh the profile list on window
focus/visibilitychange, matching the existing pattern used elsewhere in
the sidebar (sidebar/index.tsx, use-background-sync.ts, star-map.tsx,
use-gateway-boot.ts all use the same focus+visibilitychange pattern).

## Related work already on main

PR NousResearch#57329 (merged) fixed the *headline* symptom from issue NousResearch#52279
(deleted profile respawns) via a different, non-overlapping mechanism:
routing profile-delete through the primary backend instead of spawning a
fresh pool backend, plus a separate recreation guard in
ensure_hermes_home() (NousResearch#49435, merged) that makes a backend spawned into a
deleted profile's directory raise FileNotFoundError instead of silently
recreating it.

This PR is NOT a duplicate of that fix. Verified: even with both of those
merged, a backend process that survives because of gap #1 above still
holds a bound port via uvicorn -- it just can no longer resurrect the
profile directory. That's real resource-hygiene, not a symptom already
covered. Gap #2 touches a different file/component (ProfileRail /
profile-switcher.tsx) than NousResearch#57329's rail-refresh half (which touched the
Manage-Profiles view's own $profiles.ts / index.tsx) and covers a
distinct staleness path (cross-window/cross-process, not same-window
delete-then-refresh).

Tests: tests/hermes_cli/test_profiles.py -- 156 passed (existing +
regression coverage for the argv[0] python-interpreter detection case).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yshen92

yshen92 commented Aug 21, 2026

Copy link
Copy Markdown
Owner Author

Closing without merge following the 2026-08-21 Dev-Review v2 strategic reassessment. Restricted-worker/H3 hardening is now Deferred, Evidence-Triggered and is no longer a prerequisite for framework activation. This PR and its exact reviewed commit are retained as historical/future hardening evidence; the branch should not be deleted. If the capability is needed again, reassess it against the then-current Hermes runtime/upstream rather than automatically resuming this deployment path.

@yshen92 yshen92 closed this Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant