mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
fix: drive cold-start multiplex target from the gateway boot policy
gateway_runtime_multiplex picked the cold-start owner for a named profile with its own reading of gateway.multiplex_profiles: a retired explicit `false` was treated as an opt-out (spawning a per-profile owner the boot would refuse/ignore), and an explicit `true` selected the root even for a `gateway.standalone: true` secondary that profiles_to_serve never serves, so the client waited on a multiplexer that would not serve it. Export the boot verdict from gateway_multiplex_mode (settled_explicit_flag, used by resolve_multiplex_mode, and host_serves_named_profile) and have implied_host_root / multiplexer_serves_home call it: retired false settles like unset (fall back to the recorded served set, else the host root) and a standalone secondary always gets its own owner. Finding: R1/R2 minor "cold-start multiplex target selection vs policy". (cherry picked from commit e5144060d8a49ef77ba4de5149279c1942dbe235)
This commit is contained in:
1 parent
8924db5647
commit
a257be0ba7
3 files changed
+57
-20
No files matched your search
@@ -76,6 +76,23 @@ def explicit_multiplex_flag(default_home: Path) -> Optional[bool]:
|
||||
return bool(value)
|
||||
|
||||
|
||||
def settled_explicit_flag(value: Optional[bool]) -> Optional[bool]:
|
||||
"""How boot reads an explicit ``multiplex_profiles``: ``true`` is final; the retired ``false``
|
||||
settles exactly like an unset key (``None``: the boot preflight decides)."""
|
||||
return True if value else None
|
||||
|
||||
|
||||
def host_serves_named_profile(default_home: Path, home: Path) -> Optional[bool]:
|
||||
"""Offline twin of the boot verdict for named profile *home* under *default_home*'s gateway:
|
||||
``False`` for a ``gateway.standalone: true`` secondary (``profiles_to_serve`` never serves it),
|
||||
``True`` for an explicit ``true`` on the default, ``None`` when boot settles it (unset or the
|
||||
retired ``false``, see :func:`resolve_multiplex_mode`)."""
|
||||
from hermes_cli.profiles import profile_is_standalone
|
||||
if profile_is_standalone(home):
|
||||
return False
|
||||
return settled_explicit_flag(explicit_multiplex_flag(default_home))
|
||||
|
||||
|
||||
def default_gateway_multiplexes(default_home: Optional[Path] = None) -> bool:
|
||||
"""Does the default profile's gateway serve every profile? For CLI/dashboard processes: the LIVE
|
||||
gateway's ``served_profiles`` record when one runs (it settled the unset default itself), else
|
||||
@@ -240,7 +257,7 @@ def resolve_multiplex_mode(config) -> MultiplexDecision:
|
||||
standalone = standalone_launcher_decision(config)
|
||||
if standalone is not None:
|
||||
return standalone
|
||||
if current:
|
||||
if settled_explicit_flag(current):
|
||||
return MultiplexDecision(True, "config")
|
||||
retired_opt_out = current is False
|
||||
try:
|
||||
|
||||
@@ -5,9 +5,10 @@ When the multiplexer is down (restart, update, crash) the profile looks ``absent
|
||||
loop would spawn a per-profile gateway for it — a second owner that answers this client and then
|
||||
blocks the multiplexer's next all-or-nothing reserve (``Cannot reserve gateway profiles``). The
|
||||
client must start the multiplexer instead, so the start target is decided here from the same facts
|
||||
the multiplexer boots on: the default profile's explicit ``gateway.multiplex_profiles`` flag, or —
|
||||
when the flag is unset and settled at boot — the served set the last multiplexer recorded in the
|
||||
root's ``gateway_state.json`` (a standalone default gateway clears it).
|
||||
the multiplexer boots on (``gateway_multiplex_mode.host_serves_named_profile``): a profile's own
|
||||
``gateway.standalone: true``, the default profile's explicit ``true``, or — when the flag is unset or
|
||||
the retired ``false`` that boot settles like unset — the served set the last multiplexer recorded in
|
||||
the root's ``gateway_state.json`` (a standalone default gateway clears it).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -27,36 +28,32 @@ def implied_host_root(home: Path) -> Path | None:
|
||||
"""The root whose gateway must serve named profile *home* when nothing records a verdict.
|
||||
|
||||
``hermes gateway run`` refuses a ``profiles/<name>`` home a gateway of its own unless it authored
|
||||
``gateway.standalone: true`` (or passes ``--force``); only an explicit multiplex ``false`` on the
|
||||
root leaves the profile to its own daemon.
|
||||
``gateway.standalone: true`` (or passes ``--force``); the retired ``multiplex_profiles: false``
|
||||
no longer opts out, so only a standalone profile keeps its own daemon.
|
||||
"""
|
||||
root = multiplexer_root_for(home)
|
||||
if root is None:
|
||||
return None
|
||||
from hermes_cli.gateway_multiplex_mode import explicit_multiplex_flag
|
||||
from hermes_cli.profiles import profile_is_standalone
|
||||
if explicit_multiplex_flag(root) is False or profile_is_standalone(home):
|
||||
return None
|
||||
return root
|
||||
from hermes_cli.gateway_multiplex_mode import host_serves_named_profile
|
||||
return None if host_serves_named_profile(root, home) is False else root
|
||||
|
||||
|
||||
def multiplexer_serves_home(home: Path) -> Path | None:
|
||||
"""The root whose (possibly stopped) multiplexer serves *home*, else None.
|
||||
|
||||
``hermes_cli.gateway.named_profile_served_by_running_multiplexer`` answers the live question;
|
||||
this is its offline twin for the moment no gateway runs: an explicit ``true`` on the default
|
||||
profile, or a recorded ``served_profiles`` naming this profile (the boot-time verdict of an unset
|
||||
flag). An explicit ``false``, or no evidence, keeps the per-profile daemon.
|
||||
this is its offline twin for the moment no gateway runs, deciding from the boot policy
|
||||
(:func:`hermes_cli.gateway_multiplex_mode.host_serves_named_profile`): a standalone secondary is
|
||||
never served, an explicit ``true`` serves the rest, and an unset or retired ``false`` flag falls
|
||||
back to the ``served_profiles`` the last multiplexer recorded. No evidence keeps it unanswered.
|
||||
"""
|
||||
root = multiplexer_root_for(home)
|
||||
if root is None:
|
||||
return None
|
||||
from hermes_cli.gateway_multiplex_mode import explicit_multiplex_flag
|
||||
flag = explicit_multiplex_flag(root)
|
||||
if flag is True:
|
||||
return root
|
||||
if flag is False:
|
||||
return None
|
||||
from hermes_cli.gateway_multiplex_mode import host_serves_named_profile
|
||||
verdict = host_serves_named_profile(root, home)
|
||||
if verdict is not None:
|
||||
return root if verdict else None
|
||||
from gateway.status import read_runtime_status
|
||||
from hermes_cli.profiles import normalize_profile_name
|
||||
served = (read_runtime_status(root / "gateway_state.json") or {}).get("served_profiles")
|
||||
|
||||
@@ -332,3 +332,26 @@ def test_named_profile_without_multiplex_evidence_starts_the_host_gateway(tmp_pa
|
||||
|
||||
assert spawned == [(home if standalone else root).resolve()]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("flag,standalone,owner", [("false", False, "root"), ("true", True, "home")])
|
||||
def test_cold_start_target_follows_boot_multiplex_policy(tmp_path, monkeypatch, flag, standalone, owner):
|
||||
"""Boot settles a retired `multiplex_profiles: false` like unset (the host still serves the
|
||||
profile) and never serves a `standalone: true` secondary even under an explicit `true`."""
|
||||
from hermes_cli import gateway_runtime as runtime, gateway_runtime_service as service, gateway_runtime_start as start
|
||||
|
||||
monkeypatch.delenv("GATEWAY_MULTIPLEX_PROFILES", raising=False)
|
||||
root = tmp_path / ".hermes"
|
||||
home = root / "profiles" / "alpha"
|
||||
home.mkdir(parents=True, mode=0o700)
|
||||
(root / "config.yaml").write_text(f"gateway:\n multiplex_profiles: {flag}\n", encoding="utf-8")
|
||||
if standalone:
|
||||
(home / "config.yaml").write_text("gateway:\n standalone: true\n", encoding="utf-8")
|
||||
spawned = []
|
||||
monkeypatch.setattr(runtime, "discover_gateway_endpoint", lambda *a, **k: runtime.GatewayDiscovery("absent"))
|
||||
monkeypatch.setattr(service, "discover_existing_gateway_service", lambda *a, **k: None)
|
||||
monkeypatch.setattr(start, "spawn_unmanaged_gateway", lambda target, **k: spawned.append(Path(target)))
|
||||
|
||||
runtime.ensure_gateway_runtime(home, timeout=0.3)
|
||||
|
||||
assert spawned == [{"root": root, "home": home}[owner].resolve()]
|
||||
|
||||
Reference in new issue
Block a user