runtime: sync router/gateway/config policy and clan role registry

This commit is contained in:
Apple
2026-02-19 00:14:06 -08:00
parent 675b25953b
commit dfc0ef1ceb
35 changed files with 6141 additions and 498 deletions

View File

@@ -15,6 +15,7 @@ import json
import os
import subprocess
import sys
import re
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, List, Any, Optional
@@ -31,6 +32,7 @@ REGISTRY_PATH = BASE_DIR / "config" / "agent_registry.yml"
GATEWAY_DIR = BASE_DIR / "gateway-bot"
ROUTER_CONFIG = BASE_DIR / "services" / "router" / "router-config.yml"
CREWAI_DIR = BASE_DIR / "services" / "crewai-service" / "app"
CREWAI_TEAMS_GENERATED = BASE_DIR / "config" / "crewai_teams.generated.yml"
class Colors:
@@ -52,6 +54,97 @@ def load_registry() -> Dict[str, Any]:
return yaml.safe_load(f)
def _slugify(value: str) -> str:
s = (value or "").strip().lower()
s = re.sub(r"[^a-z0-9]+", "_", s)
s = re.sub(r"_+", "_", s).strip("_")
return s or "role"
def _legacy_crewai_to_orchestration(agent: Dict[str, Any]) -> Dict[str, Any]:
"""
Backward-compatible adapter from legacy `crewai` block to new `orchestration`.
"""
legacy = agent.get("crewai", {}) or {}
enabled = bool(legacy.get("enabled", False))
orchestrator = bool(legacy.get("orchestrator", False))
team = legacy.get("team", []) or []
if not enabled or not orchestrator:
mode = "llm_only"
elif team:
mode = "hybrid"
else:
# legacy "enabled but no team" usually means orchestration via A2A only
mode = "hybrid"
default_profile = {
"team_name": f"{agent.get('display_name', agent.get('id', 'agent'))} Team",
"parallel_roles": True,
"max_concurrency": 3,
"synthesis": {
"role_context": f"{agent.get('display_name', agent.get('id', 'agent'))} Orchestrator",
"llm_profile": agent.get("llm_profile", "reasoning"),
},
"team": [],
"delegation": {
"enabled": bool(legacy.get("can_delegate_to_all", False)),
"forbid_self": True,
"max_hops": 2,
"allow_top_level_agents": [],
},
}
for member in team:
role_name = member.get("role", "") if isinstance(member, dict) else str(member)
default_profile["team"].append(
{
"id": _slugify(role_name),
"role_context": role_name,
"llm_profile": "reasoning",
}
)
return {
"mode": mode,
"crew": {
"enabled": enabled and orchestrator,
"default_profile": "default",
"profiles": {"default": default_profile},
},
"a2a": {
"enabled": bool(legacy.get("can_delegate_to_all", False)),
"allow_top_level_agents": ["all_top_level"] if legacy.get("can_delegate_to_all", False) else [],
"max_hops": 2,
"forbid_self": True,
},
"response_contract": {
"user_visible_speaker": "self",
"crew_roles_user_visible": False,
},
}
def get_orchestration(agent: Dict[str, Any]) -> Dict[str, Any]:
"""
Return normalized orchestration object.
Prefers `orchestration`, falls back to legacy `crewai`.
"""
if isinstance(agent.get("orchestration"), dict):
return agent["orchestration"]
return _legacy_crewai_to_orchestration(agent)
def get_default_profile_config(orchestration: Dict[str, Any]) -> Dict[str, Any]:
crew = orchestration.get("crew", {}) if isinstance(orchestration, dict) else {}
profiles = crew.get("profiles", {}) if isinstance(crew, dict) else {}
default_profile = crew.get("default_profile", "default")
if isinstance(profiles, dict) and default_profile in profiles:
return profiles[default_profile] or {}
if isinstance(profiles, dict) and "default" in profiles:
return profiles["default"] or {}
return {}
def cmd_list(args):
registry = load_registry()
agents = registry.get("agents", [])
@@ -157,10 +250,38 @@ def cmd_validate(args):
if kw_count < 3:
warnings.append(f"{agent_id}: Only {kw_count} routing keywords (recommend >= 3)")
# top_level must be CrewAI orchestrator
crewai = agent.get("crewai", {})
if not crewai.get("orchestrator", False):
errors.append(f"{agent_id}: top_level agent must have crewai.orchestrator=true")
orchestration = get_orchestration(agent)
mode = orchestration.get("mode", "llm_only")
crew = orchestration.get("crew", {}) if isinstance(orchestration, dict) else {}
crew_enabled = bool(crew.get("enabled", False))
profiles = crew.get("profiles", {}) if isinstance(crew, dict) else {}
default_profile = crew.get("default_profile", "default")
if mode not in ["llm_only", "crew_only", "hybrid"]:
errors.append(f"{agent_id}: Invalid orchestration.mode '{mode}'")
if mode in ["crew_only", "hybrid"] and not crew_enabled:
errors.append(f"{agent_id}: mode={mode} requires orchestration.crew.enabled=true")
if crew_enabled:
if not isinstance(profiles, dict) or not profiles:
errors.append(f"{agent_id}: crew.enabled=true but no crew.profiles defined")
elif default_profile not in profiles:
errors.append(f"{agent_id}: default_profile '{default_profile}' missing in crew.profiles")
else:
p = profiles.get(default_profile) or {}
team = p.get("team", []) if isinstance(p, dict) else []
delegation = p.get("delegation", {}) if isinstance(p, dict) else {}
# allow delegation-only orchestrators, but otherwise team must exist
if not team and not delegation.get("enabled", False):
errors.append(
f"{agent_id}: default crew profile has empty team and delegation disabled "
f"(nothing to orchestrate)"
)
rc = orchestration.get("response_contract", {}) if isinstance(orchestration, dict) else {}
if rc and rc.get("crew_roles_user_visible", False):
errors.append(f"{agent_id}: response_contract.crew_roles_user_visible must be false")
if errors:
print(f"{Colors.RED}ERRORS ({len(errors)}):{Colors.RESET}")
@@ -257,34 +378,126 @@ def cmd_generate(args):
"workers": [],
"teams": {}
}
existing_crewai = {}
existing_crewai_path = BASE_DIR / "config" / "crewai_agents.json"
if existing_crewai_path.exists():
try:
with open(existing_crewai_path, "r", encoding="utf-8") as f:
existing_crewai = json.load(f)
except Exception:
existing_crewai = {}
for agent in agents:
crewai = agent.get("crewai", {})
if crewai.get("enabled", False):
has_explicit_orchestration = isinstance(agent.get("orchestration"), dict)
if has_explicit_orchestration:
orchestration = get_orchestration(agent)
mode = orchestration.get("mode", "llm_only")
crew = orchestration.get("crew", {}) if isinstance(orchestration, dict) else {}
crew_enabled = bool(crew.get("enabled", False)) and mode in ["crew_only", "hybrid"]
else:
# Strict backward compatibility for legacy registry entries.
legacy = agent.get("crewai", {}) or {}
mode = "hybrid" if legacy.get("enabled", False) else "llm_only"
crew_enabled = bool(legacy.get("enabled", False))
if crew_enabled:
agent_entry = {
"id": agent["id"],
"display_name": agent.get("display_name"),
"role": agent.get("canonical_role"),
"can_orchestrate": crewai.get("orchestrator", False),
"can_orchestrate": bool(
get_orchestration(agent).get("mode", "llm_only") != "llm_only"
if has_explicit_orchestration
else (agent.get("crewai", {}) or {}).get("orchestrator", False)
),
"domains": agent.get("domains", []),
}
if crewai.get("orchestrator"):
if has_explicit_orchestration:
is_orchestrator = agent.get("class") == "top_level"
else:
is_orchestrator = bool((agent.get("crewai", {}) or {}).get("orchestrator", False))
if is_orchestrator:
crewai_config["orchestrators"].append(agent_entry)
else:
crewai_config["workers"].append(agent_entry)
if crewai.get("team"):
dname = agent.get('display_name', '')
crewai_config["teams"][agent["id"]] = {
"team_name": f"{dname} Team",
"members": crewai["team"]
}
if has_explicit_orchestration:
orchestration = get_orchestration(agent)
profile_cfg = get_default_profile_config(orchestration)
team_members = profile_cfg.get("team", []) if isinstance(profile_cfg, dict) else []
team_name = profile_cfg.get("team_name", f"{agent.get('display_name', agent['id'])} Team")
if team_members:
# Router needs lightweight list; keep role names for compatibility.
members_summary = []
for m in team_members:
if isinstance(m, dict):
members_summary.append(
{
"role": m.get("role_context", m.get("id", "role")),
"skills": m.get("skills", []),
}
)
else:
members_summary.append({"role": str(m), "skills": []})
crewai_config["teams"][agent["id"]] = {
"team_name": team_name,
"members": members_summary,
}
else:
# Preserve legacy team payload (including skills) if present.
legacy_team = (agent.get("crewai", {}) or {}).get("team", [])
if existing_crewai.get("teams", {}).get(agent["id"]):
# Keep pre-existing generated team shape to avoid accidental shrinking.
crewai_config["teams"][agent["id"]] = existing_crewai["teams"][agent["id"]]
elif legacy_team:
crewai_config["teams"][agent["id"]] = {
"team_name": f"{agent.get('display_name', agent['id'])} Team",
"members": legacy_team,
}
crewai_json = BASE_DIR / "config" / "crewai_agents.json"
with open(crewai_json, "w") as f:
json.dump(crewai_config, f, indent=2, ensure_ascii=False)
generated_files.append(str(crewai_json))
print(f" {Colors.GREEN}OK{Colors.RESET} {crewai_json}")
if flags.get("generate_crewai_teams", False):
teams_doc = {
"schema_version": 1,
"version": registry.get("version", "generated"),
"description": "Generated from config/agent_registry.yml (orchestration.crew.*)",
}
for agent in agents:
if agent.get("class") != "top_level":
continue
# Canary-safe generation: only agents with explicit orchestration block
# are emitted to generated teams file.
if not isinstance(agent.get("orchestration"), dict):
continue
orchestration = get_orchestration(agent)
mode = orchestration.get("mode", "llm_only")
crew = orchestration.get("crew", {}) if isinstance(orchestration, dict) else {}
crew_enabled = bool(crew.get("enabled", False)) and mode in ["crew_only", "hybrid"]
if not crew_enabled:
continue
profiles = crew.get("profiles", {})
if not isinstance(profiles, dict) or not profiles:
continue
teams_doc[agent["id"]] = {
"profiles": profiles,
"default_profile": crew.get("default_profile", "default"),
}
hints = crew.get("profile_hints")
if hints:
teams_doc[agent["id"]]["profile_hints"] = hints
with open(CREWAI_TEAMS_GENERATED, "w") as f:
yaml.safe_dump(teams_doc, f, sort_keys=False, allow_unicode=True)
generated_files.append(str(CREWAI_TEAMS_GENERATED))
print(f" {Colors.GREEN}OK{Colors.RESET} {CREWAI_TEAMS_GENERATED}")
print(f"\n{Colors.GREEN}Generated {len(generated_files)} files{Colors.RESET}\n")