#!/usr/bin/env python3 """Harness: registry/seed_agents.yaml → router/registry_facts.pl (stdlib + PyYAML). Not product code. Regenerates Prolog agent facts so symbolic_rules.pl stays aligned with the seed registry. Prefer: scripts/check_prolog_registry_sync.sh """ from __future__ import annotations import sys from pathlib import Path try: import yaml except ImportError: yaml = None ROOT = Path(__file__).resolve().parents[1] SEED = ROOT / "registry" / "seed_agents.yaml" OUT = ROOT / "router" / "registry_facts.pl" def to_atom(agent_id: str) -> str: """Registry PascalCase id → Prolog atom (downcase first letter).""" if not agent_id: raise ValueError("empty agent_id") return agent_id[0].lower() + agent_id[1:] def parse_seed_stdlib(text: str) -> list[dict]: """Minimal YAML subset parser for seed_agents.yaml (no PyYAML required).""" agents: list[dict] = [] cur: dict | None = None list_key: str | None = None def flush(): nonlocal cur if cur is not None: agents.append(cur) cur = None for raw in text.splitlines(): if not raw.strip() or raw.lstrip().startswith("#"): continue if raw.startswith("- agent_id:"): flush() cur = { "agent_id": raw.split(":", 1)[1].strip(), "capabilities": [], "permissions": [], "dependencies": [], "control_plane": False, } list_key = None continue if cur is None: continue indent = len(raw) - len(raw.lstrip(" ")) s = raw.strip() if indent == 2 and ":" in s and not s.startswith("-"): key, _, val = s.partition(":") key = key.strip() val = val.strip() list_key = None if key in ("capabilities", "permissions", "dependencies", "languages", "input_types", "output_types", "required_evidence"): if val.startswith("[") and val.endswith("]"): inner = val[1:-1].strip() cur[key] = [x.strip() for x in inner.split(",") if x.strip()] if inner else [] elif val == "": list_key = key cur[key] = [] else: cur[key] = [val] elif key == "control_plane": cur[key] = val.lower() in ("true", "yes", "1") elif key == "risk_class": cur[key] = val elif key == "domain": cur[key] = val else: cur[key] = val elif indent >= 4 and s.startswith("- ") and list_key: cur.setdefault(list_key, []).append(s[2:].strip()) flush() return agents def load_agents() -> list[dict]: text = SEED.read_text(encoding="utf-8") if yaml is not None: data = yaml.safe_load(text) if not isinstance(data, list): raise SystemExit("seed_agents.yaml: expected list") return data return parse_seed_stdlib(text) def emit(agents: list[dict]) -> str: lines: list[str] = [ "% SPDX-License-Identifier: AGPL-3.0-only", "% Copyright (C) 2026 David contributors", "% GENERATED by scripts/gen_registry_facts.py from registry/seed_agents.yaml", "% Do not edit by hand — regenerate via scripts/check_prolog_registry_sync.sh", "%", "% Agent atom map: registry agent_id → Prolog atom = downcase first letter", "% LegacyCobol → legacyCobol, Risk → risk, Quant → quant", "% Predicates: agent_id/2, agent/1, control_plane/1, capability/2,", "% permission/2, risk_class/2, depends/2", "", ] # agent_id/2 map first for a in agents: rid = a["agent_id"] atom = to_atom(rid) lines.append(f"agent_id('{rid}', {atom}).") lines.append("") for a in agents: atom = to_atom(a["agent_id"]) lines.append(f"agent({atom}).") lines.append("") for a in agents: if a.get("control_plane"): lines.append(f"control_plane({to_atom(a['agent_id'])}).") lines.append("") for a in agents: atom = to_atom(a["agent_id"]) for cap in a.get("capabilities") or []: lines.append(f"capability({atom}, {cap}).") lines.append("") for a in agents: atom = to_atom(a["agent_id"]) for perm in a.get("permissions") or []: lines.append(f"permission({atom}, {perm}).") lines.append("") for a in agents: atom = to_atom(a["agent_id"]) rc = a.get("risk_class") if not rc: raise SystemExit(f"missing risk_class for {a['agent_id']}") lines.append(f"risk_class({atom}, {rc}).") lines.append("") for a in agents: atom = to_atom(a["agent_id"]) for dep in a.get("dependencies") or []: lines.append(f"depends({atom}, {to_atom(dep)}).") lines.append("") return "\n".join(lines) def main() -> int: out = Path(sys.argv[1]) if len(sys.argv) > 1 else OUT agents = load_agents() if not agents: print("no agents loaded", file=sys.stderr) return 1 text = emit(agents) out.parent.mkdir(parents=True, exist_ok=True) out.write_text(text, encoding="utf-8") print(f"wrote {out} ({len(agents)} agents)") return 0 if __name__ == "__main__": raise SystemExit(main())