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.
| Capacidade | Como |
|---|---|
| Adicionar ferramentas | ctx.register_tool(name=..., toolset=..., schema=..., handler=...) |
| Adicionar hooks | ctx.register_hook("post_tool_call", callback) |
| Adicionar comandos de barra | ctx.register_command(name, handler, description) — cria /name na CLI e em sessões de gateway |
| Despachar ferramentas a partir de comandos | ctx.dispatch_tool(name, args) — invoca uma ferramenta registrada com o contexto do agente pai já ligado |
| Adicionar comandos de CLI | ctx.register_cli_command(name, help, setup_fn, handler_fn) — cria hermes <plugin> <subcomando> |
| Injetar mensagens | ctx.inject_message(content, role="user", session_key=...) - veja Injetar mensagens |
| Distribuir arquivos de dados | Path(__file__).parent / "data" / "file.yaml" |
| Empacotar skills | ctx.register_skill(name, path) — nomeadas como plugin:skill, carregadas via skill_view("plugin:skill") |
| Exigir variáveis de ambiente | requires_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 imagem | ctx.register_image_gen_provider(provider) — veja Plugins de Provedor de Geração de Imagem |
| Registrar um backend de geração de vídeo | ctx.register_video_gen_provider(provider) — veja Plugins de Provedor de Geração de Vídeo |
| Registrar um motor de compressão de contexto | ctx.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 humana | ctx.register_approval_transport(name, present_fn) — veja Transportes de aprovação |
| Registrar um backend de memória | Herde 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 host | ctx.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
| Origem | Caminho | Caso 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) |
| pip | entry_points de hermes_agent.plugins | Pacotes distribuídos |
| Nix | services.hermes-agent.extraPlugins / extraPythonPackages | Instalaçõ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ório | O que guarda | Sistema de descoberta |
|---|---|---|
plugins/ (raiz) | Plugins gerais — ferramentas, hooks, comandos de barra, comandos de CLI, skills empacotadas | PluginManager (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 plugin | Como é 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 pip | Opt-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 descritiva | Hooks distribuídos |
|---|---|
| Diretiva/controle | pre_tool_call, pre_llm_call, pre_verify, pre_gateway_dispatch |
| Transformação | transform_tool_result, transform_terminal_output, transform_llm_output, pre_transcription |
| Observação | post_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:
| Tipo | O que faz | Seleção | Local |
|---|---|---|---|
| Plugins gerais | Adicionam ferramentas, hooks, comandos de barra e comandos de CLI | Múltipla escolha (ativa/desativa) | ~/.hermes/plugins/ |
| Provedores de memória | Substituem ou complementam a memória embutida | Escolha única (um ativo) | plugins/memory/ |
| Motores de contexto | Substituem o compressor de contexto embutido | Escolha única (um ativo) | plugins/context_engine/ |
| Provedores de modelo | Declaram um backend de inferência (OpenRouter, Anthropic, …) | Registro múltiplo, escolhido por --provider / config.yaml | plugins/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… | Como | Guia de criação |
|---|---|---|
| Uma ferramenta que o LLM pode chamar | Plugin 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 / gateway | Plugin 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 distribui | Plugin 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 contexto | Plugin 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.yaml | Hooks de Shell |
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
Links de instalação em um clique (Desktop)
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.
Capacidades de plugin e consentimento
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:
| Capacidade | Chave legada (plugins.entries.<id>.…) |
|---|---|
tools.override | allow_tool_override |
llm.provider_override | llm.allow_provider_override |
llm.model_override | llm.allow_model_override |
llm.agent_id_override | llm.allow_agent_id_override |
llm.profile_override | llm.allow_profile_override |
llm.task_override | llm.allow_task_override |
gateway.platform_actions | allow_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.
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.
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.
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:
| Veredicto | Comportamento |
|---|---|
| safe | Instala normalmente, sem saída extra |
| caution | Os achados são exibidos; você confirma Install anyway? [y/N] (ou passa --force) |
| dangerous | Bloqueado. 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á emplugins.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:
| Estado | Significado | Está em plugins.enabled? | Está em plugins.disabled? |
|---|---|---|---|
enabled | Carrega na próxima sessão | Sim | Não |
disabled | Desligado explicitamente — não carrega nem se também estiver em enabled | (irrelevante) | Sim |
not enabled | Descoberto, mas nunca aceito | Não | Nã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
Truese 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
Truequando 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
Falsequandosession_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
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.
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
resultcomo dado, não como instrução — não o alimente em decisões privilegiadas (aprovações, execução de comando) sem validar.
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.