Pular para o conteúdo principal

Documentação oficial desta página · v0.20.6

Plugins

O Hermes tem um sistema de plugins para adicionar ferramentas, hooks e integrações próprias sem modificar o código principal.

Se você quer criar uma ferramenta sob medida para você, para o seu time ou para um projeto só, este costuma ser o caminho certo. A página Adicionar Ferramentas do guia do desenvolvedor é sobre as ferramentas nativas do núcleo do Hermes, que vivem em tools/ e em toolsets.py.

→ Construa um Plugin do Hermes — guia passo a passo com um exemplo completo e funcional.

Visão geral rápida​

Coloque um diretório em ~/.hermes/plugins/ com um plugin.yaml e código Python:

~/.hermes/plugins/meu-plugin/
├── plugin.yaml # manifesto
├── __init__.py # register() — liga os schemas aos handlers
├── schemas.py # schemas das ferramentas (o que o LLM enxerga)
└── tools.py # handlers das ferramentas (o que roda quando são chamadas)

Inicie o Hermes — suas ferramentas aparecem ao lado das embutidas. O modelo já pode chamá-las na hora.

Exemplo mínimo funcional​

Este é um plugin completo que adiciona uma ferramenta hello_world e registra em log toda chamada de ferramenta por meio de um hook.

~/.hermes/plugins/hello-world/plugin.yaml

name: hello-world
version: "1.0"
description: Um plugin de exemplo mínimo

~/.hermes/plugins/hello-world/__init__.py

"""Plugin mínimo do Hermes — registra uma ferramenta e um hook."""

import json


def register(ctx):
# --- Ferramenta: hello_world ---
schema = {
"name": "hello_world",
"description": "Devolve uma saudação amigável para o nome informado.",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Nome a saudar",
}
},
"required": ["name"],
},
}

def handle_hello(params, **kwargs):
del kwargs
name = params.get("name", "World")
return json.dumps({"success": True, "greeting": f"Hello, {name}!"})

ctx.register_tool(
name="hello_world",
toolset="hello_world",
schema=schema,
handler=handle_hello,
)

# --- Hook: registra toda chamada de ferramenta ---
def on_tool_call(tool_name, params, result):
print(f"[hello-world] ferramenta chamada: {tool_name}")

ctx.register_hook("post_tool_call", on_tool_call)

Coloque os dois arquivos em ~/.hermes/plugins/hello-world/, reinicie o Hermes, e o modelo já consegue chamar hello_world. O hook imprime uma linha de log depois de cada invocação de ferramenta.

A descrição da ferramenta que o modelo vê pertence a schema["description"]. O valor opcional ctx.register_tool(description=...) é um metadado separado, do registro ToolEntry: quando você o omite, ele assume a descrição do schema, mas o Hermes não o copia de volta para um schema que não tenha description. Prefira definir o texto uma vez só, no schema. Se você fornecer os dois valores, mantenha-os sincronizados; o modelo vê o valor do schema.

Plugins locais do projeto, em ./.hermes/plugins/, ficam desativados por padrão. Ative-os apenas para repositórios confiáveis, definindo HERMES_ENABLE_PROJECT_PLUGINS=true antes de iniciar o Hermes.

O que os plugins conseguem fazer​

Toda API ctx.* listada abaixo está disponível dentro da função register(ctx) de um plugin.

CapacidadeComo
Adicionar ferramentasctx.register_tool(name=..., toolset=..., schema=..., handler=...)
Adicionar hooksctx.register_hook("post_tool_call", callback)
Adicionar comandos de barractx.register_command(name, handler, description) — cria /name na CLI e em sessões de gateway
Despachar ferramentas a partir de comandosctx.dispatch_tool(name, args) — invoca uma ferramenta registrada com o contexto do agente pai já ligado
Adicionar comandos de CLIctx.register_cli_command(name, help, setup_fn, handler_fn) — cria hermes <plugin> <subcomando>
Injetar mensagensctx.inject_message(content, role="user", session_key=...) - veja Injetar mensagens
Distribuir arquivos de dadosPath(__file__).parent / "data" / "file.yaml"
Empacotar skillsctx.register_skill(name, path) — nomeadas como plugin:skill, carregadas via skill_view("plugin:skill")
Exigir variáveis de ambienterequires_env: [API_KEY] no plugin.yaml — perguntado durante o hermes plugins install
Distribuir via pip[project.entry-points."hermes_agent.plugins"]
Registrar uma plataforma de gateway (Discord, Telegram, IRC, …)ctx.register_platform(name, label, adapter_factory, check_fn, ...) — veja Adicionar Adaptadores de Plataforma
Registrar um backend de geração de imagemctx.register_image_gen_provider(provider) — veja Plugins de Provedor de Geração de Imagem
Registrar um backend de geração de vídeoctx.register_video_gen_provider(provider) — veja Plugins de Provedor de Geração de Vídeo
Registrar um motor de compressão de contextoctx.register_context_engine(engine) — veja Plugins de Motor de Contexto
Registrar um backend de execução de terminal (sandbox na nuvem)ctx.register_terminal_environment_provider(provider) — veja Plugins de Ambiente de Terminal
Rotear pedidos de aprovação humanactx.register_approval_transport(name, present_fn) — veja Transportes de aprovação
Registrar um backend de memóriaHerde de MemoryProvider em plugins/memory/<nome>/__init__.py — veja Plugins de Provedor de Memória (usa um sistema de descoberta separado)
Fazer uma chamada de LLM pertencente ao hostctx.llm.complete(...) / ctx.llm.complete_structured(...) — pega emprestado o modelo ativo e a autenticação do usuário para uma completação avulsa, com validação opcional por schema JSON. Veja Acesso a LLM em Plugins
Chamar uma ferramenta MCP (com capacidade exigida)ctx.call_mcp(server, tool, arguments, timeout=30) — veja Chamar servidores MCP a partir de plugins
Registrar um backend de inferência (provedor de LLM)register_provider(ProviderProfile(...)) em plugins/model-providers/<nome>/__init__.py — veja Plugins de Provedor de Modelo (usa um sistema de descoberta separado)

Descoberta de plugins​

OrigemCaminhoCaso de uso
Empacotado<repo>/plugins/Vem junto com o Hermes — veja Plugins Embutidos
Usuário~/.hermes/plugins/Plugins pessoais
Projeto.hermes/plugins/Plugins específicos do projeto (exige HERMES_ENABLE_PROJECT_PLUGINS=true)
pipentry_points de hermes_agent.pluginsPacotes distribuídos
Nixservices.hermes-agent.extraPlugins / extraPythonPackagesInstalações declarativas no NixOS — veja Configuração com Nix

Origens posteriores sobrescrevem as anteriores em caso de colisão de nome, então um plugin de usuário com o mesmo nome de um plugin empacotado o substitui.

Subcategorias de plugin​

Dentro de cada origem, o Hermes também reconhece diretórios de subcategoria que encaminham plugins para sistemas de descoberta especializados:

SubdiretórioO que guardaSistema de descoberta
plugins/ (raiz)Plugins gerais — ferramentas, hooks, comandos de barra, comandos de CLI, skills empacotadasPluginManager (kind: standalone ou backend)
plugins/platforms/<nome>/Adaptadores de canal do gateway (ctx.register_platform())PluginManager (kind: platform, um nível mais fundo)
plugins/image_gen/<nome>/Backends de geração de imagem (ctx.register_image_gen_provider())PluginManager (kind: backend, um nível mais fundo)
plugins/memory/<nome>/Provedores de memória (herdam de MemoryProvider)Carregador próprio em plugins/memory/__init__.py (kind: exclusive — um ativo por vez)
plugins/context_engine/<nome>/Motores de compressão de contexto (ctx.register_context_engine())Carregador próprio em plugins/context_engine/__init__.py (um ativo por vez)
plugins/model-providers/<nome>/Perfis de provedor de LLM (register_provider(ProviderProfile(...)))Carregador próprio em providers/__init__.py (varrido preguiçosamente na primeira chamada a get_provider_profile())

Plugins de usuário em ~/.hermes/plugins/model-providers/<nome>/ e em ~/.hermes/plugins/memory/<nome>/ sobrescrevem plugins empacotados de mesmo nome — vence quem escreve por último em register_provider() / register_memory_provider(). Basta colocar um diretório ali para substituir o embutido, sem nenhuma edição no repositório.

Plugins são opt-in (com algumas exceções)​

Plugins gerais e backends instalados pelo usuário ficam desativados por padrão — a descoberta os encontra (então eles aparecem em hermes plugins e em /plugins), mas nada com hooks ou ferramentas carrega até que você adicione o nome do plugin a plugins.enabled em ~/.hermes/config.yaml. Isso impede que código de terceiros rode sem o seu consentimento explícito.

plugins:
enabled:
- meu-plugin-de-ferramenta
- disk-cleanup
disabled: # lista de negação opcional — sempre vence se um nome aparecer nas duas
- plugin-barulhento
# Opcional: limite de tempo de relógio (segundos) para os callbacks de hook de
# plugin em Python, executados no processo e com timeout (observadores de caminho
# quente + pre_tool_call). Padrão 30; use 0 para desativar; valores acima de 600
# são limitados. Callbacks de pre_tool_call que estouram o tempo falham fechado
# (bloqueiam a ferramenta). Hooks da thread chamadora, como subagent_stop, nunca
# são movidos para um worker com timeout.
# Hooks de shell mantêm o próprio timeout por entrada, sob a chave hooks: de topo.
hook_callback_timeout: 30

Três jeitos de alternar o estado:

hermes plugins # alternância interativa (espaço para marcar/desmarcar)
hermes plugins enable <nome> # adiciona à lista de permissão
hermes plugins disable <nome> # tira da lista de permissão + adiciona a disabled

Depois de hermes plugins install owner/repo, você recebe a pergunta Enable 'name' now? [y/N] — o padrão é não. Para instalações via script, pule a pergunta com --enable ou --no-enable.

Para uma instalação reproduzível, fixe um commit imutável completo (tags, branches e SHAs abreviados não são aceitos):

hermes plugins install owner/repo --ref 0123456789abcdef0123456789abcdef01234567

O Hermes faz checkout do commit em modo detached, confere se o HEAD bate exatamente com o SHA pedido, e registra no perfil atual a origem canônica, a revisão instalada e o estado de fixação. O hermes plugins update se recusa a mover um plugin fixado; escolha explicitamente um novo commit exato com hermes plugins install <origem> --force --ref <novo-commit>. Os metadados de instalação, locais ao perfil, não contêm valores de configuração, valores de ambiente, segredos nem concessões de capacidade.

O que a lista de permissão NÃO controla​

Algumas categorias de plugin passam por cima de plugins.enabled — elas fazem parte da superfície embutida do Hermes e quebrariam funcionalidades básicas se ficassem bloqueadas por padrão:

Tipo de pluginComo é ativado no lugar disso
Plugins de plataforma empacotados (IRC, Teams, etc., sob plugins/platforms/)Carregados automaticamente, para que todo canal de gateway distribuído esteja disponível. O canal em si liga por gateway.platforms.<name>.enabled no config.yaml.
Backends empacotados (provedores de geração de imagem sob plugins/image_gen/, etc.)Carregados automaticamente, para que o backend padrão "simplesmente funcione". A escolha acontece via <category>.provider no config.yaml (por exemplo, image_gen.provider: openai).
Provedores de memória (plugins/memory/)Todos descobertos; exatamente um fica ativo, escolhido por memory.provider no config.yaml.
Motores de contexto (plugins/context_engine/)Todos descobertos; um fica ativo, escolhido por context.engine no config.yaml.
Provedores de modelo (plugins/model-providers/)Todos os provedores empacotados sob plugins/model-providers/ são descobertos e registrados na primeira chamada a get_provider_profile(). O usuário escolhe um de cada vez via --provider ou config.yaml.
Plugins backend instalados via pipOpt-in por plugins.enabled (igual aos plugins gerais).
Plataformas instaladas pelo usuário (sob ~/.hermes/plugins/platforms/)Opt-in por plugins.enabled — adaptadores de gateway de terceiros precisam de consentimento explícito.

Resumindo: a infraestrutura empacotada, que "sempre funciona", carrega automaticamente; plugins gerais de terceiros são opt-in. A lista de permissão plugins.enabled é o portão específico para código arbitrário que o usuário joga em ~/.hermes/plugins/.

Transportes de aprovação​

Um transporte de aprovação muda onde uma pessoa vê e responde a um pedido de aprovação de ferramenta já existente do Hermes. Ele não decide se um comando precisa de aprovação e não é uma API de política de autorização.

def present(request):
# Entregue request.command e request.description à sua interface, aguarde a
# resposta humana autenticada e devolva uma decisão vinculada à requisição.
choice = send_to_my_ui_and_wait(request) # once/session/always/deny
return request.respond(choice)


def register(ctx):
ctx.register_approval_transport("my-ui", present)

present pode ser síncrona ou assíncrona. O Hermes a executa num worker limitado e impõe o approvals.timeout canônico, mesmo que o plugin não o faça. A requisição é imutável e contém o texto de exibição já redigido, a classe de apresentação do host (cli ou gateway), o timeout do host, as escolhas permitidas e um ID/digest opaco da requisição. Devolva o resultado de request.respond(choice); dicionários não vinculados e IDs/digests obsoletos ou alterados são rejeitados. Um plugin não pode devolver um escopo que o host não ofereceu (por exemplo, always numa requisição de uso único).

Só registrar não faz nada. Ativar o plugin e selecionar explicitamente o transporte dele são dois passos de consentimento separados:

plugins:
enabled: [my-approval-plugin]

security:
approval:
transport: my-ui
transport_fallback: deny # padrão

Exceções do transporte, timeouts, registros indisponíveis, escolhas inválidas e respostas obsoletas negam por padrão. Para mostrar de propósito o pedido na superfície comum de CLI/TUI/gateway/ACP quando o transporte escolhido falha, defina transport_fallback: builtin. Sem esse opt-in exato, o Hermes nunca materializa o pedido em outra superfície.

O Hermes continua dono dos bloqueios hardline, da proteção de sudo por stdin, das regras de negação do usuário, da vinculação de requisição, dos escopos permitidos, da persistência, dos hooks e da autorização final. Comandos hardline são bloqueados antes de qualquer callback de transporte. Nesta interface não existe, de propósito, política de aprovação de plugin, callback de auto-liberação nem política obrigatória de pre_tool_call. Uma futura capacidade de política de aprovação pode usar o modelo de consentimento de capacidades de plugin, mas escolher um transporte não a concede.

Migração para quem já usa​

Quando você atualiza para uma versão do Hermes com plugins opt-in (schema de configuração v21+), quaisquer plugins de usuário já instalados em ~/.hermes/plugins/ que ainda não estivessem em plugins.disabled são automaticamente herdados para plugins.enabled. Sua instalação atual continua funcionando. Plugins standalone empacotados NÃO são herdados — mesmo quem já usa precisa optar por eles explicitamente. (Plugins empacotados de plataforma/backend nunca precisaram desse tratamento, porque nunca foram bloqueados.)

Hooks disponíveis​

Os plugins podem registrar os 26 eventos de ciclo de vida atualmente aceitos por hermes_cli.plugins.VALID_HOOKS. O catálogo de Hooks de Evento é a referência canônica para o momento exato de disparo, o tratamento de retorno, os campos do payload e as observações de privacidade.

Categoria descritivaHooks distribuídos
Diretiva/controlepre_tool_call, pre_llm_call, pre_verify, pre_gateway_dispatch
Transformaçãotransform_tool_result, transform_terminal_output, transform_llm_output, pre_transcription
Observaçãopost_tool_call, post_llm_call, pre_api_request, post_api_request, api_request_error, on_stream_start, on_stream_delta, on_stream_end, on_interim_message, on_session_start, on_session_end, on_session_finalize, on_session_reset, on_skill_lifecycle, subagent_start, subagent_stop, pre_approval_request, post_approval_response, pre_command, kanban_task_claimed, kanban_task_completed, kanban_task_blocked

Essas categorias descrevem o comportamento atual, e não definem regras futuras de nomenclatura. O middleware de plugin continua sendo um registro/superfície separado.

Tipos de plugin​

O Hermes tem quatro tipos de plugin:

TipoO que fazSeleçãoLocal
Plugins geraisAdicionam ferramentas, hooks, comandos de barra e comandos de CLIMúltipla escolha (ativa/desativa)~/.hermes/plugins/
Provedores de memóriaSubstituem ou complementam a memória embutidaEscolha única (um ativo)plugins/memory/
Motores de contextoSubstituem o compressor de contexto embutidoEscolha única (um ativo)plugins/context_engine/
Provedores de modeloDeclaram um backend de inferência (OpenRouter, Anthropic, …)Registro múltiplo, escolhido por --provider / config.yamlplugins/model-providers/

Provedores de memória e motores de contexto são plugins de provedor — só um de cada tipo pode estar ativo por vez. Provedores de modelo também são plugins, mas muitos carregam ao mesmo tempo; o usuário escolhe um de cada vez via --provider ou config.yaml. Plugins gerais podem ser ativados em qualquer combinação.

Interfaces plugáveis — para onde ir em cada caso​

A tabela acima mostra as quatro categorias de plugin, mas dentro de "plugins gerais" o PluginContext expõe vários pontos de extensão distintos — e o Hermes também aceita extensões fora do sistema de plugins em Python (backends dirigidos por configuração, comandos com hooks de shell, servidores externos, etc.). Use esta tabela para achar a documentação certa para o que você quer construir:

Quer adicionar…ComoGuia de criação
Uma ferramenta que o LLM pode chamarPlugin Python — ctx.register_tool()Construa um Plugin do Hermes · Adicionar Ferramentas
Um hook de ciclo de vida (pré/pós LLM, início/fim de sessão, filtro de ferramentas)Plugin Python — ctx.register_hook()Referência de hooks · Construa um Plugin do Hermes
Um comando de barra para a CLI / gatewayPlugin Python — ctx.register_command()Construa um Plugin do Hermes · Estender a CLI
Um subcomando para hermes <coisa>Plugin Python — ctx.register_cli_command()Estender a CLI
Uma skill empacotada que o seu plugin distribuiPlugin Python — ctx.register_skill()Criar Skills
Um backend de inferência (provedor de LLM: compatível com OpenAI, Codex, Anthropic-Messages, Bedrock)Plugin de provedor — register_provider(ProviderProfile(...)) em plugins/model-providers/<nome>/Plugins de Provedor de Modelo · Adicionar Provedores
Um canal de gateway (Discord / Telegram / IRC / Teams / etc.)Plugin de plataforma — ctx.register_platform() em plugins/platforms/<nome>/Adicionar Adaptadores de Plataforma
Um backend de memória (Honcho, Mem0, Supermemory, …)Plugin de memória — herdar de MemoryProvider em plugins/memory/<nome>/Plugins de Provedor de Memória
Uma estratégia de compressão de contextoPlugin de motor de contexto — ctx.register_context_engine()Plugins de Motor de Contexto
Um backend de geração de imagem (DALL·E, SDXL, …)Plugin de backend — ctx.register_image_gen_provider()Plugins de Provedor de Geração de Imagem
Um backend de geração de vídeo (Veo, Kling, Pixverse, Grok-Imagine, Runway, …)Plugin de backend — ctx.register_video_gen_provider()Plugins de Provedor de Geração de Vídeo
Um backend de TTS (qualquer CLI — Piper, VoxCPM, Kokoro, xtts, scripts de clonagem de voz, …)Dirigido por configuração (recomendado) — declare sob tts.providers.<name> com type: command no config.yaml. OU plugin de backend em Python — ctx.register_tts_provider() para engines de SDK Python / streaming que precisam de mais do que um template de shell.Configuração de TTS · Guia de plugin Python
Um backend de STT (qualquer CLI — whisper.cpp, binário whisper próprio, CLI de ASR local)Dirigido por configuração (recomendado) — declare sob stt.providers.<name> com type: command no config.yaml, ou defina HERMES_LOCAL_STT_COMMAND para a saída de emergência legada de comando único. OU plugin de backend em Python — ctx.register_transcription_provider() para engines de SDK Python (OpenRouter, SenseAudio, Gemini-STT, etc.).Configuração de STT · Guia de plugin Python
Ferramentas externas via MCP (sistema de arquivos, GitHub, Linear, Notion, qualquer servidor MCP)Dirigido por configuração — declare mcp_servers.<name> com command: / url: no config.yaml. O Hermes descobre sozinho as ferramentas do servidor e as registra junto das embutidas.MCP
Outras fontes de skills (repositórios próprios no GitHub, índices privados de skills)CLI — hermes skills tap add <repo>Skills Hub · Publicar um tap próprio
Hooks de evento do gateway (disparam em gateway:startup, session:start, agent:end, command:*)Coloque HOOK.yaml + handler.py em ~/.hermes/hooks/<nome>/Hooks de Evento
Hooks de shell (rodam um comando de shell em eventos — notificações, logs de auditoria, alertas de desktop)Dirigido por configuração — declare sob hooks: no config.yamlHooks de Shell
observação

Nem tudo é plugin Python. Algumas superfícies de extensão usam de propósito comandos de shell dirigidos por configuração (TTS, STT, hooks de shell), para que qualquer CLI que você já tenha vire um plugin sem escrever Python. Outras são servidores externos (MCP), aos quais o agente se conecta e de onde registra ferramentas automaticamente. E algumas são diretórios de arrastar e soltar (hooks de gateway), com formato de manifesto próprio. Escolha a superfície certa para o estilo de integração que combina com o seu caso; cada guia de criação da tabela acima cobre placeholders, descoberta e exemplos.

Plugins declarativos no NixOS​

No NixOS, os plugins podem ser instalados de forma declarativa pelas opções do módulo — sem precisar de hermes plugins install. Veja o guia de Configuração com Nix para todos os detalhes.

services.hermes-agent = {
# Plugin em diretório (árvore de código com plugin.yaml)
extraPlugins = [ (pkgs.fetchFromGitHub { ... }) ];
# Plugin por entry point (pacote pip)
extraPythonPackages = [ (pkgs.python312Packages.buildPythonPackage { ... }) ];
# Ativar na configuração
settings.plugins.enabled = [ "my-plugin" ];
};

Plugins declarativos são ligados por symlink com o prefixo nix-managed- — eles convivem com plugins instalados manualmente e são removidos automaticamente quando saem da configuração do Nix.

Gerenciar plugins​

hermes plugins # interface interativa unificada
hermes plugins list # tabela: ativado / desativado / não ativado
hermes plugins search <termo> # busca no índice comunitário de plugins
hermes plugins install <nome> # instala pelo nome no índice (resolvido para repo @ ref fixada)
hermes plugins install user/repo # instala do Git, e então pergunta Enable? [y/N]
hermes plugins install user/repo --enable # instala E ativa (sem pergunta)
hermes plugins install user/repo --no-enable # instala mas deixa desativado (sem pergunta)
hermes plugins update my-plugin # puxa a última versão (edições locais vão para stash e voltam depois)
hermes plugins remove my-plugin # desinstala
hermes plugins enable my-plugin # adiciona à lista de permissão
hermes plugins disable my-plugin # tira da lista de permissão + adiciona a disabled
hermes plugins capabilities [my-plugin] # capacidades declaradas vs. concedidas

O Hermes Desktop registra o esquema de URL hermes://, então um site, um README ou uma mensagem de chat pode linkar direto para a instalação de um plugin:

hermes://plugin/install?repo=owner/repo # link principal de instalação
hermes://plugin/install?repo=owner/repo&enable=1 # ativa o plugin de agente depois de instalar
hermes://plugin/install?repo=owner/repo&force=1 # substitui uma instalação existente

Clicar em um deles abre o Hermes e mostra um diálogo de confirmação — o id do repositório, uma nota "antes de instalar" e links para navegar e clonar no GitHub — e então faz um clone raso do repositório para detectar o que ele distribui (um plugin de agente — Python no backend, um plugin de desktop — interface do app, ou ambos). Você escolhe os componentes por caixas de seleção e confirma. Nada é instalado até você confirmar; deep links nunca instalam sozinhos, e instalações de plugin de agente passam pela mesma varredura de segurança na instalação que o hermes plugins install.

Repositórios híbridos (metade agente + metade desktop no mesmo repo) usam um link só e um diálogo só. O mesmo modal é acessível sem link em Settings → Plugins → Install from Git. URLs legadas hermes://plugin-agent/… e hermes://plugin-desktop/… caem no mesmo diálogo. Em builds de desenvolvimento (npm run dev), o esquema é hermes-dev://.

Sites não precisam de SDK — uma âncora comum resolve:

<a href="hermes://plugin/install?repo=owner/repo&enable=1">Instalar no Hermes</a>

Servidores MCP têm o formato de link equivalente — veja link Add to Hermes.

Os plugins podem declarar no plugin.yaml as superfícies privilegiadas do host que querem usar:

name: my-plugin
capabilities:
- tools.override # substituir ferramentas embutidas
- llm.model_override # escolher o modelo das chamadas de LLM pertencentes ao host

Quando um plugin declara capacidades, o hermes plugins install (e o hermes plugins enable) mostra a lista com descrições de risco de uma linha e pergunta uma vez. Consentir grava a concessão em plugins.entries.<id>.granted_capabilities, junto de um hash de consentimento e de um carimbo de tempo. Recusar deixa o plugin ativado com aquelas capacidades desligadas — um plugin bem-comportado sonda com ctx.has_capability() e degrada com elegância.

Novo consentimento na atualização: se uma atualização de plugin declarar capacidades que você não concedeu, o hermes plugins update mostra as novidades e pergunta de novo. Capacidades novas ficam desligadas até você consentir — uma atualização de plugin nunca amplia o acesso dele em silêncio.

Sessões não interativas falham fechado: instalar ou atualizar sem um TTY conclui a instalação, mas as capacidades declaradas não são concedidas. Rode hermes plugins enable <id> de forma interativa para concedê-las depois.

Inspecione o estado a qualquer momento:

hermes plugins capabilities # todos os plugins, com capacidades declaradas/concedidas
hermes plugins capabilities my-plugin # um plugin, declaradas vs. concedidas

Os ids de capacidade mapeiam 1:1 para os antigos portões de configuração por funcionalidade, que continuam funcionando mas estão obsoletos em favor do fluxo de consentimento:

CapacidadeChave legada (plugins.entries.<id>.…)
tools.overrideallow_tool_override
llm.provider_overridellm.allow_provider_override
llm.model_overridellm.allow_model_override
llm.agent_id_overridellm.allow_agent_id_override
llm.profile_overridellm.allow_profile_override
llm.task_overridellm.allow_task_override
gateway.platform_actionsallow_platform_actions

Um portão está aberto quando ou a capacidade foi concedida ou a chave legada está definida — as configurações existentes continuam funcionando sem mudança.

Não é um sandbox

Capacidades são uma camada de consentimento e auditoria, não de isolamento. Plugins rodam como Python comum, dentro do processo: um plugin malicioso pode ignorar todos os portões daqui. Conceder uma capacidade é uma declaração de confiança no autor do plugin — não é uma auditoria de código, e o Hermes não revisou o código do plugin. Só instale plugins de fontes em que você confia.

Ações de plataforma​

ctx.platform_actions dá ao plugin um conjunto mínimo de verbos, controlado por capacidade, para agir nas plataformas de chat conectadas por meio do registro vivo de adaptadores do gateway — a alternativa sancionada a fazer monkeypatch num adaptador. Vem desligado por padrão: toda chamada recheca a capacidade gateway.platform_actions (chave legada plugins.entries.<id>.allow_platform_actions), e uma chamada sem concessão devolve um erro estruturado em vez de agir.

Verbos da v1 (ambos async, ambos devolvem um dict simples, e nenhum dos dois jamais levanta exceção dentro do despacho de hooks):

result = await ctx.platform_actions.add_reaction(
platform="telegram", chat_id="-100123", message_id="456", emoji="👍",
)
result = await ctx.platform_actions.set_thread_title(
platform="discord", chat_id="123", thread_id="456", title="Novo título",
)
if not result["ok"]:
print(result["error"], result.get("detail"))

Sucesso é {"ok": True, "action": <verbo>}. Falhas são {"ok": False, "error": <código>, "detail": <str>}, com códigos de erro estáveis: capability_not_granted, invalid_argument, gateway_unavailable, unknown_platform, adapter_not_registered, adapter_disconnected, unsupported_platform_action, action_failed. As ações validam que o adaptador de destino existe e está conectado antes de agir; um adaptador desconectado ou ausente vira um erro estruturado, nunca uma exceção.

Plataformas suportadas na v1: Telegram e Discord. O add_reaction do Telegram define a reação do bot (a Bot API substitui uma reação anterior do bot em vez de empilhar). Toda ação — permitida ou negada — é escrita no log com o id do plugin, o verbo, a plataforma e o desfecho.

Observação de segurança

Ações de plataforma são um poder de falar como o bot: um plugin com a concessão pode reagir e renomear threads em qualquer chat que o bot do gateway alcance, não só no chat que disparou o hook. Conceda gateway.platform_actions apenas a plugins em que você confia, e prefira plugins que documentem exatamente quais ações executam. O acesso cru a payloads e handles do SDK da plataforma não faz parte desta superfície, de propósito — pela correção de projeto da rodada 2 do #64176 ele exige uma capacidade própria (gateway.raw_events), com um rótulo de "sem garantia de estabilidade" e um projeto separado, e ainda não foi lançado.

Descobrir plugins da comunidade​

hermes plugins search <termo> busca no índice comunitário de plugins — um catálogo JSON estático e legível por máquina de plugins da comunidade. A correspondência é difusa, sobre nome, descrição e tags:

hermes plugins search telegram # busca difusa
hermes plugins search # navega pelo índice inteiro
hermes plugins search --capability platform # filtra por capacidade declarada
hermes plugins search media --json # saída legível por máquina
hermes plugins search --refresh # ignora o cache local de 24h

Depois de achar um plugin, instale-o pelo nome puro — o nome é resolvido pelo índice para o owner/repo dele mais o commit fixado no índice:

hermes plugins install hermes-media-studio

Se um nome casar com mais de uma entrada, os candidatos são listados e nada é instalado. Identificadores explícitos de owner/repo ou URLs Git nunca tocam o índice e continuam funcionando exatamente como antes. Um --ref <sha> explícito sempre vence a fixação do índice.

Como o índice é buscado. O índice fica numa URL canônica (https://raw.githubusercontent.com/NousResearch/hermes-plugin-index/main/index.json, que você pode trocar com hermes config set plugins.index_url <url>). As buscas ficam em cache em ~/.hermes/cache/plugin_index.json por 24 horas; quando o remoto está inacessível, o cache velho é usado, e quando não há cache nenhum entra uma cópia semente que vem junto com o Hermes — então a busca funciona totalmente offline.

Formato da entrada de índice. Cada entrada é um objeto JSON:

{
"name": "hermes-media-studio",
"description": "Generative media workspace plugin.",
"author": "NousResearch",
"tags": ["media", "image-gen"],
"repo": "NousResearch/hermes-media-studio",
"ref": "<SHA de commit com 40 caracteres>",
"subdir": null,
"homepage": "https://github.com/NousResearch/hermes-media-studio",
"capabilities": ["tools", "dashboard"],
"api_version": 1,
"added_at": "2026-08-12"
}

repo é o identificador owner/name do GitHub, ref fixa um SHA de commit imutável, e o subdir opcional dá suporte a monorepos. O arquivo semente que vem junto (hermes_cli/data/plugin_index.json no repositório) é a referência de formato.

Submeter um plugin. O índice é mantido como um arquivo JSON simples — abra um pull request no repositório hermes-plugin-index adicionando sua entrada (nome, descrição, autor, tags, owner/repo e um SHA de commit fixado). A revisão cobre apenas os metadados da entrada.

Indexado ≠ auditado

Estar no índice comunitário significa que os metadados da entrada foram revisados — não é uma auditoria de código. Instalar continua passando pelo fluxo normal de consentimento/revisão (plugins instalam desativados por padrão, ativar é um passo explícito, e direitos de sobrescrita de ferramenta exigem uma concessão à parte). Revise o código-fonte de um plugin antes de ativá-lo.

Pacotes de plugins​

Um pacote de plugins é um arquivo YAML declarativo e compartilhável (hermes-pack.yaml) que fixa um conjunto de plugins — como compartilhar um modpack. Instalar um pacote se desdobra em instalações fixadas comuns; nada de novo existe em tempo de execução.

name: voice-assistant-pack
description: STT + streaming TTS + approval relay
author: hyper
version: 1.0.0
plugins:
- name: hermes-media-studio # nome puro do índice comunitário…
ref: e8d59971d2b7901405b39dac7b03bdd616272d0d
- repo: owner/approval-relay # …ou owner/repo explícito (ou URL git)
ref: 8f3c2d1a9b4e5f6071829304a5b6c7d8e9f00112
subdir: plugins/relay # caminho opcional em monorepo
config: # opcional, só sementes não secretas
hermes-media-studio:
default_model: flux-3
skills: [] # apenas lista declarada (ainda não instala sozinho)
hermes plugins pack show ./hermes-pack.yaml # revisão em modo de teste
hermes plugins pack install ./hermes-pack.yaml # revisar → confirmar → instalar
hermes plugins pack export > hermes-pack.yaml # tira um retrato da instalação atual
hermes plugins pack export --enabled-only # só o que está em plugins.enabled

Postura de cadeia de suprimentos. O ref de cada entrada precisa ser um SHA de commit exato, de 40 caracteres — tags e nomes de branch são rejeitados com um erro que nomeia a entrada, a mesma regra do índice comunitário. Instalações de pacote seguem exatamente o mesmo caminho de instalação fixada que o hermes plugins install --ref <sha> e registram a mesma procedência em plugins/.install-metadata.json, então duas instalações do mesmo pacote resolvem de forma idêntica. Os pacotes se apoiam nos campos do manifesto v2 (manifest_version, api_version, requires_plugins) — o manifesto de cada plugin continua sendo validado pelo caminho normal de instalação.

O consentimento nunca é concedido em bloco. O pack install mostra uma tela de revisão obrigatória (cada plugin, sua origem, a ref fixada e as capacidades que ele declara), e então pede uma confirmação para o conteúdo do pacote. Depois disso, as capacidades declaradas por cada plugin passam pelo pedido padrão de consentimento de capacidade, plugin a plugin — idêntico a um hermes plugins install avulso. Não existe --yes, e sessões não interativas não conseguem instalar pacotes.

Segredos nunca viajam em pacotes. As sementes de config: se limitam a chaves não secretas de plugins.entries.<id> — nomes de chave com cara de segredo (*token*, *key*, *password*, …), concessões de capacidade e os portões de confiança obsoletos allow_* são rejeitados na instalação e retirados na exportação. Plugins que precisam de segredos os declaram no próprio requires_env, que pergunta durante a instalação, como de costume. Valores já existentes do usuário em plugins.entries.<id> sempre vencem as sementes do pacote.

Falha parcial. Cada plugin instala de forma independente; falhas são reportadas plugin a plugin, o resto continua, e o comando sai com código diferente de zero se algum plugin falhou.

Ressalvas da exportação. O pack export só inclui plugins com procedência Git conhecida (instalados via hermes plugins install). Plugins apenas locais aparecem como comentários de aviso no YAML gerado, não como entradas instaláveis.

A lista skills: é lida e exibida na hora da instalação, mas ainda não é instalada automaticamente — instale essas skills manualmente por enquanto (hermes skills). Ligar ids do hub de skills à instalação de pacotes é uma continuação já documentada.

Varredura de segurança na instalação​

Todo hermes plugins install e hermes plugins update roda uma varredura estática de segurança sobre a árvore do plugin antes de ativá-lo (inspirada na varredura de segurança de skills e plugins do Claude Cowork). O scanner reaproveita o mesmo motor de padrões de ameaça da proteção do Skills Hub — exfiltração de repositórios de credenciais, shells reversos, comandos destrutivos, mecanismos de persistência, execução ofuscada e injeção de prompt em arquivos de documentação — com isenções conscientes do mundo dos plugins: um plugin de provedor lendo a própria chave de API do ambiente (o padrão documentado do requires_env) não é sinalizado.

Três veredictos, equivalentes ao pass/warn/fail do Cowork:

VeredictoComportamento
safeInstala normalmente, sem saída extra
cautionOs achados são exibidos; você confirma Install anyway? [y/N] (ou passa --force)
dangerousBloqueado. O --force não passa por cima

No hermes plugins update, um veredicto dangerous sobre a árvore atualizada desativa o plugin até você revisar os achados e reativá-lo.

A varredura vem ligada por padrão; desative-a no config.yaml:

plugins:
scan_on_install: false

Interface interativa​

Rodar hermes plugins sem argumentos abre uma tela interativa composta:

Plugins
↑↓ navegar ESPAÇO alternar ENTER configurar/confirmar ESC concluir

Plugins Gerais
→ [✓] my-tool-plugin — Ferramenta de busca personalizada
[ ] webhook-notifier — Hooks de evento
[ ] disk-cleanup — Limpeza automática de arquivos efêmeros [bundled]

Plugins de Provedor
Provedor de Memória ▸ honcho
Motor de Contexto ▸ compressor
  • Seção Plugins Gerais — caixas de seleção, alterne com ESPAÇO. Marcado = está em plugins.enabled, desmarcado = está em plugins.disabled (desligado explicitamente).
  • Seção Plugins de Provedor — mostra a escolha atual. Aperte ENTER para entrar num seletor de opção única, onde você escolhe um provedor ativo.
  • Plugins empacotados aparecem na mesma lista, com a etiqueta [bundled].

As escolhas de plugin de provedor são gravadas no config.yaml:

memory:
provider: "honcho" # string vazia = só o embutido

context:
engine: "compressor" # compressor embutido padrão

Ativado vs. desativado vs. nenhum dos dois​

Os plugins ficam em um de três estados:

EstadoSignificadoEstá em plugins.enabled?Está em plugins.disabled?
enabledCarrega na próxima sessãoSimNão
disabledDesligado explicitamente — não carrega nem se também estiver em enabled(irrelevante)Sim
not enabledDescoberto, mas nunca aceitoNãoNão

O padrão para um plugin recém-instalado ou empacotado é not enabled. O hermes plugins list mostra os três estados de forma distinta, para você diferenciar o que foi desligado de propósito do que só está esperando ser ativado.

Numa sessão em execução, /plugins mostra quais plugins estão carregados no momento.

Injetar mensagens​

Os plugins podem injetar mensagens numa conversa da CLI ou numa sessão de gateway conhecida, usando ctx.inject_message():

# Conversa ativa na CLI
ctx.inject_message("Chegaram dados novos do webhook", role="user")

# Conversa de gateway existente
ctx.inject_message(
"Chegaram dados novos do webhook",
role="user",
session_key="agent:main:telegram:dm:123456789",
)

Assinatura: ctx.inject_message(content: str, role: str = "user", *, session_key: str | None = None) -> bool

No modo CLI:

  • Se o agente está ocioso (esperando entrada do usuário), a mensagem entra na fila como a próxima entrada e inicia um turno novo.
  • Se o agente está no meio de um turno (rodando), a mensagem interrompe a operação atual — igual a um usuário digitando uma mensagem nova e apertando Enter.
  • Para papéis diferentes de "user", o conteúdo ganha o prefixo [role] (por exemplo, [system] ...).
  • Devolve True se a mensagem entrou na fila com sucesso.

No modo gateway:

  • session_key é obrigatório e precisa identificar uma sessão de gateway existente. Ele é a chave estável de roteamento, não o ID de sessão da CLI.
  • O Hermes reaproveita a plataforma, o chat, a thread, o perfil e o histórico de conversa guardados naquela sessão. Os plugins não conseguem fornecer uma rota de chat nova por esta API.
  • O Hermes recheca a rota guardada contra as regras de autorização atuais do gateway antes de despachar.
  • Rotas que dependiam apenas de uma decisão de autorização tomada no adaptador ou mais acima são rejeitadas, a menos que o Hermes consiga revalidá-las a partir das listas de permissão atuais do núcleo, do pareamento ou de uma configuração explícita de liberar tudo.
  • O texto injetado é sempre entrada conversacional. Ele não pode invocar comandos de barra, aprovar ferramentas nem resolver pedidos pendentes de confirmação e de esclarecimento.
  • A rota e a conversa ficam fixadas enquanto o despacho está pendente. O Hermes descarta a requisição se a recuperação de tópico mudar a rota ou se a sessão rotacionar antes de o tratamento começar.
  • A requisição entra no caminho normal de mensagens do adaptador de plataforma. Sessões ativas usam a fila de sessão ocupada já existente, em vez de iniciar um turno concorrente.
  • Devolve True quando o gateway vivo aceita a requisição para despacho assíncrono. Isso não confirma que o turno do agente ou a entrega na plataforma terminaram.
  • Devolve False quando session_key é omitido, quando a permissão não foi concedida, ou quando nenhum gateway vivo pode aceitar a requisição. Chaves de sessão desconhecidas ou não roteáveis descobertas depois da aceitação assíncrona são escritas no log do gateway.

Isso permite que plugins como visualizadores de controle remoto, pontes de mensagens ou receptores de webhook alimentem a conversa com mensagens vindas de fontes externas.

A injeção no gateway pode enviar uma resposta do agente para uma plataforma de mensagens externa. Ela vem desativada por padrão para todo plugin. Conceda por plugin no config.yaml:

plugins:
entries:
my-plugin:
allow_gateway_injection: true
aviso

Só conceda injeção no gateway a plugins em que você confia. O Hermes verifica essa permissão de API do host e a restringe a rotas de sessão já existentes, mas plugins Python rodam dentro do processo e essa configuração não é um sandbox.

observação

Esta API de plugin não expõe endpoint HTTP público nem comando de CLI para processos externos. O plugin já precisa conhecer o session_key do gateway de destino, por exemplo a partir da própria configuração confiável ou de estado de sessão que ele guardou antes.

Chamar servidores MCP a partir de plugins​

ctx.call_mcp() deixa um plugin chamar uma ferramenta num dos servidores MCP configurados pelo usuário — de forma síncrona, a partir de qualquer hook ou handler de ferramenta — passando pelo cliente MCP nativo que o Hermes já tem (mesmas conexões, mesmos portões de nível de confiança, mesmo disjuntor e mesma lógica de reconexão das ferramentas MCP invocadas pelo modelo; nunca um cliente paralelo).

result = ctx.call_mcp(
"knowledge_rag", # nome do servidor em mcp.servers
"query_knowledge", # ferramenta naquele servidor
{"query": "runbook de deploy"},
timeout=30, # segundos; limitado a 1–600
)
if result["ok"]:
print(result["result"])
else:
print("erro de MCP:", result["error"])

Assinatura: ctx.call_mcp(server: str, tool: str, arguments: dict | None = None, timeout: float = 30) -> dict

Devolve um envelope estável: {"ok": True, "result": ...} (mais structuredContent, quando o servidor fornece) ou {"ok": False, "error": "..."}. Resultados acima de cerca de 64 KB são truncados e marcados com "truncated": True.

Segurança: desligado por padrão, lista de permissão por servidor​

Um plugin não tem acesso a MCP por padrão. O operador precisa conceder cada servidor explicitamente, no config.yaml:

plugins:
entries:
my-plugin:
mcp_allowlist: ["knowledge_rag", "github"]
  • Chamar um servidor fora da lista levanta PermissionError, nomeando a chave de configuração exata a definir.
  • A concessão é por servidor e por plugin — nunca autoridade ambiente sobre todos os servidores configurados, e curingas "*" não são respeitados.
  • Toda chamada tem um timeout imposto (padrão de 30 s), para que um servidor MCP travado não pare o hook ou o pipeline de ferramenta que o invocou.
  • Servidores MCP devolvem conteúdo não confiável. Trate result como dado, não como instrução — não o alimente em decisões privilegiadas (aprovações, execução de comando) sem validar.
aviso

Conceder mcp_allowlist dá ao plugin o mesmo acesso àquele servidor MCP que o modelo tem — incluindo qualquer ferramenta de escrita que o servidor exponha (sujeita aos portões de nível de trust do servidor). Conceda apenas os servidores de que o plugin realmente precisa.

Veja o guia completo para contratos de handler, formato de schema, comportamento de hooks, tratamento de erros e erros comuns.