Skip to content

hermes agent Tools Runtime

Hermes tools 是自注册函数,它们被分组到 toolsets 中,并通过一个中央 registry / dispatch system 执行。

主要文件:

  • tools/registry.py
  • model_tools.py
  • toolsets.py
  • tools/terminal_tool.py
  • tools/environments/*

每个 tool module 都会在 import time 调用 registry.register(...)

model_tools.py 负责导入 / 发现 tool modules,并构建 model 使用的 schema list。

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,并且后注册的会获胜。

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 也会被发现:

  1. MCP tools —— tools.mcp_tool.discover_mcp_tools() 会读取 MCP server config,并从 external servers 注册 tools。

  2. Plugin tools —— hermes_cli.plugins.discover_plugins() 会加载可能注册 additional tools 的 user / project / pip plugins。

每个 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。

Toolsets 是命名的 tool bundles。Hermes 通过以下内容解析它们:

  • explicit enabled / disabled toolset lists
  • platform presets(hermes-clihermes-telegram 等)
  • dynamic MCP toolsets
  • 精选 special-purpose sets,例如 hermes-acp

主要入口是 model_tools.get_tool_definitions(enabled_toolsets, disabled_toolsets, quiet_mode)

  1. 如果提供了 enabled_toolsets —— 只包含这些 toolsets 中的 tools。每个 toolset name 会通过 resolve_toolset() 解析,该函数会将 composite toolsets 展开成 individual tool names。

  2. 如果提供了 disabled_toolsets —— 从 ALL toolsets 开始,然后减去 disabled 的那些。

  3. 如果两者都没有 —— 包含所有 known toolsets。

  4. Registry filtering —— resolved tool name set 会被传递给 registry.get_definitions(),它会应用 check_fn filtering,并返回 OpenAI-format schemas。

  5. Dynamic schema patching —— filtering 之后,execute_codebrowser_navigate schemas 会被动态调整为只引用实际通过 filtering 的 tools(防止 model hallucination 不可用的 tools)。

带有 _tools 后缀的旧 toolset names(例如 web_toolsterminal_tools)会通过 _LEGACY_TOOLSET_MAP 映射到现代 tool names,以保持向后兼容。

在运行时,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", ...)

所有 tool execution 都在两个层级进行了错误处理包装:

  1. registry.dispatch() —— 捕获 handler 抛出的任何 exception,并以 JSON 形式返回 {"error": "Tool execution failed: ExceptionType: message"}

  2. handle_function_call() —— 使用第二层 try/except 包装整个 dispatch,返回 {"error": "Error executing tool_name: message"}

这确保 model 总是收到格式良好的 JSON string,而不是未处理的 exception。

有四个 tools 会在 registry dispatch 之前被拦截,因为它们需要 agent-level state(TodoStoreMemoryStore 等):

  1. todo —— planning / task tracking
  2. memory —— persistent memory writes
  3. session_search —— cross-session recall
  4. delegate_task —— 生成 subagent sessions

这些 tools 的 schemas 仍然会注册在 registry 中(用于 get_tool_definitions),但如果 dispatch 不知为何直接到达它们的 handlers,它们会返回一个 stub error。

当 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

terminal tool 集成了一个 dangerous-command approval system,定义在 tools/approval.py 中:

  1. Pattern detection —— DANGEROUS_PATTERNS 是一个 (regex, description) tuples 列表,覆盖破坏性操作:
  • Recursive deletes(rm -rf
  • Filesystem formatting(mkfsdd
  • SQL destructive operations(DROP TABLE、没有 WHEREDELETE FROM
  • System config overwrites(> /etc/
  • Service manipulation(systemctl stop
  • Remote code execution(curl | sh
  • Fork bombs、process kills 等
  1. Detection —— 在执行任何 terminal command 之前,detect_dangerous_command(command) 会检查所有 patterns。

  2. 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”)
  1. Session state —— approvals 按 session 跟踪。一旦你在某个 session 中批准了 “recursive delete”,后续 rm -rf commands 就不会再次提示。

  2. Permanent allowlist —— “allow permanently” 选项会将 pattern 写入 config.yamlcommand_allowlist,跨 sessions 持久化。

terminal system 支持多个 backends:

  • local
  • docker
  • ssh
  • singularity
  • modal
  • daytona
  • vercel_sandbox

它还支持:

  • per-task cwd overrides
  • background process management
  • PTY mode
  • dangerous commands 的 approval callbacks

Tool calls 可以按顺序执行,也可以并发执行,具体取决于 tool mix 和交互需求。

  • Toolsets Reference
  • Built-in Tools Reference
  • Agent Loop Internals
  • ACP Internals
-
0:000:00