Documentação oficial desta página · v0.20.6
Runtime de Ferramentas
As ferramentas do Hermes são funções que se registram sozinhas, agrupadas em conjuntos de ferramentas e executadas por um sistema central de registro e despacho.
Arquivos principais:
tools/registry.pymodel_tools.pytoolsets.pytools/terminal_tool.pytools/environments/*
Modelo de registro de ferramentas
Cada módulo de ferramenta chama registry.register(...) no momento da importação.
O model_tools.py é responsável por importar/descobrir os módulos de ferramenta e montar a lista de esquemas usada pelo modelo.
Como o registry.register() funciona
Todo arquivo de ferramenta em tools/ chama registry.register() no nível do módulo para se declarar. A assinatura da função é:
registry.register(
name="terminal", # Nome único da ferramenta (usado nos esquemas da API)
toolset="terminal", # Conjunto de ferramentas ao qual ela pertence
schema={...}, # Esquema visível ao modelo (descrição, parâmetros)
handler=handle_terminal, # A função executada quando a ferramenta é chamada
check_fn=check_terminal, # Opcional: devolve True/False de disponibilidade
requires_env=["SOME_VAR"], # Opcional: variáveis de ambiente necessárias (para exibir na interface)
is_async=False, # Se o handler é uma corrotina assíncrona
description="Run commands", # Opcional: metadado de registro do ToolEntry
emoji="💻", # Emoji para o spinner/indicador de progresso
)
Cada chamada cria um ToolEntry guardado no dicionário singleton ToolRegistry._tools, indexado pelo nome da ferramenta. Um registro que sobrescreveria uma ferramenta existente de um conjunto diferente é rejeitado (com log de erro), a menos que quem chamou passe override=True; sobrescritas de ferramentas embutidas feitas por plugin exigem, além disso, que o operador opte por elas com plugins.entries.<plugin_id>.allow_tool_override: true no config.yaml.
O schema["description"] é a descrição oficial que o modelo enxerga. O argumento separado description= preenche o ToolEntry.description; quando ele é omitido, o metadado do registro recai na descrição do esquema. O get_definitions() monta a definição de função no formato OpenAI a partir de entry.schema e não copia o entry.description para um esquema que não tenha description. Portanto, o description= sozinho não descreve a ferramenta para o modelo, e quando os dois valores divergem o modelo vê o valor do esquema. Prefira definir a descrição uma vez só, no esquema, a menos que algum consumidor do registro precise de metadados diferentes de propósito.
Descoberta: discover_builtin_tools()
Quando o model_tools.py é importado, ele chama discover_builtin_tools() de tools/registry.py. Essa função varre todo arquivo tools/*.py com análise de AST para achar os módulos que contêm chamadas de registry.register() no nível superior e então os importa:
# tools/registry.py (simplificado)
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): # checagem de AST por registry.register() no nível superior
importlib.import_module(f"tools.{path.stem}")
Essa descoberta automática significa que arquivos novos de ferramenta entram sozinhos — não existe lista manual para manter. A checagem de AST só casa com chamadas de registry.register() no nível superior (não com chamadas dentro de funções), então módulos auxiliares em tools/ não são importados.
Cada importação dispara as chamadas de registry.register() do módulo. Erros em ferramentas opcionais (por exemplo, falta do fal_client para geração de imagem) são capturados e registrados em log — eles não impedem o carregamento das outras ferramentas.
Depois da descoberta das ferramentas centrais, as ferramentas de MCP e de plugin também são descobertas:
- Ferramentas MCP — o
tools.mcp_tool.discover_mcp_tools()lê a configuração dos servidores MCP e registra as ferramentas de servidores externos. - Ferramentas de plugin — o
hermes_cli.plugins.discover_plugins()carrega plugins de usuário, de projeto e do pip que podem registrar ferramentas adicionais.
Checagem de disponibilidade da ferramenta (check_fn)
Cada ferramenta pode, opcionalmente, fornecer um check_fn — um chamável que devolve True quando a ferramenta está disponível e False caso contrário. Checagens típicas incluem:
- Chave de API presente — por exemplo,
lambda: bool(os.environ.get("SERP_API_KEY"))para a busca na web - Serviço em execução — por exemplo, verificar se o servidor Honcho está configurado
- Binário instalado — por exemplo, confirmar que o
playwrightestá disponível para as ferramentas de navegador
Quando o registry.get_definitions() monta a lista de esquemas para o modelo, ele roda o check_fn() de cada ferramenta:
# Simplificado de registry.py
if entry.check_fn:
try:
available = bool(entry.check_fn())
except Exception:
available = False # Exceções = indisponível
if not available:
continue # Pula esta ferramenta por completo
Comportamentos importantes:
- Os resultados da checagem são cacheados por chamada — se várias ferramentas compartilham o mesmo
check_fn, ele roda uma vez só. - Exceções no
check_fn()são tratadas como "indisponível" (à prova de falha). - O método
is_toolset_available()verifica se ocheck_fnde um conjunto de ferramentas passa; ele é usado na exibição da interface e na resolução de conjuntos.
Resolução de conjuntos de ferramentas
Conjuntos de ferramentas são pacotes nomeados de ferramentas. O Hermes os resolve por:
- listas explícitas de conjuntos habilitados/desabilitados
- presets de plataforma (
hermes-cli,hermes-telegrametc.) - conjuntos dinâmicos de MCP
- conjuntos curados de propósito específico, como o
hermes-acp
Como o get_tool_definitions() filtra as ferramentas
O ponto de entrada principal é o model_tools.get_tool_definitions(enabled_toolsets, disabled_toolsets, quiet_mode):
-
Se
enabled_toolsetsfor informado — só entram as ferramentas desses conjuntos. Cada nome de conjunto é resolvido peloresolve_toolset(), que expande conjuntos compostos em nomes individuais de ferramenta. -
Se
disabled_toolsetsfor informado — comece com TODOS os conjuntos e subtraia os desabilitados. -
Se nenhum dos dois — inclua todos os conjuntos conhecidos.
-
Filtragem no registro — o conjunto resolvido de nomes de ferramenta é passado para o
registry.get_definitions(), que aplica a filtragem porcheck_fne devolve os esquemas no formato OpenAI. -
Ajuste dinâmico de esquema — depois da filtragem, os esquemas de
execute_codeebrowser_navigatesão ajustados dinamicamente para só referenciar ferramentas que de fato passaram pelo filtro (evita que o modelo alucine ferramentas indisponíveis).
Nomes antigos de conjuntos
Nomes antigos de conjuntos, com sufixo _tools (por exemplo, web_tools, terminal_tools), são mapeados para os nomes modernos pelo _LEGACY_TOOLSET_MAP, por compatibilidade retroativa.
Despacho
Em tempo de execução, as ferramentas são despachadas pelo registro central, com exceções no laço do agente para algumas ferramentas de nível de agente, como o tratamento de memória, de todo e de busca em sessões.
Fluxo de despacho: tool_call do modelo → execução do handler
Quando o modelo devolve um tool_call, o fluxo é:
Resposta do modelo com tool_call
↓
laço do agente em run_agent.py
↓
model_tools.handle_function_call(name, args, task_id, user_task)
↓
[Ferramenta do laço do agente?] → tratada direto pelo laço do agente (todo, memory, session_search, delegate_task)
↓
[Pré-hook de plugin] → invoke_hook("pre_tool_call", ...)
↓
registry.dispatch(name, args, **kwargs)
↓
Busca o ToolEntry pelo nome
↓
[Handler assíncrono?] → ponte via _run_async()
[Handler síncrono?] → chamada direta
↓
Devolve a string de resultado (ou um erro em JSON)
↓
[Pós-hook de plugin] → invoke_hook("post_tool_call", ...)
Encapsulamento de erros
Toda execução de ferramenta é envolvida em tratamento de erro em dois níveis:
-
registry.dispatch()— captura qualquer exceção do handler e devolve{"error": "Tool execution failed: ExceptionType: message"}em JSON. -
handle_function_call()— envolve o despacho inteiro num segundo try/except que devolve{"error": "Error executing tool_name: message"}.
Isso garante que o modelo sempre receba uma string JSON bem formada, nunca uma exceção não tratada.
Ferramentas do laço do agente
Quatro ferramentas são interceptadas antes do despacho pelo registro, porque precisam de estado de nível de agente (TodoStore, MemoryStore etc.):
todo— planejamento e acompanhamento de tarefasmemory— escritas em memória persistentesession_search— recuperação entre sessõesdelegate_task— abre sessões de subagente
Os esquemas dessas ferramentas continuam registrados no registro (para o get_tool_definitions), mas os handlers devolvem um erro de esqueleto se o despacho de alguma forma chegar até eles diretamente.
Ponte assíncrona
Quando o handler de uma ferramenta é assíncrono, o _run_async() faz a ponte com o caminho síncrono de despacho:
- Caminho da CLI (sem laço em execução) — usa um laço de eventos persistente para manter vivos os clientes assíncronos em cache
- Caminho do gateway (laço em execução) — sobe uma thread descartável com
asyncio.run() - Threads de trabalho (ferramentas em paralelo) — usa laços persistentes por thread, guardados em armazenamento thread-local
O fluxo de aprovação DANGEROUS_PATTERNS
A ferramenta de terminal integra um sistema de aprovação de comandos perigosos definido em tools/approval.py:
-
Detecção por padrão — o
DANGEROUS_PATTERNSé uma lista de tuplas(regex, descrição)que cobrem operações destrutivas:- Remoções recursivas (
rm -rf) - Formatação de sistema de arquivos (
mkfs,dd) - Operações SQL destrutivas (
DROP TABLE,DELETE FROMsemWHERE) - Sobrescrita de configuração do sistema (
> /etc/) - Manipulação de serviços (
systemctl stop) - Execução remota de código (
curl | sh) - Fork bombs, encerramento de processos etc.
- Remoções recursivas (
-
Detecção — antes de executar qualquer comando de terminal, o
detect_dangerous_command(command)confere todos os padrões. -
Pedido de aprovação — se houver correspondência:
- Modo CLI — um prompt interativo pede que você aprove, negue ou permita de forma permanente
- Modo gateway — um callback assíncrono de aprovação manda o pedido para a plataforma de mensagens
- Aprovação inteligente — opcionalmente, um LLM auxiliar pode aprovar automaticamente comandos de baixo risco que casam com os padrões (por exemplo,
rm -rf node_modules/é seguro, mas casa com "remoção recursiva")
-
Estado da sessão — as aprovações são rastreadas por sessão. Depois que você aprova "remoção recursiva" numa sessão, os comandos
rm -rfseguintes não pedem de novo. -
Lista de permissão permanente — a opção "permitir permanentemente" escreve o padrão no
command_allowlistdoconfig.yaml, o que faz a permissão sobreviver entre sessões.
Ambientes de terminal/execução
O sistema de terminal aceita vários backends:
- local
- docker
- ssh
- singularity
- modal
- daytona
- vercel_sandbox
Ele também aceita:
- sobrescrita de cwd por tarefa
- gerenciamento de processos em segundo plano
- modo PTY
- callbacks de aprovação para comandos perigosos
Concorrência
As chamadas de ferramenta podem rodar em sequência ou ao mesmo tempo, dependendo da mistura de ferramentas e das exigências de interação.
Documentos relacionados
- Referência de conjuntos de ferramentas
- Referência de ferramentas embutidas
- Laço do agente por dentro
- ACP por dentro