Seção 16: Posto de agentes e aprovação humana¶
O posto roda agentes Claude Code no servidor, em paralelo, cada tarefa num worktree próprio de um repositório, e você os acompanha numa tela web: o Vibe Kanban (https://kanban.seudominio.com). Você cria a tarefa, escolhe o agente, revisa o diff e pede o PR.
Os agentes trabalham sem você olhando. Por isso, toda ação deles no servidor (reiniciar um container, redeployar uma stack, aplicar um playbook) vira um pedido que espera o seu "aprovar". Você decide por botões no Telegram ou na tela https://aprovacoes.seudominio.com. A mesma regra vale para o assistente do Telegram (seção 17).
flowchart LR
voce[Você] -- kanban. --> vk[Vibe Kanban<br/>posto, ai-desktop-vm]
vk --> ag[Claude Code<br/>worktree por tarefa]
ag -- chave agentes --> llm[LiteLLM]
ag -- notas e escrita --> notas[MCPs das notas]
ag -- "rota autonomos :4200<br/>chave agentes" --> hub[agentgateway]
hub --> ops[ops_controle]
ops -- "pedido" --> apr[aprovador<br/>stack agents]
apr -- botões --> tg[Telegram]
apr -- aprovacoes. --> tela[Tela de aprovações]
tg -- Aprovar --> apr
ag -- "repete com aprovacao_id" --> hub
| Peça | Onde | O que faz |
|---|---|---|
posto |
stack posto, ai-desktop-vm (imagem local localhost/homeserver/posto) |
Vibe Kanban (servidor local, um usuário, SQLite) e os agentes que ele roda: Claude Code, git e gh |
| Vibe Kanban | compilado do código-fonte na tag posto_vibe_kanban_tag |
Tarefas em colunas, um worktree e um terminal por tentativa, revisão de diff com comentários, PR |
posto_porteiro |
stack posto, mesma rede do container posto (imagem caddy) |
Atende a porta 3005 e só repassa ao Vibe Kanban o que veio pelo Traefik (abaixo) |
aprovador |
stack agents, core-services-vm (imagem local localhost/homeserver/aprovador) |
Pedidos de ação com botões no Telegram e a tela aprovacoes.; o ops só executa com um pedido aprovado |
Rota autonomos do hub |
http://<IP da core>:4200/mcp, só da ai-desktop-vm |
O que a rota de leitura mostra e as ações do ops, sem as escritas do Grafana, da casa e do n8n (seção 13) |
Por que compilar o Vibe Kanban¶
O pacote npm do Vibe Kanban baixa um binário de um CDN da Bloop, que encerrou a nuvem paga em abril de 2026, e traz chaves de telemetria embutidas. O ansible/playbooks/setup-posto-volumes.yml baixa o código na tag fixada e usa o Dockerfile do próprio projeto, com os argumentos de telemetria vazios: nada sai para a PostHog nem para a Sentry, e o posto não depende do CDN. O custo é uma compilação Rust de dezenas de minutos na ai-desktop-vm, uma vez por versão. A imagem do posto (docker/images/posto/Dockerfile) copia o servidor dessa compilação para uma base Node e deixa no cache a versão do Claude Code que o Vibe Kanban chama (posto_claude_code_version). A tag da imagem junta as três versões (posto_version, a tag do Vibe Kanban e a do Claude Code): mudou qualquer uma, o posto.env aponta para a imagem nova.
\"Claude Code não detectado\"
A tela de configurações do Vibe Kanban procura o Claude Code em ~/.claude, e o posto o configura em CLAUDE_CONFIG_DIR. O aviso é só cosmético: as tarefas rodam o Claude Code normalmente.
Porteiro do Vibe Kanban¶
O Vibe Kanban não tem login e aceita qualquer requisição que chegue nele. Por isso ele escuta só no 127.0.0.1 do próprio container (HOST=127.0.0.1), e quem atende a porta 3005 é o porteiro: um Caddy (posto_porteiro) que divide a rede do container posto (network_mode: service:posto). O porteiro:
- deixa passar
/api/health, para as sondas do Prometheus e do painel; - repassa o resto só se a requisição trouxer o cabeçalho
X-Posto-Chavecom o segredoposto_chave_proxy, que o Traefik injeta depois do Authelia (middlewareposto-chave, seção 5), e retira o cabeçalho antes de repassar; - responde 403 a todo o resto, sem painel de administração (
admin off).
Assim, nenhum container das redes ai_network e notes_mcp (Open WebUI, LiteLLM, as notas, o assistente) chega ao Vibe Kanban, e a porta 3005 continua aceitando só a core (firewall das portas do Docker). O Caddyfile sai de ansible/templates/posto/Caddyfile.j2, com o segredo, em 0600; o playbook para se o segredo tiver menos de 32 caracteres. O preço: a pré-visualização de apps do Vibe Kanban (outra porta, também no 127.0.0.1) não abre de fora.
Futuro do projeto
A Bloop encerrou o serviço em nuvem em abril de 2026; o repositório continua aberto (Apache-2.0) e recebeu a versão fixada aqui em setembro de 2026. Se o projeto parar, o posto (Claude Code), o aprovador e a rota autonomos continuam funcionando; só a tela muda.
Aprovação humana¶
Há duas camadas, e as duas valem ao mesmo tempo:
| Camada | O que pede aprovação | Onde você decide |
|---|---|---|
| Tela do Vibe Kanban | Comandos e arquivos do agente dentro do posto (o perfil DEFAULT do Claude Code tem approvals: true) |
Na própria tarefa, no Vibe Kanban |
| Aprovador | Ações no servidor pelo ops: container_reiniciar, stack_redeploy, ansible_check e ansible_aplicar |
Botões no Telegram ou https://aprovacoes.seudominio.com |
Para tarefas de confiança, escolha o perfil AUTONOMO ao criar a tentativa: o agente deixa de pedir permissão para comandos e arquivos do posto. As ações no servidor continuam pedindo.
Como um pedido funciona¶
- O agente chama, por exemplo,
ops_container_reiniciarcomnome: jellyfin. - O
opsconfere as regras de sempre (containers e stacks protegidos são recusados na hora, sem incomodar você). - Todo cliente fora de
ops_clientes_sem_aprovacao(padrão: sóclaude_code, que pede aprovação na própria tela) passa peloaprovador:agentes,assistente, uma chave nova esquecida e uma chamada sem o cabeçalho do hub. A lista diz quem não precisa, e não quem precisa, para que uma variável perdida (lista vazia) faça todos pedirem aprovação em vez de nenhum. Oopscria o pedido e responde ao agente: aguardando aprovação: pedido X. Cada cliente tem no máximo 5 pedidos pendentes; o sexto é recusado até você decidir os outros. - Chega no Telegram (bot de avisos) uma mensagem com o que o agente quer fazer e os botões ✅ Aprovar e ❌ Recusar. O mesmo pedido aparece na tela
aprovacoes.. - Você decide. A mensagem é atualizada com a decisão e quem decidiu.
- Avise o agente na tarefa (ou na conversa, no assistente): "aprovei, pode seguir". Ele não fica perguntando sozinho; a resposta do
opsmanda esperar o seu aviso. - O agente chama de novo com
aprovacao_id: X. Oopssó executa se o pedido estiver aprovado, for do mesmo cliente, ação, alvo e argumentos, ainda não tiver sido usado e estiver no prazo (aprovador_validade_min, 30 min). Depois de usado, ele não vale de novo.
Ansible: duas aprovações¶
Mesmo em --check, o Ansible roda código do repositório (lookups, templates, tarefas com check_mode: false) com a senha do vault. Por isso um agente autônomo pede aprovação para simular (ansible_check, depois do git pull da main) e de novo para aplicar (ansible_aplicar). O pedido de aplicar mostra o commit simulado com o autor e o assunto (git log -1) e o PLAY RECAP do --check: confira se o commit é o que você espera antes de aprovar. Playbook fora de ops_playbooks_permitidos é recusado sem virar pedido.
O repositório é a outra porta
O ops aplica o que estiver na main do wiki-home-server. Nunca dê ao posto_github_token permissão de escrita (Contents) nesse repositório, e proteja a main no GitHub (Settings → Branches: exigir PR com revisão, sem push direto). Um agente que conseguisse escrever na main mudaria o que você aprova sem que a descrição do pedido parecesse diferente.
Quem pode decidir: no Telegram, só o seu usuário (telegram_chat_id, mais aprovador_telegram_usuarios); clique de outra pessoa num grupo responde "Sem permissão". Na tela, só quem passou pelo Authelia com 2FA e o grupo admins. A tela só responde a requisições com o segredo que o Traefik injeta (aprovador_chave_proxy, no middleware aprovador-chave@file de ansible/roles/infra/traefik-config/templates/dynamic_agents.yml.j2, e não num label que o docker inspect mostraria), então outro container da rede proxy não decide no seu lugar, e o POST precisa vir da própria tela (Origin), então outra página aberta no navegador também não. Cada evento (criado, aprovado, recusado, expirado, usado) vira uma linha JSON no log do aprovador, coletada pelo Alloy.
Bot de avisos e o aprovador
O aprovador lê os cliques do bot de avisos (o mesmo das notificações) por getUpdates. O Grafana e o n8n só enviam por esse bot, então não há conflito. Não configure um webhook nesse bot, senão o getUpdates para de receber. O assistente tem um bot próprio pelo mesmo motivo.
O que os agentes alcançam¶
| Recurso | Como | Observação |
|---|---|---|
| LLM | ANTHROPIC_BASE_URL=http://litellm:4000 (rede ai_network), chave litellm_client_keys.agentes |
Orçamento em litellm_key_budgets.agentes; uso na tela do LiteLLM (llm.) e traces no Langfuse. Nomes reais dos modelos Claude (seção 8) |
| Servidor | MCP servidor → rota autonomos (chave agents_autonomos_keys.agentes) |
Lê métricas, logs, alertas, containers e stacks; age só pelo ops, com aprovação |
| Notas | MCPs notas (http://qmd:8181/mcp, busca) e escrita (http://notes_escrita:8000/mcp, só Trabalho e Estudos) |
Pela rede interna notes_mcp (seção 12) |
| Repositórios | /repos no disco local da VM (/opt/local-data/posto/repos) |
Você clona; o agente trabalha num worktree por tentativa |
| GitHub | gh com GH_TOKEN=posto_github_token (opcional) |
Token fine-grained, só os repositórios de trabalho, com Contents e Pull requests; nunca escrita no wiki-home-server |
| Prometheus e Loki da core | Portas 9090 e 3100 da core, abertas à ai-desktop-vm para o Alloy |
Limite conhecido: o firewall vê o IP da VM, não o container, então um agente com o perfil AUTONOMO também alcança essas portas (consultar métricas e mandar métricas ou logs falsos). Nada muda no servidor por elas; desconfie de métricas e logs estranhos com origem na VM de IA |
Os MCPs vêm de /etc/claude-code/managed-mcp.json (gerado de ansible/templates/posto/managed-mcp.json.j2, montado só leitura): todo agente usa esses três, e nenhum agente os troca. Os perfis do Claude Code no Vibe Kanban vêm de ansible/templates/posto/profiles.json.j2, gravado uma vez (o que você mudar na tela fica).
Arquivos¶
docker/stacks/posto/docker-compose.yml— o serviçoposto(Vibe Kanban em127.0.0.1) e oposto_porteiro(porta 3005 só no IP da VM, aceita só da core).ansible/templates/posto/Caddyfile.j2— o porteiro:/api/healthlivre, o resto só comX-Posto-Chave.docker/images/posto/Dockerfile— o servidor do Vibe Kanban sobrenode:22-bookworm-slim, com git,gh,tinie o Claude Code em cache.ansible/playbooks/setup-posto-volumes.yml— clona e compila o Vibe Kanban, constrói o posto, cria os diretórios e gera os MCPs, os perfis e oposto.env.ansible/templates/posto/managed-mcp.json.j2eansible/templates/posto/profiles.json.j2— acima.docker/images/aprovador/Dockerfileedocker/images/aprovador/.dockerignore— só a biblioteca padrão do Python.docker/images/aprovador/aprovador/__init__.py— descrição e versão.docker/images/aprovador/aprovador/config.py— variáveis de ambiente e quem pode decidir.docker/images/aprovador/aprovador/pedidos.py— o registro: criar, decidir, usar uma vez, expirar; JSON no disco.docker/images/aprovador/aprovador/telegram.py— mensagem com botões, leitura dos cliques e atualização da mensagem.docker/images/aprovador/aprovador/servidor.py— API doops(token), tela (chave do proxy,Remote-User,Origin).docker/images/aprovador/aprovador/__main__.py—python -m aprovador.docker/images/ops/ops/aprovacao.py— o cliente doaprovadornoops(falha fechada se não estiver configurado).- Serviço
aprovadore as variáveis doops_controleemdocker/stacks/agents/docker-compose.yml; imagem eagents.envemansible/playbooks/setup-agents-volumes.yml(seção 13). - Testes:
tools/wikisync/tests/test_aprovacao.py(oaprovadore o fluxo noops, de ponta a ponta) etools/wikisync/tests/test_posto_assistente.py.
Primeira configuração¶
- Vault (seção 1):
litellm_client_keys.agentes(echo sk-$(openssl rand -hex 24)),agents_autonomos_keys.agentes(ag-+openssl rand -hex 24),aprovador_token,aprovador_chave_proxyeposto_chave_proxy(openssl rand -hex 32cada) e, se quiser PRs pelogh,posto_github_token(sem escrita nowiki-home-server; proteja amain, veja acima). - Chave no LiteLLM:
ansible-playbook -i inventories/home/hosts.ini playbooks/setup-ai-keys.yml --ask-vault-pass. - Firewall, rotas e Authelia:
setup-base-ubuntu.yml(portas 4200 e 3005, seção 14),setup-core-networking.yml(rotakanban.com o segredo do porteiro, o middlewareaprovador-chavee os domínioskanban/aprovacoessó paraadmins) esetup-monitoring-stack-volumes.yml(sondas e painel). - Hub e aprovador:
setup-agents-volumes.yml; no Portainer, Update the stack daagentscom oagents.envnovo em Advanced mode; depoisdocker restart agentgateway. - Posto:
ansible-playbook -i inventories/home/hosts.ini playbooks/setup-posto-volumes.yml --ask-vault-pass(a primeira compilação demora). No Portainer, ambienteai-desktop-vm: Add stackpostocomdocker/stacks/posto/docker-compose.ymle oposto.envem Advanced mode. - Primeiro uso: clone o repositório no posto (
docker exec -it posto git clone https://github.com/<você>/<repo>.git /repos/<repo>), abrahttps://kanban.seudominio.com, crie o projeto apontando para/repos/<repo>e escolha o agente Claude Code.
Teste de ponta a ponta¶
https://kanban.seudominio.compede o login do Authelia; um usuário fora deadminsé recusado. Na LAN,curl http://<IP da ai>:3005/api/healthnão conecta (só a core alcança). Da core,curl http://<IP da ai>:3005/api/healthresponde ecurl http://<IP da ai>:3005/dá 403 (sem o segredo). De outro container da VM de IA,docker exec open-webui curl -m 3 http://posto:3000/não conecta.- Numa tarefa de teste, peça ao agente "liste os containers da core pelo MCP servidor": funciona sem aprovação (é leitura).
- Peça "reinicie o container
homepage": o agente responde que está aguardando aprovação; no Telegram chega o pedido com botões. Aprove e diga ao agente "aprovei, pode seguir": ele repete comaprovacao_ide o container reinicia. A mensagem mostra Aprovado e executado. - Peça "reinicie o
traefik": recusado na hora (protegido), sem pedido. - Crie um pedido e não decida: em 30 min a mensagem passa a Expirado. A tela
aprovacoes.mostra o histórico. - Peça "simule o playbook de mídia": chega um pedido para o
ansible_check; aprovado, ele simula e devolve oplano_id. Peça para aplicar: o novo pedido mostra o autor, o assunto do commit e o PLAY RECAP.
Falhas¶
| Falha | Comportamento | O que fazer |
|---|---|---|
aprovador fora |
Ações dos autônomos são recusadas (falha fechada); leitura e o Claude Code do seu PC seguem normais | Portainer → stack agents |
| Telegram fora | O pedido continua na tela aprovacoes. |
Decida pela tela |
| Botões não respondem | Outro programa usa o bot por webhook ou getUpdates |
docker logs aprovador; deleteWebhook no bot de avisos |
| Orçamento dos agentes estourado | O Claude Code recebe erro do LiteLLM | Tela do LiteLLM (llm.); aumente litellm_key_budgets.agentes e rode o setup-ai-keys.yml |
kanban. responde 403 |
O segredo do Traefik e o do porteiro diferem (vault mudou e só um playbook rodou) | Rode setup-core-networking.yml e setup-posto-volumes.yml e reinicie o posto_porteiro |
| Agente diz "5 pedidos pendentes" | Limite por cliente no aprovador |
Decida (ou recuse) os pendentes em aprovacoes. |
| Vibe Kanban não compila | O setup-posto-volumes.yml falha na tarefa de compilação |
Veja o log do build; mantenha a tag anterior em posto_vibe_kanban_tag |