Documentação oficial desta página · v0.20.6
Execução de código
A ferramenta execute_code deixa o agente escrever scripts Python que chamam as ferramentas do
Hermes por programa, comprimindo fluxos de vários passos num turno só do modelo. O script roda num
processo filho, na máquina do agente, conversando com o Hermes por RPC sobre um socket de domínio
Unix.
Como funciona
- O agente escreve um script Python, usando
from hermes_tools import ... - O Hermes gera um módulo
hermes_tools.pycom as funções de RPC - O Hermes abre um socket de domínio Unix e sobe uma thread ouvinte de RPC
- O script roda num processo filho — as chamadas de ferramenta viajam pelo socket até o Hermes
- Só a saída de
print()do script volta ao modelo; os resultados intermediários nunca entram na janela de contexto
# O agente escreve scripts assim:
from hermes_tools import web_search, web_extract
results = web_search("Python 3.13 features", limit=5)
for r in results["data"]["web"]:
content = web_extract([r["url"]])
# ... filtra e processa ...
print(summary)
Ferramentas disponíveis dentro dos scripts: web_search, web_extract, read_file,
write_file, search_files, patch e terminal (só em primeiro plano).
Quando o agente usa isso
O agente recorre ao execute_code quando há:
- 3 ou mais chamadas de ferramenta, com lógica de processamento entre elas
- filtragem de dados em massa, ou desvios condicionais
- laços sobre resultados
O ganho principal: os resultados intermediários nunca entram no contexto — só a saída final do
print() volta, o que reduz muito o gasto de tokens.
Exemplos práticos
Pipeline de processamento de dados
from hermes_tools import search_files, read_file
import json
# Acha os arquivos de configuração e extrai as opções de banco
matches = search_files("database", path=".", file_glob="*.yaml", limit=20)
configs = []
for match in matches.get("matches", []):
content = read_file(match["path"])
configs.append({"file": match["path"], "preview": content["content"][:200]})
print(json.dumps(configs, indent=2))
Pesquisa web em vários passos
from hermes_tools import web_search, web_extract
import json
# Busca, extrai e resume, tudo num turno
results = web_search("Rust async runtime comparison 2025", limit=5)
summaries = []
for r in results["data"]["web"]:
page = web_extract([r["url"]])
for p in page.get("results", []):
if p.get("content"):
summaries.append({
"title": r["title"],
"url": r["url"],
"excerpt": p["content"][:500]
})
print(json.dumps(summaries, indent=2))
Refatoração em massa
from hermes_tools import search_files, read_file, patch
# Acha os arquivos Python com a API antiga e corrige
matches = search_files("old_api_call", path="src/", file_glob="*.py")
fixed = 0
for match in matches.get("matches", []):
result = patch(
path=match["path"],
old_string="old_api_call(",
new_string="new_api_call(",
replace_all=True
)
if "error" not in str(result):
fixed += 1
print(f"Fixed {fixed} files out of {len(matches.get('matches', []))} matches")
Compilar e testar
from hermes_tools import terminal, read_file
import json
# Roda os testes, interpreta o resultado e relata
result = terminal("cd /project && python -m pytest --tb=short -q 2>&1", timeout=120)
output = result.get("output", "")
passed = output.count(" passed")
failed = output.count(" failed")
errors = output.count(" error")
report = {
"passed": passed,
"failed": failed,
"errors": errors,
"exit_code": result.get("exit_code", -1),
"summary": output[-500:] if len(output) > 500 else output
}
print(json.dumps(report, indent=2))
Modo de execução
O execute_code tem dois modos, controlados por code_execution.mode, no ~/.hermes/config.yaml:
| Modo | Diretório de trabalho | Interpretador Python |
|---|---|---|
project (padrão) | O diretório da sessão (o mesmo do terminal()) | O Python do VIRTUAL_ENV ou CONDA_PREFIX ativo, caindo no do próprio Hermes |
strict | Um diretório temporário, isolado do projeto | sys.executable (o Python do Hermes) |
Quando deixar em project: você quer que import pandas, from meu_projeto import foo e
caminhos relativos como open(".env") funcionem igual ao que funcionam no terminal(). Quase sempre
é o que se quer.
Quando mudar para strict: você precisa de reprodutibilidade máxima — quer o mesmo interpretador
em toda sessão, independentemente do ambiente virtual que a pessoa ativou, e quer os scripts em
quarentena, longe da árvore do projeto (sem risco de ler arquivo do projeto por caminho relativo).
# ~/.hermes/config.yaml
code_execution:
mode: project # ou "strict"
Recuo no modo project: se o VIRTUAL_ENV ou o CONDA_PREFIX não existe, está quebrado, ou aponta
para um Python anterior ao 3.8, o resolvedor cai limpo no sys.executable — ele nunca deixa o agente
sem interpretador.
As garantias críticas de segurança são idênticas nos dois modos:
- limpeza do ambiente (chaves de API, tokens e credenciais removidos)
- lista de ferramentas permitidas (scripts não chamam
execute_coderecursivamente, nemdelegate_task, nem ferramentas MCP) - limites de recurso (tempo, teto de saída, teto de chamadas de ferramenta)
Trocar o modo muda onde os scripts rodam e qual interpretador os roda, não que credenciais eles enxergam nem que ferramentas podem chamar.
Limites de recurso
| Recurso | Limite | Observações |
|---|---|---|
| Tempo | 5 minutos (300s) | O script recebe SIGTERM e, depois de 5 segundos, SIGKILL |
| Saída padrão | 50 KB | Cortada, com o aviso [output truncated at 50KB] |
| Saída de erro | 10 KB | Incluída na saída quando a execução falha, para depurar |
| Chamadas de ferramenta | 50 por execução | Erro devolvido ao atingir o limite |
Tudo é configurável:
# em ~/.hermes/config.yaml
code_execution:
mode: project # project (padrão) | strict
timeout: 300 # segundos por script (padrão: 300)
max_tool_calls: 50 # chamadas por execução (padrão: 50)
Como as chamadas funcionam dentro dos scripts
Quando o seu script chama algo como web_search("consulta"):
- A chamada é serializada em JSON e mandada pelo socket ao processo pai
- O pai despacha pelo
handle_function_callpadrão - O resultado volta pelo socket
- A função devolve o resultado já interpretado
Ou seja: as chamadas dentro do script se comportam igual às chamadas normais — mesmos limites de
requisição, mesmo tratamento de erro, mesmas capacidades. A única restrição é que o terminal() só
roda em primeiro plano (sem os parâmetros background e pty).
Tratamento de erro
Quando um script falha, o agente recebe informação estruturada:
- Saída diferente de zero: a saída de erro vem junto, então o agente vê a pilha inteira
- Tempo esgotado: o script é encerrado e o agente vê
"Script timed out after 300s and was killed." - Interrupção: se a pessoa manda uma mensagem nova durante a execução, o script é encerrado e o
agente vê
[execution interrupted — user sent a new message] - Limite de chamadas: atingido o teto de 50, as chamadas seguintes devolvem erro
A resposta sempre traz status (sucesso, erro, tempo esgotado ou interrompido), output,
tool_calls_made e duration_seconds.
Segurança
O processo filho roda com um ambiente mínimo. Chaves de API, tokens e credenciais são removidos por padrão. O script acessa as ferramentas exclusivamente pelo canal de RPC — ele não consegue ler segredos das variáveis de ambiente, a não ser que você libere explicitamente.
Variáveis de ambiente com KEY, TOKEN, SECRET, PASSWORD, CREDENTIAL, PASSWD ou AUTH no
nome são excluídas. Só as variáveis seguras de sistema (PATH, HOME, LANG, SHELL,
PYTHONPATH, VIRTUAL_ENV e afins) passam.
Repasse de variáveis declaradas por skill
Quando uma skill declara required_environment_variables no frontmatter, essas variáveis são
repassadas automaticamente aos processos filhos do execute_code e do terminal, depois que a
skill é carregada. Assim as skills usam as chaves que declararam, sem enfraquecer a postura de
segurança para código arbitrário.
Fora de skills, dá para liberar variáveis explicitamente no config.yaml:
terminal:
env_passthrough:
- MINHA_CHAVE
- OUTRO_TOKEN
Veja o guia de Segurança para os detalhes.
Variáveis HERMES_* no processo filho
O processo filho recebe só um conjunto pequeno e fixo de variáveis operacionais HERMES_*, por nome
exato:
HERMES_HOMEHERMES_PROFILEHERMES_CONFIGHERMES_ENV
(mais HERMES_RPC_DIR, HERMES_RPC_SOCKET, TZ e HOME, que o Hermes injeta explicitamente para
o canal de RPC funcionar).
Versões anteriores repassavam qualquer variável cujo nome começasse com HERMES_. Esse prefixo
amplo foi removido por endurecimento de segurança: ele podia vazar configuração com nome HERMES_*
que não casa com nenhuma palavra de segredo (por exemplo, HERMES_BASE_URL, HERMES_KANBAN_DB, ou
um endpoint HERMES_*_WEBHOOK) para dentro de código arbitrário em sandbox.
Se um script de execute_code — ou um módulo do repositório ou de plugin que ele importe —
dependia de uma variável HERMES_* fora dos quatro nomes operacionais acima, ela agora chega
vazia no processo filho. A remoção é intencional, não um bug.
Contorno — libere a variável explicitamente. Os dois caminhos repassam a variável tanto ao
execute_code quanto ao terminal, e nenhum deles enfraquece a garantia de remoção de segredo
(credenciais de provedor gerenciadas pelo Hermes nunca podem ser reliberadas assim):
-
Por máquina, no
config.yaml— acrescente o nome exato à lista de repasse:terminal:env_passthrough:- HERMES_KANBAN_DB- HERMES_BASE_URL -
Por skill, no frontmatter — declare, para ser registrada sempre que a skill carregar:
required_environment_variables:- HERMES_KANBAN_DB
Como diagnosticar. Quando o filho descarta uma ou mais variáveis HERMES_* não liberadas, o
Hermes emite uma linha de log em nível debug, nomeando as variáveis e apontando o escape do
env_passthrough. Rode com log de depuração (hermes logs --level DEBUG, ou olhe o
~/.hermes/logs/agent.log) e procure por
execute_code: dropped N non-allowlisted HERMES_* var(s) se um script se comportar como se uma
variável estivesse faltando.
O Hermes sempre grava o script e o hermes_tools.py gerado num diretório temporário, limpo depois
da execução. No modo strict, o script também roda ali; no modo project, ele roda no diretório
da sessão (o diretório temporário fica no PYTHONPATH, então os imports continuam resolvendo). O
processo filho roda no próprio grupo de processos, para poder ser encerrado de forma limpa em caso de
tempo esgotado ou interrupção.
execute_code ou terminal?
| Caso | execute_code | terminal |
|---|---|---|
| Fluxo de vários passos, com chamadas de ferramenta no meio | Sim | Não |
| Comando de shell simples | Não | Sim |
| Filtrar ou processar saída grande de ferramenta | Sim | Não |
| Rodar uma compilação ou suíte de testes | Não | Sim |
| Percorrer resultados de busca num laço | Sim | Não |
| Processos interativos ou em segundo plano | Não | Sim |
| Precisa de chave de API no ambiente | Só por repasse | Sim (a maioria passa) |
Regra prática: use o execute_code quando precisar chamar as ferramentas do Hermes por programa,
com lógica entre as chamadas. Use o terminal para comandos de shell, compilações e processos.
Onde funciona
A execução de código existe no Linux, no macOS e no Windows. No Linux e no macOS o canal de RPC
usa um socket de domínio Unix; no Windows, onde o AF_UNIX é pouco confiável, o Hermes cai sozinho
num socket TCP de loopback. Backends de terminal remotos (Docker, SSH, Modal e afins) usam um
transporte de RPC baseado em arquivo, e exigem Python 3 dentro do backend.