Hermes tools 是自注册函数,它们被分组到 toolsets 中,并通过一个中央 registry / dispatch system 执行。
主要文件:
- tools/registry.py
- model_tools.py
- toolsets.py
- tools/terminal_tool.py
- tools/environments/*
工具注册模型
Section titled “工具注册模型”每个 tool module 都会在 import time 调用 registry.register(...)。
model_tools.py 负责导入 / 发现 tool modules,并构建 model 使用的 schema list。
registry.register() 如何工作
Section titled “registry.register() 如何工作”tools/ 中的每个 tool file 都会在 module level 调用 registry.register() 来声明自身。函数签名如下:
registry.register( name="terminal", # 唯一 tool name(用于 API schemas) toolset="terminal", # 该 tool 所属的 Toolset schema={...}, # OpenAI function-calling schema(description、parameters) handler=handle_terminal, # tool 被调用时执行的函数 check_fn=check_terminal, # 可选:返回 True/False 表示可用性 requires_env=["SOME_VAR"], # 可选:需要的 env vars(用于 UI display) is_async=False, # handler 是否是 async coroutine description="Run commands", # 人类可读 description emoji="💻", # spinner/progress display 使用的 emoji)每次调用都会创建一个 ToolEntry,并存储在 singleton ToolRegistry._tools dict 中,以 tool name 作为 key。如果跨 toolsets 发生 name collision,会记录一个 warning,并且后注册的会获胜。
Discovery:discover_builtin_tools()
Section titled “Discovery:discover_builtin_tools()”当 model_tools.py 被导入时,它会调用 tools/registry.py 中的 discover_builtin_tools()。此函数使用 AST parsing 扫描每个 tools/*.py 文件,查找包含顶层 registry.register() 调用的 modules,然后导入它们:
# tools/registry.py(简化版)def discover_builtin_tools(tools_dir=None): tools_path = Path(tools_dir) if tools_dir else Path(__file__).parent for path in sorted(tools_path.glob("*.py")): if path.name in {"__init__.py", "registry.py", "mcp_tool.py"}: continue if _module_registers_tools(path): # AST check for top-level registry.register() importlib.import_module(f"tools.{path.stem}")这种 auto-discovery 意味着新的 tool files 会被自动识别 —— 不需要维护手动列表。AST check 只匹配顶层 registry.register() 调用(不匹配函数内部的调用),因此 tools/ 中的 helper modules 不会被导入。
每次 import 都会触发该 module 的 registry.register() 调用。Optional tools 中的错误(例如 image generation 缺少 fal_client)会被捕获并记录日志 —— 它们不会阻止其他 tools 加载。
Core tool discovery 之后,MCP tools 和 plugin tools 也会被发现:
-
MCP tools ——
tools.mcp_tool.discover_mcp_tools()会读取 MCP server config,并从 external servers 注册 tools。 -
Plugin tools ——
hermes_cli.plugins.discover_plugins()会加载可能注册 additional tools 的 user / project / pip plugins。
Tool availability checking(check_fn)
Section titled “Tool availability checking(check_fn)”每个 tool 都可以可选地提供一个 check_fn —— 一个在 tool 可用时返回 True、不可用时返回 False 的 callable。典型检查包括:
- API key present —— 例如 web search 使用
lambda: bool(os.environ.get("SERP_API_KEY")) - Service running —— 例如检查 Honcho server 是否已配置
- Binary installed —— 例如验证 browser tools 的 playwright 是否可用
当 registry.get_definitions() 为 model 构建 schema list 时,它会运行每个 tool 的 check_fn():
# 从 registry.py 简化而来if entry.check_fn: try: available = bool(entry.check_fn()) except Exception: available = False # Exceptions = unavailable if not available: continue # 完全跳过此 tool关键行为:
- Check results 会按每次调用缓存 —— 如果多个 tools 共享相同的
check_fn,它只会运行一次。 check_fn()中的 exceptions 会被视为 “unavailable”(fail-safe)。is_toolset_available()方法会检查某个 toolset 的check_fn是否通过,用于 UI display 和 toolset resolution。
Toolset resolution
Section titled “Toolset resolution”Toolsets 是命名的 tool bundles。Hermes 通过以下内容解析它们:
- explicit enabled / disabled toolset lists
- platform presets(
hermes-cli、hermes-telegram等) - dynamic MCP toolsets
- 精选 special-purpose sets,例如
hermes-acp
get_tool_definitions() 如何过滤 tools
Section titled “get_tool_definitions() 如何过滤 tools”主要入口是 model_tools.get_tool_definitions(enabled_toolsets, disabled_toolsets, quiet_mode):
-
如果提供了
enabled_toolsets—— 只包含这些 toolsets 中的 tools。每个 toolset name 会通过resolve_toolset()解析,该函数会将 composite toolsets 展开成 individual tool names。 -
如果提供了
disabled_toolsets—— 从 ALL toolsets 开始,然后减去 disabled 的那些。 -
如果两者都没有 —— 包含所有 known toolsets。
-
Registry filtering —— resolved tool name set 会被传递给
registry.get_definitions(),它会应用check_fnfiltering,并返回 OpenAI-format schemas。 -
Dynamic schema patching —— filtering 之后,
execute_code和browser_navigateschemas 会被动态调整为只引用实际通过 filtering 的 tools(防止 model hallucination 不可用的 tools)。
Legacy toolset names
Section titled “Legacy toolset names”带有 _tools 后缀的旧 toolset names(例如 web_tools、terminal_tools)会通过 _LEGACY_TOOLSET_MAP 映射到现代 tool names,以保持向后兼容。
Dispatch
Section titled “Dispatch”在运行时,tools 会通过中央 registry 进行 dispatch,但一些 agent-level tools 例外,例如 memory / todo / session-search 处理。
Dispatch flow:model tool_call → handler execution
Section titled “Dispatch flow:model tool_call → handler execution”当 model 返回 tool_call 时,流程如下:
Model response with tool_call ↓run_agent.py agent loop ↓model_tools.handle_function_call(name, args, task_id, user_task) ↓[Agent-loop tools?] → 由 agent loop 直接处理(todo、memory、session_search、delegate_task) ↓[Plugin pre-hook] → invoke_hook("pre_tool_call", ...) ↓registry.dispatch(name, args, **kwargs) ↓按 name 查找 ToolEntry ↓[Async handler?] → 通过 _run_async() 桥接[Sync handler?] → 直接调用 ↓返回 result string(或 JSON error) ↓[Plugin post-hook] → invoke_hook("post_tool_call", ...)Error wrapping
Section titled “Error wrapping”所有 tool execution 都在两个层级进行了错误处理包装:
-
registry.dispatch()—— 捕获 handler 抛出的任何 exception,并以 JSON 形式返回{"error": "Tool execution failed: ExceptionType: message"}。 -
handle_function_call()—— 使用第二层try/except包装整个 dispatch,返回{"error": "Error executing tool_name: message"}。
这确保 model 总是收到格式良好的 JSON string,而不是未处理的 exception。
Agent-loop tools
Section titled “Agent-loop tools”有四个 tools 会在 registry dispatch 之前被拦截,因为它们需要 agent-level state(TodoStore、MemoryStore 等):
todo—— planning / task trackingmemory—— persistent memory writessession_search—— cross-session recalldelegate_task—— 生成 subagent sessions
这些 tools 的 schemas 仍然会注册在 registry 中(用于 get_tool_definitions),但如果 dispatch 不知为何直接到达它们的 handlers,它们会返回一个 stub error。
Async bridging
Section titled “Async bridging”当 tool handler 是 async 时,_run_async() 会将它桥接到 sync dispatch path:
- CLI path(没有正在运行的 loop)—— 使用一个 persistent event loop,以保持 cached async clients 存活
- Gateway path(已有 running loop)—— 启动一个 disposable thread,并使用
asyncio.run() - Worker threads(parallel tools)—— 使用存储在线程本地存储中的 per-thread persistent loops
DANGEROUS_PATTERNS approval flow
Section titled “DANGEROUS_PATTERNS approval flow”terminal tool 集成了一个 dangerous-command approval system,定义在 tools/approval.py 中:
- Pattern detection ——
DANGEROUS_PATTERNS是一个(regex, description)tuples 列表,覆盖破坏性操作:
- Recursive deletes(
rm -rf) - Filesystem formatting(
mkfs、dd) - SQL destructive operations(
DROP TABLE、没有WHERE的DELETE FROM) - System config overwrites(
> /etc/) - Service manipulation(
systemctl stop) - Remote code execution(
curl | sh) - Fork bombs、process kills 等
-
Detection —— 在执行任何 terminal command 之前,
detect_dangerous_command(command)会检查所有 patterns。 -
Approval prompt —— 如果发现匹配:
- CLI mode —— 交互式 prompt 会询问用户 approve、deny,或 allow permanently
- Gateway mode —— async approval callback 会将请求发送到 messaging platform
- Smart approval —— 可选地,一个 auxiliary LLM 可以自动批准匹配 patterns 的低风险 commands(例如
rm -rf node_modules/是安全的,但会匹配 “recursive delete”)
-
Session state —— approvals 按 session 跟踪。一旦你在某个 session 中批准了 “recursive delete”,后续
rm -rfcommands 就不会再次提示。 -
Permanent allowlist —— “allow permanently” 选项会将 pattern 写入
config.yaml的command_allowlist,跨 sessions 持久化。
Terminal / runtime environments
Section titled “Terminal / runtime environments”terminal system 支持多个 backends:
localdockersshsingularitymodaldaytonavercel_sandbox
它还支持:
- per-task cwd overrides
- background process management
- PTY mode
- dangerous commands 的 approval callbacks
Concurrency
Section titled “Concurrency”Tool calls 可以按顺序执行,也可以并发执行,具体取决于 tool mix 和交互需求。
- Toolsets Reference
- Built-in Tools Reference
- Agent Loop Internals
- ACP Internals