2026-04-27

A ideia desse post é mostrar como eu montei uma assistente de IA pessoal rodando 24/7 em um VPS da AWS, acessível de qualquer lugar pelo Telegram, e que escolhe o modelo mais barato disponível antes de cair em opções pagas. O coração disso é o OpenClaw: um daemon open source (MIT, TypeScript, Node 24) que expõe uma assistente única em vários canais (Telegram, Slack, Discord, Signal, iMessage, Matrix, WeChat) e roteia toda requisição por uma stack configurável de provedores e modelos com failover embutido.
A parte interessante: o OpenClaw já implementa uma cascata de fallback nativa. Eu configuro modelos grátis primeiro, e ele escala automaticamente quando o tier estoura. Claude fica gateado no fim, só ativado por comando explícito.
[ seu celular ]
|
| Telegram DM
v
api.telegram.org
|
| long-poll (saída apenas)
v
+--------------------------------------------------------+
| VPS AWS (Ubuntu 24.04, Lightsail $20/mes) |
| |
| systemd user unit: openclaw.service |
| openclaw gateway (Node 24) porta 18789 (lo) |
| canal: telegram (grammY long-poll) |
| runtime de agentes (sandbox: Docker) |
| cascata de modelos -- HTTPS de saida ----+ |
| | |
| ~/.openclaw/ | |
| openclaw.json config + chain | |
| auth-profiles OAuth tokens, API keys | |
| workspace/ estado por agente | |
+------------------------------------------------|-------+
|
+----------------+--------------+--------+--------+
v v v v
openrouter.ai api.minimax.io ChatGPT (Codex) api.anthropic.com
(modelos grátis) (Coding Plan) (sua subscription) (gateado)
Por que essa forma:
127.0.0.1:18789. Security group expõe só SSH.~/.openclaw/). Backup pra S3 fica trivial.A documentação do OpenClaw em docs.openclaw.ai/concepts/model-failover deixa explícito:
O OpenClaw lida com falhas em dois estágios: (1) rotação de auth profile dentro do provedor atual, depois (2) fallback de modelo pro próximo modelo em
agents.defaults.model.fallbacks.
O que isso me dá de graça:
| Requisito | Como o OpenClaw resolve |
|---|---|
| Tentar modelo grátis primeiro | agents.defaults.model.primary = "openrouter/qwen/qwen3-coder:free" |
| Escalar automaticamente em rate limit / 429 | fallbacks[] avança em rate-limit, timeout e erros upstream |
| Não loopar em provedor quebrado | Cooldown exponencial (1m, 5m, 25m, 1h) no profile que falhou |
| Parar no Codex sem permissão | Codex último em fallbacks; não colocar Claude na cadeia automática |
| Chegar no Claude só com permissão | Usar /model anthropic/claude-opus-4-7 no chat. Quando você escolhe /model, a sessão fica estrita: não cai pra outro tier silenciosamente. |
Esse comportamento de “override do usuário é estrito” é o pulo do gato: quando eu digito /model anthropic/..., o OpenClaw trava a sessão no Claude até eu resetar, e uma falha no Claude vira erro em vez de roteamento silencioso pra um tier mais barato. É exatamente o “Claude requer permissão” que eu queria.
O Gateway é um único processo Node mais um sandbox Docker opcional. RAM é o gargalo.
| Critério | EC2 t3.medium | EC2 t4g.medium (ARM) | Lightsail $20 |
|---|---|---|---|
| vCPU | 2 burst | 2 burst | 2 |
| RAM | 4 GB | 4 GB | 4 GB |
| Storage | gp3, BYO | gp3, BYO | 80 GB SSD incluso |
| Rede | EIP $3.60 ocioso | igual | 3 TB egress incluso |
| Preço cheio | ~$30/mês | ~$24/mês | $20/mês flat |
| Savings Plan 1 ano | ~$19/mês | ~$15/mês | n/a |
| Complexidade ops | VPC, SG, EBS, EIP | igual | um clique |
Recomendação: Lightsail $20/mês. Custo flat previsível, egress incluso, sem fee de EIP. Mesmo Ubuntu 24.04 por baixo, então todo comando desse guia funciona igual. Tamanhos abaixo (Lightsail $10 ou t3.small com 2 GB) ficam apertados: Node 24 mais o sandbox Docker passa de 2 GB durante turnos pesados.
brew install awscli
aws configure
brew install --cask tailscale # opcional mas recomendado
AmazonLightsailFullAccess.REGION="sa-east-1"
aws lightsail create-instances \
--instance-names openclaw-1 \
--availability-zone "${REGION}a" \
--blueprint-id ubuntu_24_04 \
--bundle-id medium_3_0 \
--tags key=Name,value=openclaw
# Esperar a instância subir
aws lightsail get-instance --instance-name openclaw-1 \
--query 'instance.state.name' --output text
# Restringir SSH ao meu IP atual
MY_IP=$(curl -s https://checkip.amazonaws.com)
aws lightsail put-instance-public-ports \
--instance-name openclaw-1 \
--port-infos "fromPort=22,toPort=22,protocol=TCP,cidrs=${MY_IP}/32"
# Baixar a chave SSH default da região
aws lightsail download-default-key-pair --query 'privateKeyBase64' --output text \
| base64 -d > ~/.ssh/lightsail-${REGION}.pem
chmod 400 ~/.ssh/lightsail-${REGION}.pem
# Pegar IP e conectar
PUBLIC_IP=$(aws lightsail get-instance --instance-name openclaw-1 \
--query 'instance.publicIpAddress' --output text)
ssh -i ~/.ssh/lightsail-${REGION}.pem ubuntu@${PUBLIC_IP}
Já dentro da máquina, arrumo hostname e timezone:
sudo hostnamectl set-hostname openclaw
sudo timedatectl set-timezone America/Sao_Paulo
sudo apt update && sudo apt upgrade -y
O OpenClaw tem um script de install que puxa Node 24 e o binário, e instala o daemon systemd pra mim.
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs
node --version # esperar v24.x
npm --version
curl -fsSL https://openclaw.ai/install.sh | bash
which openclaw
openclaw --version
# Pode escolher "skip" ou só ANTHROPIC_API_KEY por enquanto.
# A configuração da cascata vem no próximo passo.
openclaw onboard --install-daemon
# Confirmar daemon
openclaw gateway status
# Esperar: "running on 127.0.0.1:18789"
# Confirmar onde foi parar o estado
ls ~/.openclaw/
Se quiser checar a Control UI (útil pra debug), faço um port-forward via SSH:
# Do meu laptop
ssh -i ~/.ssh/lightsail-sa-east-1.pem -L 18789:127.0.0.1:18789 ubuntu@${PUBLIC_IP}
# Aí abro http://127.0.0.1:18789 no navegador local.
ssh -i ~/.ssh/... ubuntu@${PUBLIC_IP} 'cat ~/.openclaw/.env | grep TOKEN'
Os quatro tutoriais de chave estão na seção Tutoriais de chave abaixo. No fim eu preciso ter:
OPENROUTER_API_KEY (sk-or-v1-…)MINIMAX_API_KEY (ou OAuth via openclaw onboard --auth-choice minimax-global-oauth)openclaw onboard --auth-choice openai-codex-oauthANTHROPIC_API_KEY (sk-ant-…).envcat > ~/.openclaw/.env <<'EOF'
# Tier 1 -- OpenRouter (modelos grátis)
OPENROUTER_API_KEY=sk-or-v1-REPLACE_ME
# Tier 2 -- MiniMax Coding Plan ($20/mes). Comente se usou OAuth.
MINIMAX_API_KEY=REPLACE_ME
# Tier 4 -- Anthropic API key (gateado; só por /model)
ANTHROPIC_API_KEY=sk-ant-REPLACE_ME
# Telegram (preencho no Passo 4)
# TELEGRAM_BOT_TOKEN=
EOF
chmod 600 ~/.openclaw/.env
O Tier 3 (Codex) entra via OAuth, não env var. O token vai parar em
~/.openclaw/agents/<id>/agent/auth-profiles.json.
openclaw.json com a cadeiacp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.bak
openclaw config set agents.defaults.model.primary 'openrouter/qwen/qwen3-coder:free'
openclaw config set --strict-json --merge agents.defaults.model.fallbacks '[
"openrouter/z-ai/glm-4.5-air:free",
"openrouter/openai/gpt-oss-120b:free",
"minimax/MiniMax-M2.7",
"openai-codex/gpt-5.5"
]'
# Permitir Claude pra escolher manual com /model. Não está na cadeia automática.
openclaw config set --strict-json --merge agents.defaults.models '{
"openrouter/qwen/qwen3-coder:free": {"alias": "free-coder"},
"openrouter/z-ai/glm-4.5-air:free": {"alias": "free-glm"},
"openrouter/openai/gpt-oss-120b:free": {"alias": "free-oss"},
"minimax/MiniMax-M2.7": {"alias": "minimax"},
"openai-codex/gpt-5.5": {"alias": "codex"},
"anthropic/claude-opus-4-7": {"alias": "claude"}
}'
openclaw config get agents.defaults.model
openclaw config get agents.defaults.models
| Evento | Resultado |
|---|---|
| Modelo grátis saudável | Tudo servido pelo qwen3-coder:free. Grátis. |
| Modelo grátis recebe 429 | Roda pra glm-4.5-air:free, depois gpt-oss-120b:free. |
| Pool grátis esgotado | Sobe pro MiniMax (MiniMax-M2.7). $20/mês flat. |
| Janela do MiniMax esgotada | Sobe pro Codex via subscription do ChatGPT. |
| Cap semanal do Codex bate | Resposta erra (porque o Claude não está na cadeia). Eu decido se escalo. |
Eu digito /model claude | Sessão trava no Anthropic Opus. Cobra do meu API key. |
Eu digito /new ou /reset | Volta pro primary da cascata. Lock do Claude vai embora. |
Esse é exatamente o comportamento que eu queria: free-to-Codex automático; Claude só com ação explícita.
Em vez de digitar /model toda hora, defino um segundo agente:
openclaw config set --strict-json --merge agents.list '[
{
"id": "premium",
"model": { "primary": "anthropic/claude-opus-4-7", "fallbacks": [] },
"system": "Você é o tier premium. Use com parcimônia."
}
]'
fallbacks: [] deixa estrito: falha no Claude não cai silenciosamente.
openclaw gateway restart
openclaw doctor
No app do Telegram, no celular:
@BotFather)./newbot.Minha OpenClaw).bot (tipo minha_openclaw_bot).123456789:ABCDef-GhIjKlMnOpQrStUv./setprivacy, escolher o bot, Disable (deixa o bot ver mensagens em grupos; só importa se for adicionar em grupos)./setjoingroups, Disable (não quero que aleatórios adicionem o bot).# Na VPS, acompanho o log e mando qualquer coisa pro bot do celular
openclaw logs --follow
# Procurar uma linha com "from.id": 123456789
Esse número é o <meu-id-telegram>.
echo "TELEGRAM_BOT_TOKEN=123456789:ABC..." >> ~/.openclaw/.env
openclaw config set --strict-json --merge channels.telegram '{
"enabled": true,
"dmPolicy": "allowlist",
"allowFrom": ["telegram:<meu-id-telegram>"]
}'
# Me marca como dona dos comandos (libera /model, /reset, etc.)
openclaw config set --strict-json --merge commands.ownerAllowFrom '["telegram:<meu-id-telegram>"]'
openclaw gateway restart
openclaw doctor
dmPolicy: "allowlist" bloqueia qualquer outro usuário do Telegram que tente DM no bot, mesmo se vazar o username.
No Telegram, mando hello pro bot. Em poucos segundos chega resposta servida pelo qwen3-coder:free.
Tentando escalar manualmente:
/model claude
escreve um decorator Python que retenta com backoff exponencial
/new
/model claude troca pro Claude na sessão; /new reseta de volta pra cascata.
O modelo de ameaça: um daemon de longa duração com credenciais pra ChatGPT, Claude, MiniMax, OpenRouter, e um bot do Telegram que pode rodar comandos shell no sandbox dele. Preciso manter as superfícies de entrada e saída apertadas.
Lightsail/EC2 SG só pode expor porta 22 do meu IP. O Gateway já liga só em loopback (gateway.bind: "lan" é o default; verifico com openclaw config get gateway.bind. Se eu não preciso de LAN, mudo pra "lo").
sudo ss -tlnp | grep -v 127.0.0.1
# Devo ver só :22 (sshd)
Já feito no Passo 4. Confirmo:
openclaw config get channels.telegram.dmPolicy # esperar "allowlist"
openclaw config get channels.telegram.allowFrom # esperar ["telegram:..."]
Se eu vou deixar a assistente rodar shell (que é metade da graça), ligo o sandbox Docker pra ela não tocar no filesystem fora de ~/.openclaw/workspace/.
curl -fsSL https://get.docker.com | sudo bash
sudo usermod -aG docker ubuntu
newgrp docker
openclaw config set agents.defaults.sandbox.mode 'non-main'
openclaw config set agents.defaults.sandbox.backend 'docker'
openclaw gateway restart
openclaw doctor
A sessão main (DMs diretas) ainda tem acesso ao host por default, útil pra trabalhar de verdade. Sessões em grupo ou multi-usuário ficam sandboxed.
chmod 600 ~/.openclaw/.env
chmod 700 ~/.openclaw/agents
find ~/.openclaw -name 'auth-profiles.json' -exec chmod 600 {} \;
Se eu não quero SSH na internet pública:
curl -fsSL https://tailscale.com/install.sh | sudo bash
sudo tailscale up --ssh
# Aí fecho a porta 22 no SG do Lightsail/EC2 totalmente.
# SSH só via MagicDNS do Tailscale: ssh ubuntu@openclaw
gateway.bind = "lo" ou "lan" apenas)dmPolicy: "allowlist" com meu ID numéricocommands.ownerAllowFrom com meu ID numérico~/.openclaw/.env em 0600openclaw doctor verdeopenclaw onboard --install-daemon já criou o user unit. Eu só verifico e aperto.
systemctl --user status openclaw
# Se não tiver, instala:
openclaw daemon install --user
# Lingering pra sobreviver logout
sudo loginctl enable-linger ubuntu
# Auto-start no boot
systemctl --user enable openclaw
# Logs
journalctl --user -u openclaw -f
# Restart / stop / start
systemctl --user restart openclaw
systemctl --user stop openclaw
systemctl --user start openclaw
O daemon roda como o usuário ubuntu (sem root), lê ~/.openclaw/.env e escreve estado em ~/.openclaw/. Restart-on-crash já está embutido.
Tudo que importa vive em ~/.openclaw/. Snapshot noturno pra S3:
sudo apt install -y awscli
aws configure # usar IAM key minimal com PutObject em um bucket
cat > ~/backup-openclaw.sh <<'EOF'
#!/bin/bash
set -euo pipefail
BUCKET="s3://meu-bucket/openclaw-backups"
TS=$(date -u +%Y%m%d-%H%M%S)
cd ~
tar --exclude='.openclaw/workspace/*/node_modules' \
--exclude='.openclaw/agents/*/sessions/cache' \
-czf /tmp/openclaw-${TS}.tar.gz .openclaw
aws s3 cp /tmp/openclaw-${TS}.tar.gz "${BUCKET}/openclaw-${TS}.tar.gz" \
--storage-class STANDARD_IA
rm /tmp/openclaw-${TS}.tar.gz
# Manter os últimos 14 dias
aws s3 ls "${BUCKET}/" | sort | head -n -14 | awk '{print $4}' | while read f; do
aws s3 rm "${BUCKET}/${f}"
done
EOF
chmod +x ~/backup-openclaw.sh
( crontab -l 2>/dev/null; echo "0 3 * * * /home/ubuntu/backup-openclaw.sh" ) | crontab -
Snapshot do disco do Lightsail ($0.05/GB/mês) é uma alternativa mais grosseira e mais simples.
O OpenRouter é uma API única que faz proxy pra centenas de LLMs. O tier grátis (sufixo :free no model id) dá ~50 requests/dia por conta; sobe pra 1.000/dia se eu carregar $10 em créditos vitalícios (esses créditos não são consumidos pelos modelos grátis, só servem como unlock de quota).
openrouter.ai. Login com Google, GitHub ou magic link. Sem telefone, sem KYC.openrouter.ai/keys, Create Key, opcionalmente setar um cap de crédito.sk-or-v1-... (mostrado uma vez só).openrouter.ai/credits carregar $10. Levanta o cap de modelo grátis de 50/dia pra 1.000/dia em todos os :free combinados. Os $10 ficam parados; só uso pago subtrai.Teste rápido:
export OPENROUTER_API_KEY=sk-or-v1-...
curl https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen/qwen3-coder:free",
"messages": [{"role":"user","content":"oi"}]
}'
Limites em abril de 2026:
| Limite | Valor |
|---|---|
| Modelos grátis, RPM | 20/min na conta inteira |
| Modelos grátis, RPD (sem créditos) | 50/dia |
| Modelos grátis, RPD (com $10 em créditos) | 1.000/dia |
| Pool | Todos os :free dividem o mesmo contador diário |
O Token Plan do MiniMax é uma assinatura mensal de verdade, não top-up de crédito. O tier Plus dá 4.500 requests por janela de 5h em MiniMax-M2.7.
platform.minimax.io. Sign up com email mais verificação.MINIMAX_API_KEY=... no ~/.openclaw/.env e usar o ref minimax/MiniMax-M2.7. Caminho OAuth no desktop: openclaw onboard --auth-choice minimax-global-oauth, usando minimax-portal/MiniMax-M2.7.| Plano | Preço | Quota M2.7 |
|---|---|---|
| Starter | $10/mês | 1.500 req/5h |
| Plus | $20/mês | 4.500 req/5h |
| Max | $50/mês | 15.000 req/5h |
Quando a janela de 5h esgota, a Token Plan API Key retorna 429 e a cascata avança pro Codex.
Codex CLI é o agente de coding oficial da OpenAI em Rust. Com ChatGPT Plus/Pro/Business eu autentico por OAuth, sem API key separada, sem cobrança por token. Limites seguem o plano do ChatGPT (janelas de 5h, cap semanal).
O OAuth callback bate em localhost:1455, o que é chato em VPS headless. Três caminhos.
ssh -L 1455:localhost:1455 -i ~/.ssh/lightsail-...pem ubuntu@${PUBLIC_IP}
openclaw onboard --auth-choice openai-codex-oauth
https://auth.openai.com/.... Abro no navegador do laptop.localhost:1455. Por causa do -L, resolve dentro da VPS, completando o flow.~/.openclaw/agents/<id>/agent/auth-profiles.json.openclaw onboard --auth-choice openai-codex-oauth --device-code
Imprime um código; abro a URL em qualquer navegador, colo o código, aprovo. Funciona em conta pessoal; ChatGPT Business/Team precisa de admin habilitando antes.
auth.json de um login no desktop# No Mac
brew install --cask codex
codex login
# Copiar credenciais
ssh -i ~/.ssh/... ubuntu@${PUBLIC_IP} 'mkdir -p ~/.codex'
scp -i ~/.ssh/... ~/.codex/auth.json ubuntu@${PUBLIC_IP}:~/.codex/auth.json
# Na VPS
openclaw onboard --auth-choice openai-codex-oauth --import ~/.codex/auth.json
Cuidado: refresh token do Codex é praticamente single-use. Se duas máquinas usarem a mesma credencial e uma fizer refresh, a outra fica inválida. Caminho A é o mais estável pra deploy de longa duração.
Claude é o tier gateado. Não coloco na cascata automática; só invoco via /model.
console.anthropic.com.openclaw. Copio o sk-ant-... (mostrado uma vez)..env:echo 'ANTHROPIC_API_KEY=sk-ant-...' >> ~/.openclaw/.env
chmod 600 ~/.openclaw/.env
systemctl --user restart openclaw.Teste no Telegram:
/model anthropic/claude-opus-4-7
explica a diferença entre Promise.all e Promise.allSettled
/new
/new retorna a sessão pro primary da cascata pra eu não queimar crédito Anthropic à toa.
Esses são os três modelos grátis que valem encadear, em ordem. Todos aceitam tools, todos pelo <id>:free.
| Posição | Model ref | Por quê | Contexto | Bom em |
|---|---|---|---|---|
| 1 | qwen/qwen3-coder:free | MoE de coding (480B/35B ativos), melhor coder grátis disponível | 262K | Geração de código, tool use agentic |
| 2 | z-ai/glm-4.5-air:free | MoE agente forte, perto de fronteira no SWE-bench | 131K | Raciocínio, tarefas longas |
| 3 | openai/gpt-oss-120b:free | Open-weight da OpenAI, Apache-2.0, família diferente (descorrelaciona quedas) | 131K | Raciocínio geral, diversidade |
Por que três grátis em sequência em vez de só um? Endpoints grátis throttlam direto durante horário comercial dos EUA. Um único modelo grátis quer dizer que um burst de 429 me joga direto no MiniMax (quota paga). Três modelos diversos absorvem throttles curtos sem gastar.
| Item | Custo | Observação |
|---|---|---|
| Lightsail medium_3_0 | $20.00/mês | Flat. Inclui 3 TB egress. |
| Carga inicial OpenRouter | $10.00 | One-time. Levanta cap free de 50/dia pra 1.000/dia. |
| MiniMax Coding Plan Plus | $20.00/mês | Tier 2. |
| ChatGPT Plus | $20.00/mês | Tier 3. |
| Anthropic API | $0-50/mês | Só quando dou /model claude. Cap mensal recomendado. |
| Backup S3 (~1 GB) | $0.02/mês | Standard-IA. |
| Total recorrente | ~$60.02/mês | Sem o Claude oportunístico. |
| Plano | Custo/mês |
|---|---|
| Essa cascata (grátis primeiro, Claude sob demanda) | ~$60 + Claude sob demanda |
| Só Claude via API, ~5M tokens/mês | ~$75 (mistura input/output) |
| ChatGPT Pro standalone | $200 |
~90 minutos do começo ao fim:
| Comando | O que faz |
|---|---|
oi | Roteia pela cascata, modelo default. |
/model claude | Trava sessão no Claude (estrito, sem failover). |
/model codex | Força Codex (alias do passo 3.3). |
/model list | Lista os modelos liberados. |
/new | Sessão nova. Volta pro primary. |
/reset | Igual /new; limpa o estado da sessão. |
/status | Modelo, canal, sessão e estado de tools. |
/think high | Aumenta esforço de raciocínio na próxima resposta. |
/usage tokens | Tokens da sessão. |
# Logs
journalctl --user -u openclaw -f
# Hot-edit sem restart
openclaw config set agents.defaults.model.primary 'minimax/MiniMax-M2.7'
openclaw gateway reload
# Saúde dos auth profiles
openclaw doctor
openclaw models auth list
# Última mensagem servida por qual modelo
openclaw sessions list
openclaw sessions history --session <id>
# Atualizar OpenClaw
npm install -g openclaw@latest
systemctl --user restart openclaw
Tirar um tier de circulação:
# Desabilitar temporariamente
openclaw models auth disable openrouter:default
# Reabilitar
openclaw models auth enable openrouter:default
# Forçar tier específico
openclaw config set agents.defaults.model.primary 'minimax/MiniMax-M2.7'
openclaw gateway reload
openclaw gateway status
openclaw config get channels.telegram
openclaw doctor
openclaw logs --follow
# Suspeita comum: dmPolicy=allowlist com ID errado
openclaw config get channels.telegram.allowFrom
Significa que usei /model X onde X não está em agents.defaults.models. Adiciono:
openclaw config set --strict-json --merge agents.defaults.models \
'{"openai/gpt-4o": {"alias": "gpt4o"}}'
openclaw logs --follow | grep -i 'failover\|429\|cooldown'
openclaw config get agents.defaults.model.fallbacks
Se o array de fallbacks estiver vazio, reaplico o JSON do passo 3.3.
openclaw onboard --auth-choice openai-codex-oauth --reauth
Sandbox Docker está disputando RAM com Node em host de 4 GB. Ou subo pra Lightsail large_3_0 (8 GB, $40/mês), ou desligo o sandbox (agents.defaults.sandbox.mode = "off", menos seguro).