Files
hermes-agent/atropos/tools/toolset_resolver.py
Shannon Sands 4939130485 tool dedup
2026-02-02 15:28:10 +10:00

89 lines
3.0 KiB
Python

"""
Toolset resolution for Hermes-Agent Atropos integration.
We primarily reuse Hermes-Agent toolsets (`toolsets.py`), but Atropos training/envs
need a few extra sandbox-oriented toolsets that Hermes doesn't expose by default
(e.g. filesystem + stateful terminal).
"""
from __future__ import annotations
from typing import Any, Dict, List, Optional, Set
import toolsets as hermes_toolsets
ATROPOS_TOOLSETS: Dict[str, Dict[str, Any]] = {
"filesystem": {
"description": "Read/write files in the sandbox workspace.",
"tools": ["read_file", "write_file"],
"includes": [],
},
"terminal_stateful": {
"description": "Stateful terminal execution (tmux/TUI support) inside the sandbox.",
"tools": ["terminal_stateful", "tmux"],
"includes": [],
},
"sandbox": {
"description": "Sandbox tools (terminal + filesystem).",
"tools": [],
"includes": ["terminal", "filesystem"],
},
"default": {
"description": "Default toolset for Atropos AgentEnv tasks.",
"tools": [],
"includes": ["sandbox"],
},
"full": {
"description": "All Hermes tools plus Atropos sandbox additions.",
"tools": [],
"includes": ["all", "filesystem", "sandbox", "terminal_stateful"],
},
}
def validate_toolset(name: str) -> bool:
if name in {"all", "*"}:
return True
return hermes_toolsets.validate_toolset(name) or name in ATROPOS_TOOLSETS
def resolve_toolset(name: str, visited: Optional[Set[str]] = None) -> List[str]:
if visited is None:
visited = set()
if name in {"all", "*"}:
# Union Hermes + Atropos toolsets.
all_tools: Set[str] = set()
for tname in hermes_toolsets.get_toolset_names():
all_tools.update(resolve_toolset(tname, visited=set()))
for tname, spec in ATROPOS_TOOLSETS.items():
# Avoid recursion: some Atropos toolsets (e.g. "full") include "all".
if tname == "full" or "all" in (spec.get("includes") or []):
continue
all_tools.update(resolve_toolset(tname, visited=set()))
return sorted(all_tools)
if name in ATROPOS_TOOLSETS:
if name in visited:
return []
visited.add(name)
spec = ATROPOS_TOOLSETS[name]
tools: Set[str] = set(spec.get("tools", []))
for inc in spec.get("includes", []):
tools.update(resolve_toolset(inc, visited=set(visited)))
return sorted(tools)
# Fall back to Hermes toolsets.
# IMPORTANT: do not pre-add `name` to `visited` here; Hermes' resolver uses
# `visited` for its own cycle detection and will treat the presence of `name`
# as a circular dependency.
return sorted(hermes_toolsets.resolve_toolset(name, visited=set(visited)))
def resolve_multiple_toolsets(names: List[str]) -> List[str]:
tools: Set[str] = set()
for name in names:
tools.update(resolve_toolset(name, visited=set()))
return sorted(tools)