mirror of
https://github.com/msitarzewski/agency-agents.git
synced 2026-07-15 20:45:17 +03:00
fix(hermes): expose Agency tool parameters (#717)
Co-authored-by: Michael Sitarzewski <michael@sitarzewski.com>
This commit is contained in:
@@ -18,3 +18,6 @@ jobs:
|
||||
run: |
|
||||
chmod +x scripts/check-tools.sh
|
||||
./scripts/check-tools.sh
|
||||
|
||||
- name: Validate generated Hermes plugin
|
||||
run: python3 scripts/check-hermes-plugin.py
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
Generated by `scripts/convert.sh --tool hermes`.
|
||||
|
||||
This integration installs one Hermes plugin named `agency-agents-router` instead
|
||||
of adding 232+ generated skills to `skills.external_dirs`. Hermes sees a
|
||||
of adding hundreds of generated skills to `skills.external_dirs`. Hermes sees a
|
||||
small fixed tool surface at startup, while the complete Agency roster is
|
||||
stored on disk in `data/agents.json` and searched/loaded lazily.
|
||||
|
||||
Generated agent count: 232
|
||||
Generated agent count: 254
|
||||
|
||||
## Tools exposed to Hermes
|
||||
|
||||
@@ -16,6 +16,20 @@ Generated agent count: 232
|
||||
- `agency_agents_load` — compose one specialist prompt for the current task.
|
||||
- `agency_agents_delegate` — delegate through Hermes `delegate_task` when available.
|
||||
|
||||
Each tool is registered with Hermes' complete function-tool schema, including
|
||||
its name, description, and JSON `parameters`. The available arguments are:
|
||||
|
||||
| Tool | Arguments |
|
||||
| --- | --- |
|
||||
| `agency_agents_search` | `query` (required), optional `division` and `limit` |
|
||||
| `agency_agents_inspect` | `agent` or `slug`, optional `include_body` |
|
||||
| `agency_agents_load` | `agent` or `slug`, optional `task` |
|
||||
| `agency_agents_delegate` | `agent` or `slug`, `task` (required), optional `toolsets` |
|
||||
|
||||
A normal flow is: search by capability, take a returned `slug`, then inspect,
|
||||
load, or delegate to that specialist. You can ask Hermes to do this in natural
|
||||
language; direct tool calls are not required.
|
||||
|
||||
## Specialist usage instruction for Hermes
|
||||
|
||||
When a Hermes project needs Agency specialists, explicitly ask Hermes to use
|
||||
@@ -58,3 +72,8 @@ ${HERMES_HOME:-~/.hermes}/plugins/agency-agents-router
|
||||
|
||||
It then enables `agency-agents-router` under `plugins.enabled` in the Hermes
|
||||
config. It does **not** write to `skills.external_dirs`.
|
||||
|
||||
Restart Hermes or start a new session after installing so the plugin and its
|
||||
tool schemas are loaded. If Hermes displays these tools without their documented
|
||||
arguments, regenerate and reinstall the plugin from the latest Agency Agents
|
||||
checkout, then restart Hermes.
|
||||
|
||||
@@ -225,13 +225,17 @@ SEARCH_DESCRIPTION = (
|
||||
"Swami specialist, role, discipline, or wants help choosing the right agent."
|
||||
)
|
||||
SEARCH_SCHEMA = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Natural-language search query."},
|
||||
"division": {"type": "string", "description": "Optional division filter, e.g. engineering, marketing, testing."},
|
||||
"limit": {"type": "integer", "description": "Maximum results, default 8, max 25."},
|
||||
"name": "agency_agents_search",
|
||||
"description": SEARCH_DESCRIPTION,
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Natural-language search query."},
|
||||
"division": {"type": "string", "description": "Optional division filter, e.g. engineering, marketing, testing."},
|
||||
"limit": {"type": "integer", "description": "Maximum results, default 8, max 25."},
|
||||
},
|
||||
"required": ["query"],
|
||||
},
|
||||
"required": ["query"],
|
||||
}
|
||||
|
||||
READ_DESCRIPTION = (
|
||||
@@ -239,13 +243,17 @@ READ_DESCRIPTION = (
|
||||
"and includes the full specialist instructions only when include_body is true."
|
||||
)
|
||||
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."},
|
||||
"name": "agency_agents_inspect",
|
||||
"description": READ_DESCRIPTION,
|
||||
"parameters": {
|
||||
"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": [],
|
||||
},
|
||||
"required": [],
|
||||
}
|
||||
|
||||
PROMPT_DESCRIPTION = (
|
||||
@@ -253,13 +261,17 @@ PROMPT_DESCRIPTION = (
|
||||
"Use after agency_agents_search when you need one specialist's full context."
|
||||
)
|
||||
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."},
|
||||
"name": "agency_agents_load",
|
||||
"description": PROMPT_DESCRIPTION,
|
||||
"parameters": {
|
||||
"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": [],
|
||||
},
|
||||
"required": [],
|
||||
}
|
||||
|
||||
DELEGATE_DESCRIPTION = (
|
||||
@@ -268,18 +280,22 @@ DELEGATE_DESCRIPTION = (
|
||||
"specialist prompt if delegation is unavailable."
|
||||
)
|
||||
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",
|
||||
"items": {"type": "string"},
|
||||
"description": "Optional Hermes toolsets for the delegated worker, e.g. ['terminal','file'].",
|
||||
"name": "agency_agents_delegate",
|
||||
"description": DELEGATE_DESCRIPTION,
|
||||
"parameters": {
|
||||
"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",
|
||||
"items": {"type": "string"},
|
||||
"description": "Optional Hermes toolsets for the delegated worker, e.g. ['terminal','file'].",
|
||||
},
|
||||
},
|
||||
"required": ["task"],
|
||||
},
|
||||
"required": ["task"],
|
||||
}
|
||||
|
||||
|
||||
@@ -402,7 +418,7 @@ def readme(agent_count: int) -> str:
|
||||
Generated by `scripts/convert.sh --tool hermes`.
|
||||
|
||||
This integration installs one Hermes plugin named `{PLUGIN_NAME}` instead
|
||||
of adding 232+ generated skills to `skills.external_dirs`. Hermes sees a
|
||||
of adding hundreds of generated skills to `skills.external_dirs`. Hermes sees a
|
||||
small fixed tool surface at startup, while the complete Agency roster is
|
||||
stored on disk in `data/agents.json` and searched/loaded lazily.
|
||||
|
||||
@@ -415,6 +431,20 @@ def readme(agent_count: int) -> str:
|
||||
- `agency_agents_load` — compose one specialist prompt for the current task.
|
||||
- `agency_agents_delegate` — delegate through Hermes `delegate_task` when available.
|
||||
|
||||
Each tool is registered with Hermes' complete function-tool schema, including
|
||||
its name, description, and JSON `parameters`. The available arguments are:
|
||||
|
||||
| Tool | Arguments |
|
||||
| --- | --- |
|
||||
| `agency_agents_search` | `query` (required), optional `division` and `limit` |
|
||||
| `agency_agents_inspect` | `agent` or `slug`, optional `include_body` |
|
||||
| `agency_agents_load` | `agent` or `slug`, optional `task` |
|
||||
| `agency_agents_delegate` | `agent` or `slug`, `task` (required), optional `toolsets` |
|
||||
|
||||
A normal flow is: search by capability, take a returned `slug`, then inspect,
|
||||
load, or delegate to that specialist. You can ask Hermes to do this in natural
|
||||
language; direct tool calls are not required.
|
||||
|
||||
## Specialist usage instruction for Hermes
|
||||
|
||||
When a Hermes project needs Agency specialists, explicitly ask Hermes to use
|
||||
@@ -457,6 +487,11 @@ def readme(agent_count: int) -> str:
|
||||
|
||||
It then enables `{PLUGIN_NAME}` under `plugins.enabled` in the Hermes
|
||||
config. It does **not** write to `skills.external_dirs`.
|
||||
|
||||
Restart Hermes or start a new session after installing so the plugin and its
|
||||
tool schemas are loaded. If Hermes displays these tools without their documented
|
||||
arguments, regenerate and reinstall the plugin from the latest Agency Agents
|
||||
checkout, then restart Hermes.
|
||||
"""
|
||||
).lstrip()
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/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:
|
||||
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())
|
||||
Reference in New Issue
Block a user