mirror of
https://github.com/msitarzewski/agency-agents.git
synced 2026-07-11 18:53:41 +03:00
fix(hermes): accept slug alias + derive divisions from divisions.json (#666)
Two correctness fixes to the Hermes plugin generator: 1. slug alias (#665): agency_agents_search returns results keyed by `slug`, but load/inspect/delegate only accepted a param named `agent`, so the natural chain search -> load(slug=...) failed with "agent not found". Add `slug` as an optional alias across the READ/PROMPT/DELEGATE schemas and resolve either key in the handlers (via _identifier), with a clear "agent or slug is required" error when neither is passed. Backward compatible; `required` relaxed to [] (task-only for delegate). 2. division drift: AGENT_DIRS was a hardcoded copy of the division list that the bash check-divisions.sh guard can't see (it's a Python list), so it silently dropped healthcare (#655) — the two healthcare agents were missing from the Hermes roster. Derive the division dirs from divisions.json instead (mirrors the #659 fix to check-agent-originality.sh), so the roster stays in sync with the catalog by construction. Verified on the regenerated plugin: roster is 235 agents (healthcare now indexed); search "clinical evidence healthcare" -> inspect(slug=...) resolves to Clinical Evidence Agent — exercising both fixes together. agent= still works. Fixes #665 Claude-Session: https://claude.ai/code/session_01WKnDRWM4izsB8WAXKszhsq Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
217a63b8b6
commit
6f8d5e50ea
@@ -15,28 +15,19 @@ import shutil
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
AGENT_DIRS = [
|
||||
"academic",
|
||||
"design",
|
||||
"engineering",
|
||||
"finance",
|
||||
"game-development",
|
||||
"gis",
|
||||
"marketing",
|
||||
"paid-media",
|
||||
"product",
|
||||
"project-management",
|
||||
"sales",
|
||||
"security",
|
||||
"spatial-computing",
|
||||
"specialized",
|
||||
"support",
|
||||
"testing",
|
||||
]
|
||||
|
||||
PLUGIN_NAME = "agency-agents-router"
|
||||
|
||||
|
||||
def division_dirs(repo_root: Path) -> list[str]:
|
||||
# divisions.json (repo root) is the single source of truth for the division
|
||||
# set. Read it rather than hardcoding a copy here: a hardcoded list silently
|
||||
# drops new divisions from the Hermes roster (e.g. healthcare) the moment the
|
||||
# catalog grows. check-divisions.sh guards divisions.json against the tracked
|
||||
# dirs, so deriving from it keeps this plugin in sync by construction.
|
||||
data = json.loads((repo_root / "divisions.json").read_text(encoding="utf-8"))
|
||||
return sorted(data["divisions"].keys())
|
||||
|
||||
|
||||
def slugify(value: str) -> str:
|
||||
value = value.lower()
|
||||
value = re.sub(r"[^a-z0-9]+", "-", value)
|
||||
@@ -78,7 +69,7 @@ def parse_agent(path: Path, repo_root: Path) -> dict[str, str] | None:
|
||||
|
||||
def collect_agents(repo_root: Path) -> list[dict[str, str]]:
|
||||
agents: list[dict[str, str]] = []
|
||||
for dirname in AGENT_DIRS:
|
||||
for dirname in division_dirs(repo_root):
|
||||
base = repo_root / dirname
|
||||
if not base.is_dir():
|
||||
continue
|
||||
@@ -153,6 +144,21 @@ def _agent_lookup(identifier: str) -> dict[str, Any] | None:
|
||||
return None
|
||||
|
||||
|
||||
def _identifier(args: dict[str, Any]) -> str:
|
||||
# Accept either "agent" or "slug": agency_agents_search returns results keyed
|
||||
# by "slug", so callers naturally chain search -> load/inspect/delegate with
|
||||
# slug=. Both name the same thing (a slug or exact display name).
|
||||
return str(args.get("agent") or args.get("slug") or "").strip()
|
||||
|
||||
|
||||
def _not_found(identifier: str) -> dict[str, Any]:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "agent not found" if identifier else "agent or slug is required",
|
||||
"agent": identifier or None,
|
||||
}
|
||||
|
||||
|
||||
def _score(agent: dict[str, Any], query_tokens: set[str], query_text: str) -> float:
|
||||
haystack_fields = [
|
||||
agent.get("name", ""),
|
||||
@@ -236,9 +242,10 @@ READ_SCHEMA = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"agent": {"type": "string", "description": "Agent slug or exact display name."},
|
||||
"slug": {"type": "string", "description": "Alias for agent. Pass the slug from agency_agents_search results."},
|
||||
"include_body": {"type": "boolean", "description": "Include full specialist instructions."},
|
||||
},
|
||||
"required": ["agent"],
|
||||
"required": [],
|
||||
}
|
||||
|
||||
PROMPT_DESCRIPTION = (
|
||||
@@ -249,9 +256,10 @@ PROMPT_SCHEMA = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"agent": {"type": "string", "description": "Agent slug or exact display name."},
|
||||
"slug": {"type": "string", "description": "Alias for agent. Pass the slug from agency_agents_search results."},
|
||||
"task": {"type": "string", "description": "The user's task to pair with the specialist context."},
|
||||
},
|
||||
"required": ["agent"],
|
||||
"required": [],
|
||||
}
|
||||
|
||||
DELEGATE_DESCRIPTION = (
|
||||
@@ -263,6 +271,7 @@ DELEGATE_SCHEMA = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"agent": {"type": "string", "description": "Agent slug or exact display name."},
|
||||
"slug": {"type": "string", "description": "Alias for agent. Pass the slug from agency_agents_search results."},
|
||||
"task": {"type": "string", "description": "Concrete task for the specialist."},
|
||||
"toolsets": {
|
||||
"type": "array",
|
||||
@@ -270,7 +279,7 @@ DELEGATE_SCHEMA = {
|
||||
"description": "Optional Hermes toolsets for the delegated worker, e.g. ['terminal','file'].",
|
||||
},
|
||||
},
|
||||
"required": ["agent", "task"],
|
||||
"required": ["task"],
|
||||
}
|
||||
|
||||
|
||||
@@ -304,9 +313,10 @@ def register(ctx):
|
||||
|
||||
def read(args: dict[str, Any], **kwargs) -> str:
|
||||
del kwargs
|
||||
agent = _agent_lookup(str(args.get("agent", "")))
|
||||
identifier = _identifier(args)
|
||||
agent = _agent_lookup(identifier)
|
||||
if not agent:
|
||||
return _json({"success": False, "error": "agent not found", "agent": args.get("agent")})
|
||||
return _json(_not_found(identifier))
|
||||
payload = {"success": True, "agent": _summary(agent)}
|
||||
if bool(args.get("include_body", False)):
|
||||
payload["body"] = agent.get("body", "")
|
||||
@@ -314,9 +324,10 @@ def register(ctx):
|
||||
|
||||
def prompt(args: dict[str, Any], **kwargs) -> str:
|
||||
del kwargs
|
||||
agent = _agent_lookup(str(args.get("agent", "")))
|
||||
identifier = _identifier(args)
|
||||
agent = _agent_lookup(identifier)
|
||||
if not agent:
|
||||
return _json({"success": False, "error": "agent not found", "agent": args.get("agent")})
|
||||
return _json(_not_found(identifier))
|
||||
return _json({
|
||||
"success": True,
|
||||
"agent": _summary(agent),
|
||||
@@ -325,10 +336,11 @@ def register(ctx):
|
||||
|
||||
def delegate(args: dict[str, Any], **kwargs) -> str:
|
||||
del kwargs
|
||||
agent = _agent_lookup(str(args.get("agent", "")))
|
||||
identifier = _identifier(args)
|
||||
agent = _agent_lookup(identifier)
|
||||
task = str(args.get("task", "")).strip()
|
||||
if not agent:
|
||||
return _json({"success": False, "error": "agent not found", "agent": args.get("agent")})
|
||||
return _json(_not_found(identifier))
|
||||
if not task:
|
||||
return _json({"success": False, "error": "task is required"})
|
||||
composed = _specialist_prompt(agent, task)
|
||||
|
||||
Reference in New Issue
Block a user