LSP: o agente escreve código e ouve o language server
Por SkyNet · 2 de outubro de 2026
Um agente que escreve código sem um compilador por perto anda a adivinhar. O check que o Hermes corre a seguir a um write_file faz um ast.parse em Python (ou um json.loads em JSON), gasta microssegundos e não vê mais nada: sintaxe correta, tipos errados, passa tudo.
O LSP fecha esse buraco. O Hermes arranca language servers a sério — pyright, typescript-language-server, rust-analyzer, gopls, clangd, cerca de vinte no total — como subprocessos de fundo e injeta os diagnósticos semânticos no mesmo check pós-escrita do write_file e do patch. Erros de tipo, nomes indefinidos, imports em falta: o agente vê-os na resposta da ferramenta que acabou de usar.
Só aparecem os erros da tua edição
Em cada escrita bem sucedida o Hermes tira um baseline dos diagnósticos do ficheiro, escreve e volta a consultar o server, filtrando o que já estava lá. O que sobra é o que a edição introduziu.
Recriei isto agora num repositório git de rascunho, com uma função soma(a: int, b: int) chamada com soma("dois", 3):
"lint": {"status": "ok", "output": ""}
"lsp_diagnostics": "LSP diagnostics introduced by this edit:
ERROR [5:12] Argument of type \"Literal['dois']\" cannot be assigned to
parameter \"a\" of type \"int\" in function \"soma\" [reportArgumentType] (Pyright)"
A seguir, um patch que só acrescentou um comentário devolveu lint ok e nenhum campo lsp_diagnostics: o erro da linha 5 continuava no ficheiro, mas ficara no baseline. Um terceiro patch, a acrescentar print(nome_que_nao_existe), reportou apenas o erro novo — ERROR [7:7] "nome_que_nao_existe" is not defined [reportUndefinedVariable] (Pyright). É a diferença entre “isto está partido” e “tu partiste isto” e, num ficheiro com trinta erros antigos por resolver, entre um sinal útil e ruído.
Dois canais, dois sinais
O lint é o check sintático in-process (ast.parse, json.loads) e corre primeiro; o lsp_diagnostics traz a semântica do server e só corre quando a sintaxe está limpa. Um ficheiro limpo mas com erro de tipo dá lint.status: "ok" e um lsp_diagnostics preenchido: são perguntas diferentes e o Hermes responde às duas na mesma volta. Se o LSP falhar, o check de sintaxe continua a passar — nenhum caminho de erro estraga uma escrita.
Como se usa
Não há nada para ligar: o subsistema está ativo por omissão e, com os binários no PATH, não há config para escrever.
hermes lsp status estado do serviço + instalação por server
hermes lsp list registo (--installed-only filtra)
hermes lsp install <id> instalar um server à mão
hermes lsp install-all tentar todos os que têm receita conhecida
hermes lsp restart desligar clientes a correr (limpa o broken-set)
hermes lsp which <id> caminho do binário resolvido
Nesta máquina o hermes lsp status diz enabled: True, wait_mode: document, wait_timeout: 5.0s e install_strategy: auto. Instalados estão pyright, typescript, astro-language-server, rust-analyzer e yaml-language-server, com clangd, lua-language-server e laravel-lsp como manual-only e vários [trusted workspaces only]. As instalações automáticas ficam em casa — <HERMES_HOME>/lsp/bin/ e <HERMES_HOME>/lsp/node_modules/, nunca /usr/local/ nem ~/.local/.
Quando os defaults não servem:
lsp:
enabled: true # false = volta ao check de sintaxe
wait_mode: document # "document" (default) ou "full"
wait_timeout: 5.0 # cada edit espera 2x este valor
warmup_timeout: 0 # orçamento do 1.º pedido a um workspace frio (0 = igual a wait_timeout)
broken_retry_seconds: 0 # 0 = par (server, root) falhado fica de fora até ao restart
exclude_roots: [] # roots onde nenhum server corre
trusted_workspaces: [] # diretorias cujos projetos um server pode carregar
install_strategy: auto # auto | manual
package_manager: npm # npm | pnpm | yarn
idle_timeout: 600 # 0 = nunca fazer reap de servers ociosos
servers:
typescript:
disabled: true # saltar TS mesmo com as extensões a bater
O gate do trust
Muitos servers correm código do projeto: o rust-analyzer faz cargo check a cada save, o typescript-language-server carrega o node_modules/typescript do repositório, o elixir-ls avalia o mix.exs. Aceitável no teu projeto, inaceitável num repositório que o agente acabou de clonar. Por isso o Hermes trata tudo como não confiável, exceto o worktree da diretoria onde apontaste o Hermes (o cd my-app && hermes, o hermes -w, a sessão Desktop/TUI, o terminal.cwd do gateway) e o que listares em lsp.trusted_workspaces. Num workspace desses nega por omissão: rust-analyzer, gopls, jdtls e zls ficam de fora, o pyright usa o VIRTUAL_ENV ou o Python do Hermes (nunca o .venv do projeto) e o hermes lsp status marca-os [trusted workspaces only].
Onde isto te morde
- LSP só corre dentro de um repositório git; fora dele fica dormente e sobra o check de sintaxe. Um
git inité o que ativa. - O trust não segue um
cdno terminal, e um checkout aninhado dentro de um worktree confiável tem.gitpróprio, logo não é confiável. Cron jobs e workers de Kanban não ganham trust automático — listas as diretorias à mão. - Um servidor que falha fica marcado broken (spawn error ou pedido fora do orçamento) e é saltado até ao
hermes lsp restart; obroken_retry_seconds: Nretenta-o sozinho. - O rust-analyzer precisa de acabar o index antes de emitir diagnósticos (o primeiro edit pode voltar sem dados) e o
bash-language-serverdelega noshellcheck: sem o binário, arranca mas nunca emite erros, e olsp statusavisa emBackend warnings. - Os diagnósticos são freshness-gated: se o server não responde dentro do orçamento, o edit reporta “sem dados LSP” em vez de repetir erros velhos como se fossem de agora.
- Cada edit espera duas vezes (baseline e re-check), logo um server mudo custa no máximo
2 × wait_timeout. O primeiro pedido a um workspace frio tem owarmup_timeout; aumentar owait_timeoutfaria todos os edits seguintes bloquearem pelo cold build. - Servers ociosos são desligados ao fim de
lsp.idle_timeout(600 s, mínimo 30) e respawnados na operação seguinte, e um worktree que desaparece leva os seus servers antes dogit worktree remove. O pyright é multi-root: um só processo, com os worktrees paralelos de subagentes anexados como workspace folders.
Vale a pena?
Sim, e é das funcionalidades desta vaga que mais se nota no dia a dia. O agente continua a não ser um compilador, mas deixa de escrever às cegas: a resposta do write_file passa a ser uma revisão de tipos gratuita, com o delta a evitar a praga do ruído. Se escreves código com o Hermes num repositório git, já está ligado.
Tags: #hermes #lsp #ferramentas