#!/usr/bin/env python3 """Validate the generated Hermes router plugin against Hermes' tool contract.""" from __future__ import annotations import importlib.util import json import tempfile from pathlib import Path from typing import Any REPO_ROOT = Path(__file__).resolve().parents[1] BUILDER_PATH = REPO_ROOT / "scripts" / "build-hermes-plugin.py" def load_module(name: str, path: Path): spec = importlib.util.spec_from_file_location(name, path) if spec is None or spec.loader is None: raise RuntimeError(f"could not load {path}") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module class RecordingContext: def __init__(self) -> None: self.tools: dict[str, dict[str, Any]] = {} def register_tool(self, **kwargs: Any) -> None: self.tools[kwargs["name"]] = kwargs def main() -> int: builder = load_module("agency_agents_hermes_builder", BUILDER_PATH) with tempfile.TemporaryDirectory() as tmp: source = Path(tmp) / "engineering" / "engineering-multiline.md" source.parent.mkdir() source.write_text( "---\n" "name: Multiline Specialist\n" "description: First part of the description\n" " followed by essential context.\n" "vibe: First part of the vibe\n" " followed by the rest.\n" "---\n\n# Multiline Specialist\n", encoding="utf-8", ) parsed = builder.parse_agent(source, Path(tmp)) assert parsed is not None assert parsed["description"] == "First part of the description followed by essential context." assert parsed["vibe"] == "First part of the vibe followed by the rest." quoted_vibe = builder.parse_agent( REPO_ROOT / "specialized" / "identity-graph-operator.md", REPO_ROOT ) assert quoted_vibe is not None assert quoted_vibe["vibe"].endswith('who is this?"') out_dir = Path(tmp) / "hermes" builder.build(REPO_ROOT, out_dir) plugin = load_module( "agency_agents_router_check", out_dir / builder.PLUGIN_NAME / "__init__.py", ) ctx = RecordingContext() plugin.register(ctx) expected_tools = { "agency_agents_search", "agency_agents_inspect", "agency_agents_load", "agency_agents_delegate", } assert set(ctx.tools) == expected_tools for name, registration in ctx.tools.items(): schema = registration["schema"] assert schema["name"] == name, f"{name}: schema name is missing" assert schema.get("description"), f"{name}: schema description is missing" parameters = schema.get("parameters") assert isinstance(parameters, dict), f"{name}: schema.parameters is missing" assert parameters.get("type") == "object", f"{name}: parameters must be an object" assert isinstance(parameters.get("properties"), dict), f"{name}: properties are missing" assert isinstance(parameters.get("required"), list), f"{name}: required must be a list" search = json.loads( ctx.tools["agency_agents_search"]["handler"]({"query": "backend architecture"}) ) assert search["success"] is True assert search["results"], "search should return at least one specialist" slug = search["results"][0]["slug"] inspected = json.loads( ctx.tools["agency_agents_inspect"]["handler"]({"slug": slug}) ) assert inspected["success"] is True assert inspected["agent"]["slug"] == slug print("PASSED: generated Hermes plugin schemas and routing behavior are valid.") return 0 if __name__ == "__main__": raise SystemExit(main())