444 lines
16 KiB
Python
444 lines
16 KiB
Python
"""Keep Hermes boundaries on the Atlas Switchyard routing authority."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
try:
|
|
from .provider_status import provider_status_text
|
|
except ImportError: # Direct module loading in the small unit-test harness.
|
|
from provider_status import provider_status_text
|
|
|
|
try:
|
|
from .hux_mode import selection as _hux_mode_selection
|
|
except ImportError:
|
|
try: # Direct module loading in the small unit-test harness.
|
|
from hux_mode import selection as _hux_mode_selection
|
|
except ImportError: # A ConfigMap rendered before hux_mode.py shipped.
|
|
def _hux_mode_selection(session): # noqa: ARG001 - contract shim
|
|
return None
|
|
|
|
|
|
POLICY_PATH = Path("/opt/data/workspace/coordinator/route-policy.json")
|
|
POLICY_VERSION = 2
|
|
SWITCHYARD_PROVIDER = "atlas-switchyard"
|
|
ROUTER_PROFILE = os.environ.get("HERMES_AUTO_ROUTER_PROFILE", "").strip().lower()
|
|
if ROUTER_PROFILE not in {"chat", "triage", "agent"}:
|
|
ROUTER_PROFILE = (
|
|
"chat"
|
|
if os.environ.get("HERMES_AUTO_ROUTER_CHAT_MODE", "0") == "1"
|
|
else "agent"
|
|
)
|
|
|
|
PROFILE_ROUTE = {
|
|
"chat": "atlas/auto/fast",
|
|
"triage": "atlas/auto/deep",
|
|
"agent": "atlas/auto/balanced",
|
|
}
|
|
PRIORITY_ROUTE = {
|
|
"fast": "atlas/auto/fast",
|
|
"balanced": "atlas/auto/balanced",
|
|
"deep": "atlas/auto/deep",
|
|
"maximum": "atlas/auto/maximum",
|
|
}
|
|
AUTO_ROUTES = frozenset(PRIORITY_ROUTE.values())
|
|
MANUAL_ROUTES = frozenset(
|
|
{
|
|
"atlas/manual/codex/luna",
|
|
"atlas/manual/codex/terra",
|
|
"atlas/manual/codex/sol",
|
|
"atlas/manual/codex/auto",
|
|
"atlas/manual/claude/haiku",
|
|
"atlas/manual/claude/fable",
|
|
"atlas/manual/claude/sonnet",
|
|
"atlas/manual/claude/opus",
|
|
"atlas/manual/claude/auto",
|
|
"atlas/manual/local/qwen-14b",
|
|
}
|
|
)
|
|
ALL_ROUTES = AUTO_ROUTES | MANUAL_ROUTES
|
|
EFFORTS = frozenset({"none", "minimal", "low", "medium", "high", "xhigh"})
|
|
ROUTABLE_EFFORTS = frozenset({"low", "medium", "high", "xhigh"})
|
|
EXACT_MANUAL_ROUTES = frozenset(
|
|
f"{route}/{effort}"
|
|
for route in MANUAL_ROUTES
|
|
if not route.endswith("/local/qwen-14b")
|
|
for effort in ROUTABLE_EFFORTS
|
|
)
|
|
ALL_REQUEST_ROUTES = ALL_ROUTES | EXACT_MANUAL_ROUTES
|
|
PROVIDER_DEFAULT = {
|
|
"codex": "atlas/manual/codex/auto",
|
|
"claude": "atlas/manual/claude/auto",
|
|
"local": "atlas/manual/local/qwen-14b",
|
|
}
|
|
|
|
|
|
def _load_policy() -> dict[str, Any]:
|
|
"""Load the small persistent UI policy without making it an authority."""
|
|
try:
|
|
value = json.loads(POLICY_PATH.read_text(encoding="utf-8"))
|
|
except (OSError, ValueError, TypeError):
|
|
value = {}
|
|
if not isinstance(value, dict):
|
|
value = {}
|
|
if value.get("version") != POLICY_VERSION:
|
|
value = {
|
|
"version": POLICY_VERSION,
|
|
"mode": "auto",
|
|
"auto_route": PROFILE_ROUTE[ROUTER_PROFILE],
|
|
}
|
|
if value.get("mode") not in {"auto", "manual"}:
|
|
value["mode"] = "auto"
|
|
route = str(value.get("auto_route") or "")
|
|
if route not in AUTO_ROUTES:
|
|
value["auto_route"] = PROFILE_ROUTE[ROUTER_PROFILE]
|
|
return value
|
|
|
|
|
|
def _write_policy(value: dict[str, Any]) -> None:
|
|
"""Persist route preference atomically inside the current workspace."""
|
|
value["version"] = POLICY_VERSION
|
|
POLICY_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
temporary = POLICY_PATH.with_suffix(".json.tmp")
|
|
temporary.write_text(
|
|
json.dumps(value, indent=2, sort_keys=True) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
temporary.replace(POLICY_PATH)
|
|
|
|
|
|
def _runtime_agent(ctx: Any) -> Any | None:
|
|
"""Return the foreground agent when the hook did not provide a child."""
|
|
cli = getattr(getattr(ctx, "_manager", None), "_cli_ref", None)
|
|
return getattr(cli, "agent", None) if cli is not None else None
|
|
|
|
|
|
def _normalise_manual_route(provider: str, model: str = "") -> str:
|
|
"""Map a provider/model override to one public Switchyard route."""
|
|
provider = provider.strip().lower()
|
|
model = model.strip().lower()
|
|
if provider not in PROVIDER_DEFAULT:
|
|
return ""
|
|
if not model:
|
|
return PROVIDER_DEFAULT[provider]
|
|
|
|
aliases = {
|
|
"codex": {
|
|
"luna": "atlas/manual/codex/luna",
|
|
"gpt-5.6-luna": "atlas/manual/codex/luna",
|
|
"terra": "atlas/manual/codex/terra",
|
|
"gpt-5.6-terra": "atlas/manual/codex/terra",
|
|
"sol": "atlas/manual/codex/sol",
|
|
"gpt-5.6-sol": "atlas/manual/codex/sol",
|
|
},
|
|
"claude": {
|
|
"haiku": "atlas/manual/claude/haiku",
|
|
"claude-haiku-4-5-20251001": "atlas/manual/claude/haiku",
|
|
"fable": "atlas/manual/claude/fable",
|
|
"claude-fable-5": "atlas/manual/claude/fable",
|
|
"sonnet": "atlas/manual/claude/sonnet",
|
|
"claude-sonnet-5": "atlas/manual/claude/sonnet",
|
|
"opus": "atlas/manual/claude/opus",
|
|
"claude-opus-5": "atlas/manual/claude/opus",
|
|
},
|
|
"local": {
|
|
"qwen": "atlas/manual/local/qwen-14b",
|
|
"qwen-14b": "atlas/manual/local/qwen-14b",
|
|
"qwen2.5:14b-instruct-q4_0": "atlas/manual/local/qwen-14b",
|
|
},
|
|
}
|
|
return aliases[provider].get(model, "")
|
|
|
|
|
|
def _explicit_ui_route(agent: Any) -> str:
|
|
"""Return an exact route selected by the WebUI or command-line caller."""
|
|
if not bool(getattr(agent, "_hermes_explicit_model_pick", False)):
|
|
return ""
|
|
route = str(getattr(agent, "model", "") or "").strip()
|
|
return route if route in ALL_REQUEST_ROUTES else ""
|
|
|
|
|
|
def _explicit_ui_effort(agent: Any) -> str:
|
|
"""Return an exact WebUI effort override, capped by the route catalog."""
|
|
effort = str(
|
|
getattr(agent, "_hermes_explicit_reasoning_effort", "") or ""
|
|
).strip().lower()
|
|
return effort if effort in EFFORTS else ""
|
|
|
|
|
|
def _boundary_selection(agent: Any, session_id: str = "") -> tuple[str, str, str]:
|
|
"""Select only a public route; Switchyard selects the actual target."""
|
|
policy = _load_policy()
|
|
ui_route = _explicit_ui_route(agent)
|
|
ui_effort = _explicit_ui_effort(agent)
|
|
if ui_route:
|
|
mode = "auto" if ui_route in AUTO_ROUTES else "manual"
|
|
if ui_route in EXACT_MANUAL_ROUTES:
|
|
effort = ui_route.rsplit("/", 1)[-1]
|
|
return ui_route, effort, f"ui-{mode}"
|
|
return ui_route, ui_effort, f"ui-{mode}"
|
|
|
|
# HUX-06: a friendly mode selected for this conversation shapes the next
|
|
# request. Automatic modes stay provider-neutral pools; only private may
|
|
# pin the local route. Any HUX absence or failure falls straight through.
|
|
hux = _hux_mode_selection(session_id) if session_id else None
|
|
if hux and hux[0] in ALL_REQUEST_ROUTES:
|
|
return hux[0], ui_effort or hux[1], "hux-mode"
|
|
|
|
if policy["mode"] == "manual":
|
|
manual = policy.get("manual")
|
|
if isinstance(manual, dict):
|
|
route = str(manual.get("route") or "")
|
|
effort = str(manual.get("effort") or "")
|
|
if route in MANUAL_ROUTES and effort in EFFORTS:
|
|
return route, ui_effort or effort, "manual"
|
|
|
|
priority = str(
|
|
getattr(agent, "_hermes_routing_priority", "") or ""
|
|
).strip().lower()
|
|
route = PRIORITY_ROUTE.get(priority)
|
|
if route:
|
|
return route, ui_effort, "ui-auto"
|
|
return str(policy["auto_route"]), ui_effort, "auto"
|
|
|
|
|
|
def _resolved_route(route: str, effort: str) -> str:
|
|
"""Bind a manual family and UI effort to an exact Switchyard route."""
|
|
if route in EXACT_MANUAL_ROUTES:
|
|
return route
|
|
if route not in MANUAL_ROUTES or route.endswith("/local/qwen-14b"):
|
|
return route
|
|
provider_effort = effort if effort in ROUTABLE_EFFORTS else "low"
|
|
return f"{route}/{provider_effort}"
|
|
|
|
|
|
def _switch_agent(ctx: Any, agent: Any, route: str, effort: str) -> None:
|
|
"""Point one live Hermes agent at Switchyard and remove local failover."""
|
|
runtime_agent = _runtime_agent(ctx)
|
|
cli = (
|
|
getattr(getattr(ctx, "_manager", None), "_cli_ref", None)
|
|
if agent is runtime_agent
|
|
else None
|
|
)
|
|
|
|
if agent.provider != SWITCHYARD_PROVIDER or agent.model != route:
|
|
from hermes_cli.inventory import load_picker_context
|
|
from hermes_cli.model_switch import switch_model
|
|
|
|
picker = load_picker_context()
|
|
result = switch_model(
|
|
raw_input=route,
|
|
current_provider=agent.provider or "",
|
|
current_model=agent.model or "",
|
|
current_base_url=agent.base_url or "",
|
|
current_api_key=agent.api_key or "",
|
|
is_global=False,
|
|
explicit_provider=SWITCHYARD_PROVIDER,
|
|
user_providers=picker.user_providers,
|
|
custom_providers=picker.custom_providers,
|
|
)
|
|
if not result.success:
|
|
raise RuntimeError(result.error_message or "Switchyard model switch failed")
|
|
agent.switch_model(
|
|
new_model=result.new_model,
|
|
new_provider=result.target_provider,
|
|
api_key=result.api_key,
|
|
base_url=result.base_url,
|
|
api_mode=result.api_mode,
|
|
)
|
|
if cli is not None:
|
|
cli.model = result.new_model
|
|
cli.provider = result.target_provider
|
|
cli.requested_provider = result.target_provider
|
|
cli.api_key = result.api_key or cli.api_key
|
|
cli.base_url = result.base_url or ""
|
|
cli.api_mode = result.api_mode or cli.api_mode
|
|
cli._explicit_api_key = result.api_key
|
|
cli._explicit_base_url = result.base_url
|
|
|
|
reasoning = None
|
|
if effort:
|
|
from hermes_constants import parse_reasoning_effort
|
|
|
|
reasoning = parse_reasoning_effort(effort)
|
|
agent.reasoning_config = reasoning
|
|
if cli is not None:
|
|
cli.reasoning_config = reasoning
|
|
app = getattr(cli, "_app", None)
|
|
if app is not None:
|
|
app.invalidate()
|
|
|
|
# Cross-provider recovery belongs to Switchyard. A Hermes fallback here
|
|
# would create a second control plane and could bypass a manual constraint.
|
|
agent._fallback_chain = []
|
|
agent._fallback_index = 0
|
|
agent._fallback_activated = False
|
|
agent._fallback_model = None
|
|
agent._hermes_switchyard_route = route
|
|
|
|
if agent is runtime_agent:
|
|
try:
|
|
from agent.auxiliary_client import set_runtime_main
|
|
|
|
set_runtime_main(
|
|
agent.provider or "",
|
|
agent.model or "",
|
|
base_url=agent.base_url or "",
|
|
api_key=agent.api_key or "",
|
|
api_mode=agent.api_mode or "",
|
|
)
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def _record_boundary(
|
|
policy: dict[str, Any], route: str, effort: str, source: str, scope: str
|
|
) -> None:
|
|
"""Record the public request route without pretending it is the target."""
|
|
record = {
|
|
"route": route,
|
|
"effort_override": effort or None,
|
|
"source": source,
|
|
"scope": scope,
|
|
"authority": "switchyard",
|
|
"updated_at": datetime.now(timezone.utc).isoformat(),
|
|
}
|
|
policy["last_boundary"] = record
|
|
counter = f"{scope}_boundaries_total"
|
|
policy[counter] = int(policy.get(counter) or 0) + 1
|
|
_write_policy(policy)
|
|
|
|
|
|
def _route_boundary(ctx: Any, scope: str, **kwargs: Any) -> None:
|
|
"""Route a user turn, tool continuation, or delegated child."""
|
|
agent = kwargs.get("agent") or kwargs.get("child") or _runtime_agent(ctx)
|
|
if agent is None:
|
|
return
|
|
requested_route, effort, source = _boundary_selection(
|
|
agent, str(kwargs.get("session_id") or "")
|
|
)
|
|
route = _resolved_route(requested_route, effort)
|
|
_switch_agent(ctx, agent, route, effort)
|
|
policy = _load_policy()
|
|
_record_boundary(policy, route, effort, source, scope)
|
|
|
|
if scope != "turn":
|
|
return
|
|
emit = getattr(agent, "_emit_status", None)
|
|
if callable(emit):
|
|
override = f" · effort {effort}" if effort else ""
|
|
emit(
|
|
f"{source.upper()} via Switchyard → {route}{override} · "
|
|
"target and fallback selected per boundary"
|
|
)
|
|
|
|
|
|
def _pre_turn(ctx: Any, **kwargs: Any) -> None:
|
|
"""Route a visible user turn unless Hermes is handling a slash command."""
|
|
message = str(kwargs.get("user_message") or "").strip()
|
|
if message.startswith("/"):
|
|
return
|
|
_route_boundary(ctx, "turn", **kwargs)
|
|
|
|
|
|
def _status_text(ctx: Any) -> str:
|
|
"""Describe the route contract without claiming an unseen target."""
|
|
policy = _load_policy()
|
|
agent = _runtime_agent(ctx)
|
|
runtime = "not initialized"
|
|
if agent is not None:
|
|
runtime = f"{agent.provider}/{agent.model}"
|
|
if policy["mode"] == "manual":
|
|
manual = policy.get("manual") or {}
|
|
preference = (
|
|
f"{manual.get('route', 'invalid')} at "
|
|
f"{manual.get('effort', 'default')}"
|
|
)
|
|
else:
|
|
preference = str(policy["auto_route"])
|
|
last = policy.get("last_boundary") or {}
|
|
last_text = str(last.get("route") or "none yet")
|
|
return (
|
|
"Routing authority: Switchyard\n"
|
|
f"Service posture: {ROUTER_PROFILE}\n"
|
|
f"Mode: {policy['mode'].upper()} ({preference})\n"
|
|
f"Current Hermes endpoint: {runtime}\n"
|
|
f"Last public route: {last_text}\n"
|
|
"Switchyard independently selects provider/model/effort and fallback "
|
|
"for every model-call boundary.\n"
|
|
"Commands: /route auto [fast|balanced|deep|maximum] | "
|
|
"/route manual <codex|claude|local> "
|
|
"<none|minimal|low|medium|high|xhigh> [model] | /route status"
|
|
)
|
|
|
|
|
|
def _route_command(ctx: Any, raw_args: str) -> str:
|
|
"""Persist an AUTO posture or constrained Switchyard route."""
|
|
args = raw_args.strip().split()
|
|
if not args or args[0].lower() == "status":
|
|
return _status_text(ctx)
|
|
|
|
mode = args[0].lower()
|
|
policy = _load_policy()
|
|
if mode == "auto":
|
|
posture = args[1].lower() if len(args) > 1 else ""
|
|
route = PRIORITY_ROUTE.get(posture, PROFILE_ROUTE[ROUTER_PROFILE])
|
|
if posture and posture not in PRIORITY_ROUTE:
|
|
return "AUTO posture must be fast, balanced, deep, or maximum."
|
|
policy = {"mode": "auto", "auto_route": route}
|
|
_write_policy(policy)
|
|
return "AUTO routing enabled.\n" + _status_text(ctx)
|
|
|
|
if mode != "manual" or len(args) < 3:
|
|
return (
|
|
"Usage: /route auto [fast|balanced|deep|maximum] | "
|
|
"/route manual <codex|claude|local> "
|
|
"<none|minimal|low|medium|high|xhigh> [model] | /route status"
|
|
)
|
|
provider = args[1].lower()
|
|
effort = args[2].lower()
|
|
model = args[3] if len(args) > 3 else ""
|
|
route = _normalise_manual_route(provider, model)
|
|
if not route:
|
|
return "Unknown provider/model combination for a Switchyard route."
|
|
if effort not in EFFORTS:
|
|
return "Effort must be none, minimal, low, medium, high, or xhigh."
|
|
policy = {
|
|
"mode": "manual",
|
|
"auto_route": PROFILE_ROUTE[ROUTER_PROFILE],
|
|
"manual": {"route": route, "effort": effort},
|
|
}
|
|
_write_policy(policy)
|
|
return "Manual Switchyard constraint enabled.\n" + _status_text(ctx)
|
|
|
|
|
|
def register(ctx: Any) -> None:
|
|
"""Register thin boundary hooks; Switchyard owns every routing decision."""
|
|
ctx.register_hook("pre_turn_route", lambda **kwargs: _pre_turn(ctx, **kwargs))
|
|
ctx.register_hook(
|
|
"pre_internal_route",
|
|
lambda **kwargs: _route_boundary(ctx, "internal", **kwargs),
|
|
)
|
|
try:
|
|
ctx.register_hook(
|
|
"pre_subagent_route",
|
|
lambda **kwargs: _route_boundary(ctx, "subagent", **kwargs),
|
|
)
|
|
except ValueError:
|
|
pass
|
|
ctx.register_command(
|
|
"route",
|
|
lambda raw_args: _route_command(ctx, raw_args),
|
|
description="Show or constrain Switchyard AUTO routing",
|
|
args_hint="auto [posture]|status|manual provider effort [model]",
|
|
)
|
|
ctx.register_command(
|
|
"providers",
|
|
lambda _raw_args: provider_status_text(),
|
|
description="Show Codex, Claude, local, and Switchyard status",
|
|
)
|