2026-04-27

La idea de este post es mostrar cómo monté una asistente de IA personal corriendo 24/7 en un VPS de AWS, accesible desde cualquier lugar por Telegram, que elige automáticamente el modelo más barato disponible antes de caer en opciones pagas. El corazón de todo es el OpenClaw: un daemon open source (MIT, TypeScript, Node 24) que expone una asistente única en varios canales (Telegram, Slack, Discord, Signal, iMessage, Matrix, WeChat) y rutea cada pedido por una stack configurable de proveedores y modelos con failover incorporado.
Lo interesante: OpenClaw ya implementa una cascada de fallback nativa. Configuro modelos gratis primero y escala automáticamente cuando un tier se agota. Claude queda gateado al final, solo activado por comando explícito.
[ tu celular ]
|
| Telegram DM
v
api.telegram.org
|
| long-poll (solo salida)
v
+--------------------------------------------------------+
| VPS AWS (Ubuntu 24.04, Lightsail $20/mes) |
| |
| systemd user unit: openclaw.service |
| openclaw gateway (Node 24) puerto 18789 (lo) |
| canal: telegram (grammY long-poll) |
| runtime de agentes (sandbox: Docker) |
| cascada de modelos -- HTTPS de salida ----+ |
| | |
| ~/.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 gratis) (Coding Plan) (tu sub) (gateado)
Por qué esta forma:
127.0.0.1:18789. Security group expone solo SSH.~/.openclaw/). Backup a S3 trivial.La doc del OpenClaw en docs.openclaw.ai/concepts/model-failover lo deja explícito:
OpenClaw maneja fallas en dos etapas: (1) rotación de auth profile dentro del proveedor actual, después (2) fallback de modelo al próximo modelo en
agents.defaults.model.fallbacks.
Lo que esto me da gratis:
| Requisito | Cómo OpenClaw lo resuelve |
|---|---|
| Probar modelo gratis primero | agents.defaults.model.primary = "openrouter/qwen/qwen3-coder:free" |
| Escalar automáticamente en rate limit / 429 | fallbacks[] avanza en rate-limit, timeout, errores upstream |
| No loopear en proveedor roto | Cooldown exponencial (1m, 5m, 25m, 1h) en el profile que falló |
| Parar en Codex sin permiso | Codex último en fallbacks; no poner Claude en la cadena automática |
| Llegar a Claude solo con permiso | Usar /model anthropic/claude-opus-4-7 en chat. Cuando elijo /model, la sesión queda estricta: no cae a otro tier en silencio. |
Ese comportamiento de “override del usuario es estricto” es la clave: cuando yo escribo /model anthropic/..., OpenClaw traba la sesión en Claude hasta que reseteo, y una falla en Claude vuelve como error en lugar de rutear silenciosamente a un tier más barato. Es exactamente el “Claude requiere permiso” que quería.
El Gateway es un único proceso Node más un sandbox Docker opcional. La RAM es el cuello de botella.
| Criterio | 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 incluido |
| Red | EIP $3.60 ocioso | igual | 3 TB egress incluido |
| Precio lleno | ~$30/mes | ~$24/mes | $20/mes flat |
| Savings Plan 1 año | ~$19/mes | ~$15/mes | n/a |
| Complejidad ops | VPC, SG, EBS, EIP | igual | un clic |
Recomendación: Lightsail $20/mes. Costo flat predecible, egress incluido, sin fee de EIP. Mismo Ubuntu 24.04 por debajo, así que todo comando de esta guía funciona igual. Tamaños abajo (Lightsail $10 o t3.small con 2 GB) quedan apretados: Node 24 más el sandbox Docker pasa de 2 GB en turnos pesados.
brew install awscli
aws configure
brew install --cask tailscale # opcional pero 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
aws lightsail get-instance --instance-name openclaw-1 \
--query 'instance.state.name' --output text
# Restringir SSH a mi IP actual
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"
# Bajar la SSH key default de la región
aws lightsail download-default-key-pair --query 'privateKeyBase64' --output text \
| base64 -d > ~/.ssh/lightsail-${REGION}.pem
chmod 400 ~/.ssh/lightsail-${REGION}.pem
PUBLIC_IP=$(aws lightsail get-instance --instance-name openclaw-1 \
--query 'instance.publicIpAddress' --output text)
ssh -i ~/.ssh/lightsail-${REGION}.pem ubuntu@${PUBLIC_IP}
Ya dentro de la máquina, hostname y timezone:
sudo hostnamectl set-hostname openclaw
sudo timedatectl set-timezone America/Sao_Paulo
sudo apt update && sudo apt upgrade -y
OpenClaw trae un script de install que baja Node 24 y el binario, e instala el daemon systemd.
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
# Podés elegir "skip" o solo ANTHROPIC_API_KEY por ahora.
# La config de la cascada va en el próximo paso.
openclaw onboard --install-daemon
openclaw gateway status
# Esperar: "running on 127.0.0.1:18789"
ls ~/.openclaw/
Para chequear la Control UI desde el laptop:
ssh -i ~/.ssh/lightsail-sa-east-1.pem -L 18789:127.0.0.1:18789 ubuntu@${PUBLIC_IP}
# Abro http://127.0.0.1:18789 localmente.
ssh -i ~/.ssh/... ubuntu@${PUBLIC_IP} 'cat ~/.openclaw/.env | grep TOKEN'
Los cuatro tutoriales de key están en Tutoriales de key abajo. Al final necesito:
OPENROUTER_API_KEY (sk-or-v1-…)MINIMAX_API_KEY (o 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 gratis)
OPENROUTER_API_KEY=sk-or-v1-REPLACE_ME
# Tier 2 -- MiniMax Coding Plan ($20/mes). Comentar si usaste OAuth.
MINIMAX_API_KEY=REPLACE_ME
# Tier 4 -- Anthropic API key (gateado; solo via /model)
ANTHROPIC_API_KEY=sk-ant-REPLACE_ME
# Telegram (lo lleno en el Paso 4)
# TELEGRAM_BOT_TOKEN=
EOF
chmod 600 ~/.openclaw/.env
El Tier 3 (Codex) entra por OAuth, no env var. El token aterriza en
~/.openclaw/agents/<id>/agent/auth-profiles.json.
openclaw.json con la cadenacp ~/.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 para elegirlo manual con /model. No está en la cadena 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 gratis sano | Todo servido por qwen3-coder:free. Gratis. |
| Modelo gratis 429 | Rota a glm-4.5-air:free, después gpt-oss-120b:free. |
| Pool gratis agotado | Sube a MiniMax (MiniMax-M2.7). $20/mes flat. |
| Ventana MiniMax agotada | Sube a Codex via subscription de ChatGPT. |
| Cap semanal de Codex pegado | La respuesta da error (porque Claude no está en la cadena). Yo decido si escalo. |
Escribo /model claude | Sesión traba en Anthropic Opus. Cobra de mi API key. |
Escribo /new o /reset | Vuelve al primary de la cascada. Lock de Claude liberado. |
Es exactamente el comportamiento que quería: free-to-Codex automático; Claude solo por acción explícita.
En vez de tipear /model cada vez, defino un segundo agente:
openclaw config set --strict-json --merge agents.list '[
{
"id": "premium",
"model": { "primary": "anthropic/claude-opus-4-7", "fallbacks": [] },
"system": "Sos el tier premium. Usá con prudencia."
}
]'
fallbacks: [] lo hace estricto: falla en Claude no baja silenciosamente.
openclaw gateway restart
openclaw doctor
En la app de Telegram, en el celular:
@BotFather)./newbot.Mi OpenClaw).bot (tipo mi_openclaw_bot).123456789:ABCDef-GhIjKlMnOpQrStUv./setprivacy, elegir el bot, Disable (deja al bot ver mensajes en grupos; solo importa si lo vas a agregar a grupos)./setjoingroups, Disable (no querés que aleatorios lo agreguen).# En el VPS, sigo el log y mando cualquier cosa al bot desde el celu
openclaw logs --follow
# Busco una línea con "from.id": 123456789
Ese número es el <mi-id-telegram>.
echo "TELEGRAM_BOT_TOKEN=123456789:ABC..." >> ~/.openclaw/.env
openclaw config set --strict-json --merge channels.telegram '{
"enabled": true,
"dmPolicy": "allowlist",
"allowFrom": ["telegram:<mi-id-telegram>"]
}'
# Me marco como dueña de los comandos (libera /model, /reset, etc.)
openclaw config set --strict-json --merge commands.ownerAllowFrom '["telegram:<mi-id-telegram>"]'
openclaw gateway restart
openclaw doctor
dmPolicy: "allowlist" bloquea cualquier otro usuario de Telegram que mande DM al bot, aunque se filtre el username.
En Telegram, mando hola al bot. En unos segundos llega respuesta servida por qwen3-coder:free.
Probando escalar manualmente:
/model claude
escribí un decorator de Python que reintenta con backoff exponencial
/new
/model claude cambia a Claude para esa sesión; /new resetea a la cascada.
El modelo de amenaza: un daemon de larga duración con credenciales para ChatGPT, Claude, MiniMax, OpenRouter, y un bot de Telegram que puede correr shell en su sandbox. Hay que mantener apretadas las superficies de entrada y salida.
Lightsail/EC2 SG solo expone puerto 22 desde mi IP. El Gateway ya escucha en loopback (gateway.bind: "lan" es default; verifico con openclaw config get gateway.bind. Si no necesito LAN, lo dejo en "lo").
sudo ss -tlnp | grep -v 127.0.0.1
# Debería ver solo :22 (sshd)
Hecho en el Paso 4. Verifico:
openclaw config get channels.telegram.dmPolicy # esperar "allowlist"
openclaw config get channels.telegram.allowFrom # esperar ["telegram:..."]
Si voy a dejar a la asistente correr shell (que es la mitad del propósito), prendo el sandbox Docker para que no toque el filesystem fuera 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
La sesión main (DMs directas) sigue con acceso al host por default, útil para trabajar de verdad. Sesiones de grupo o multi-usuario quedan sandboxed.
chmod 600 ~/.openclaw/.env
chmod 700 ~/.openclaw/agents
find ~/.openclaw -name 'auth-profiles.json' -exec chmod 600 {} \;
Si no quiero SSH en internet pública:
curl -fsSL https://tailscale.com/install.sh | sudo bash
sudo tailscale up --ssh
# Y cierro el puerto 22 en el SG de Lightsail/EC2.
# SSH solo via MagicDNS de Tailscale: ssh ubuntu@openclaw
gateway.bind = "lo" o "lan" solamente)dmPolicy: "allowlist" con mi ID numéricocommands.ownerAllowFrom con mi ID numérico~/.openclaw/.env en 0600openclaw doctor en verdeopenclaw onboard --install-daemon ya creó el user unit. Solo verifico y aprieto.
systemctl --user status openclaw
# Si no aparece:
openclaw daemon install --user
# Lingering para sobrevivir logout
sudo loginctl enable-linger ubuntu
# Auto-arranque en 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
El daemon corre como usuario ubuntu (sin root), lee ~/.openclaw/.env y escribe estado en ~/.openclaw/. Restart-on-crash incorporado.
Todo lo que importa vive en ~/.openclaw/. Snapshot nocturno a S3:
sudo apt install -y awscli
aws configure # IAM key con privilegios mínimos, PutObject en un bucket
cat > ~/backup-openclaw.sh <<'EOF'
#!/bin/bash
set -euo pipefail
BUCKET="s3://mi-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
# Mantener los últimos 14 días
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 del disco entero del Lightsail ($0.05/GB/mes) es una alternativa más burda y más simple.
OpenRouter es una API única que hace proxy a cientos de LLMs. El tier gratis (sufijo :free en el model id) da ~50 requests/día por cuenta; sube a 1.000/día si cargo $10 en créditos vitalicios (esos créditos no se consumen con modelos gratis, solo desbloquean cuota).
openrouter.ai. Login con Google, GitHub o magic link. Sin teléfono, sin KYC.openrouter.ai/keys, Create Key, opcionalmente fijar un cap de crédito.sk-or-v1-... (mostrado una vez).openrouter.ai/credits cargar $10. Sube el cap de modelos gratis de 50/día a 1.000/día en todos los :free combinados. Los $10 quedan parados; solo uso pago resta.Test 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":"hola"}]
}'
Límites a abril de 2026:
| Límite | Valor |
|---|---|
| Modelos gratis, RPM | 20/min en toda la cuenta |
| Modelos gratis, RPD (sin créditos) | 50/día |
| Modelos gratis, RPD (con $10 en créditos) | 1.000/día |
| Pool | Todos los :free comparten el mismo contador diario |
El Token Plan de MiniMax es una subscription mensual real, no top-up de crédito. El tier Plus da 4.500 requests por ventana móvil de 5h en MiniMax-M2.7.
platform.minimax.io. Sign up con email más verificación.MINIMAX_API_KEY=... en ~/.openclaw/.env y usar el ref minimax/MiniMax-M2.7. Camino OAuth en desktop: openclaw onboard --auth-choice minimax-global-oauth, usando minimax-portal/MiniMax-M2.7.| Plan | Precio | Cuota M2.7 |
|---|---|---|
| Starter | $10/mes | 1.500 req/5h |
| Plus | $20/mes | 4.500 req/5h |
| Max | $50/mes | 15.000 req/5h |
Cuando la ventana de 5h se agota, la Token Plan API key devuelve 429 y la cascada avanza a Codex.
Codex CLI es el agente de coding oficial de OpenAI en Rust. Con ChatGPT Plus/Pro/Business autentico por OAuth, sin API key separada, sin cobro por token. Los límites siguen tu plan de ChatGPT (ventanas de 5h, cap semanal).
El callback OAuth pega en localhost:1455, lo cual es molesto en VPS headless. Tres caminos.
ssh -L 1455:localhost:1455 -i ~/.ssh/lightsail-...pem ubuntu@${PUBLIC_IP}
openclaw onboard --auth-choice openai-codex-oauth
https://auth.openai.com/.... La abro en el navegador del laptop.localhost:1455. Por el -L, se resuelve dentro del VPS, completando el flow.~/.openclaw/agents/<id>/agent/auth-profiles.json.openclaw onboard --auth-choice openai-codex-oauth --device-code
Imprime un código; abro la URL en cualquier navegador, pego el código, apruebo. Funciona en cuenta personal; ChatGPT Business/Team requiere admin habilitando antes.
auth.json desde un login en desktop# En el Mac
brew install --cask codex
codex login
# Copiar credenciales
ssh -i ~/.ssh/... ubuntu@${PUBLIC_IP} 'mkdir -p ~/.codex'
scp -i ~/.ssh/... ~/.codex/auth.json ubuntu@${PUBLIC_IP}:~/.codex/auth.json
# En el VPS
openclaw onboard --auth-choice openai-codex-oauth --import ~/.codex/auth.json
Atención: el refresh token de Codex es prácticamente single-use. Si dos máquinas usan la misma credencial y una hace refresh, la otra queda inválida. El Camino A es el más estable para deploy de larga duración.
Claude es el tier gateado. No lo pongo en la cascada automática; solo lo invoco con /model.
console.anthropic.com.openclaw. Copio el sk-ant-... (mostrado una vez)..env:echo 'ANTHROPIC_API_KEY=sk-ant-...' >> ~/.openclaw/.env
chmod 600 ~/.openclaw/.env
systemctl --user restart openclaw.Test en Telegram:
/model anthropic/claude-opus-4-7
explicame la diferencia entre Promise.all y Promise.allSettled
/new
/new retorna la sesión al primary de la cascada para no quemar crédito Anthropic al pedo.
Estos son los tres modelos gratis que vale la pena encadenar, en orden. Todos aceptan tools, todos por <id>:free.
| Posición | Model ref | Por qué | Contexto | Bueno en |
|---|---|---|---|---|
| 1 | qwen/qwen3-coder:free | MoE de coding (480B/35B activos), mejor coder gratis disponible | 262K | Generación de código, tool use agentic |
| 2 | z-ai/glm-4.5-air:free | MoE agente fuerte, cerca de frontera en SWE-bench | 131K | Razonamiento, tareas largas |
| 3 | openai/gpt-oss-120b:free | Open-weight de OpenAI, Apache-2.0, familia distinta (descorrelaciona caídas) | 131K | Razonamiento general, diversidad |
¿Por qué tres gratis en fila en lugar de solo uno? Endpoints gratis throttlean seguido en horario comercial de EE.UU. Un único modelo gratis significa que un burst de 429 te tira directo al MiniMax (cuota paga). Tres modelos diversos absorben throttles cortos sin gastar.
| Item | Costo | Observación |
|---|---|---|
| Lightsail medium_3_0 | $20.00/mes | Flat. Incluye 3 TB egress. |
| Carga inicial OpenRouter | $10.00 | Una vez. Sube cap free de 50 a 1.000/día. |
| MiniMax Coding Plan Plus | $20.00/mes | Tier 2. |
| ChatGPT Plus | $20.00/mes | Tier 3. |
| Anthropic API | $0-50/mes | Solo cuando hago /model claude. Cap mensual recomendado. |
| Backup S3 (~1 GB) | $0.02/mes | Standard-IA. |
| Total recurrente | ~$60.02/mes | Sin Claude oportunístico. |
| Plan | Costo/mes |
|---|---|
| Esta cascada (gratis primero, Claude bajo demanda) | ~$60 + Claude bajo demanda |
| Solo Claude via API, ~5M tokens/mes | ~$75 (mezcla input/output) |
| ChatGPT Pro standalone | $200 |
~90 minutos de punta a punta:
| Comando | Qué hace |
|---|---|
hola | Rutea por la cascada, modelo default. |
/model claude | Traba sesión en Claude (estricto, sin failover). |
/model codex | Forzar Codex (alias del paso 3.3). |
/model list | Lista los modelos permitidos. |
/new | Sesión nueva. Vuelve al primary. |
/reset | Igual que /new; limpia el estado de la sesión. |
/status | Modelo, canal, sesión y estado de tools. |
/think high | Aumenta el esfuerzo de razonamiento en la próxima respuesta. |
/usage tokens | Tokens de la sesión. |
# Logs
journalctl --user -u openclaw -f
# Hot-edit sin restart
openclaw config set agents.defaults.model.primary 'minimax/MiniMax-M2.7'
openclaw gateway reload
# Salud de los auth profiles
openclaw doctor
openclaw models auth list
# Qué modelo sirvió el último mensaje
openclaw sessions list
openclaw sessions history --session <id>
# Actualizar OpenClaw
npm install -g openclaw@latest
systemctl --user restart openclaw
Sacar un tier de circulación:
# Deshabilitar temporalmente
openclaw models auth disable openrouter:default
# Rehabilitar
openclaw models auth enable openrouter:default
# Forzar 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
# Sospechoso común: dmPolicy=allowlist con ID errado
openclaw config get channels.telegram.allowFrom
Significa que usé /model X donde X no está en agents.defaults.models. Lo agrego:
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
Si el array de fallbacks está vacío, reaplico el JSON del paso 3.3.
openclaw onboard --auth-choice openai-codex-oauth --reauth
El sandbox Docker compite con Node por RAM en host de 4 GB. O subo a Lightsail large_3_0 (8 GB, $40/mes), o apago el sandbox (agents.defaults.sandbox.mode = "off", menos seguro).