- Python 60%
- Go 36.5%
- Dockerfile 1.8%
- Shell 1.7%
| bot | ||
| docs | ||
| wa | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| ops | ||
| README.md | ||
PistachoBot
Bot de casa por WhatsApp: la familia le escribe al gato y el gato ayuda con la organización del hogar (listas, tareas, notas, recordatorios).
Estado actual: F0 y F1 hechas; F2 implementada, pendiente de prueba en vivo. El sidecar
wa/entrega cada mensaje normalizado al bot, que pasa porpolitica/(allowlist dura más deduplicación) y conversa con contexto en memoria por chat. En el grupo el modelo decide si interviene; en los DMs responde siempre. Hay agrupación de 4 s. El motor es DeepSeek Flash a través de PydanticAI solo como capa de modelo (sin suAgent), detrás delenguaje/. La implementación está probada con dobles y 49 tests; la prueba en vivo de F2 queda pendiente de la clave de DeepSeek. Todavía no hay herramientas, persistencia, planificador ni descarga de medios.
Documentación
Toda la decisión de diseño vive en docs/. Léela antes de tocar código.
| Documento | Contenido |
|---|---|
| ARQUITECTURA.md | Objetivo, principios, stack, capas, flujos, fases |
| SETUP.md | Cuenta de WhatsApp, VPS, enlace, contenedor, respaldos, aceptación |
| CONSIDERACIONES.md | Los seis críticos (A1–A6) y operación, privacidad y producto |
Forma del sistema
Dos procesos, una frontera local. El reparto no es solo comodidad: es un límite de seguridad (ver CONSIDERACIONES §A6).
WhatsApp (servidores)
↕ websocket saliente
wa/ sidecar Go (whatsmeow) — protocolo y sesión. ÚNICO proceso que ve session.db
↕ HTTP en la red interna (sin publicar puertos)
bot/ servicio Python (uv) — política, agente, proveedor de lenguaje, herramientas, persistencia, planificador
| Directorio | Qué es |
|---|---|
wa/ |
Sidecar Go sobre whatsmeow. Conectar, reenviar eventos, enviar, exponer salud. No interpreta ni decide. |
bot/ |
Proyecto uv (Astral). El cerebro: adaptador, política de dominio, agente, proveedor de lenguaje, herramientas, persistencia, planificador. |
docs/ |
El diseño. |
datos/ |
Datos y secretos. No se versiona. |
Las capas de bot/src/pistacho/ siguen exactamente la sección 5 de
ARQUITECTURA.md.
Puesta en marcha
Ver SETUP.md. Resumen del arranque local:
cp .env.example .env # en la RAÍZ, solo si no existe; rellenar los valores
# Incluye PISTACHO_LLM_API_KEY (DeepSeek) para que el gato converse (F2).
cd wa && go build -o wa ./cmd/wa && cd ..
# Enlazar el dispositivo (una sola vez, con el teléfono a mano):
./wa/wa -enlazar -telefono 56927758406
# Imprime un código de 8 caracteres que se teclea en el teléfono:
# WhatsApp → Ajustes → Dispositivos vinculados → Vincular un dispositivo
# → Vincular con número de teléfono
# Uso normal. Los dos desde la RAÍZ: el sidecar para encontrar datos/sesion/,
# el bot para leer el .env de la raíz (ver SETUP.md §2).
./wa/wa -bot-url http://127.0.0.1:8000 # consola + empuja los eventos al bot
bot/.venv/bin/pistacho # el bot: conversación con contexto (F2)
Operación diaria:
./ops estado # salud de ambos servicios
./ops errores 6h # errores recientes
./ops logs # seguir stdout de ambos
Resuelto
- F0 hecha con una cuenta real (12-oct-2026). Enlace por código de 8
caracteres, reconexión tras reiniciar sin volver a enlazar, presencia pasiva
(§A2) y las dos formas del identificador resueltas y reportadas (§B): el
contacto llegó como
@lidy su número aparece igual en la consola. Detalle en wa/README.md. - Go 1.26.8 instalado.
wa/compila y corre:go vet,go buildygo testpasan. - Contrato de la frontera
wa↔botfijado. El evento normalizado es el structEventodewa/cmd/wa/whatsapp.go(los tagsjsonson la especificación): lleva las dos formas del identificador —imprescindibles para reconocer a quien escribe como@lid— y sale por dos canales, consola yPOST /eventos. La entrega es de mejor esfuerzo a propósito: un intento, sin cola ni reintentos. Detalle y JSON de ejemplo en wa/README.md. - F1 hecha y verificada en vivo (13-oct-2026): eco con allowlist. Responde en el grupo de la casa y en los DMs permitidos, ignora y registra a quien no está en la lista, y no se responde a sí mismo (comprobado mandando desde el teléfono del gato al grupo, que sí está permitido). La decisión vive en politica/filtro.py —la única capa que puede bloquear— y la salida en adaptador/.
- F2 implementada (18-oct-2026): conversación con contexto en memoria por
chat, persona editable y decisión de intervenir en el grupo. Motor DeepSeek
Flash tras PydanticAI solo como capa de modelo (sin su
Agent), conthinkingapagado y modo JSON para la decisión del grupo. Agrupación de 4 s para no responder a cada línea. La lógica vive en agente/, el proveedor en lenguaje/ y la ventana en debounce.py. 49 tests conpytest, sin red. Falta la prueba en vivo con la familia: requiere la clave de DeepSeek y su consentimiento explícito (CONSIDERACIONES §C). - Riesgo A1 (passkey): descartado. El pin de whatsmeow elegido expone
SendPasskeyResponse,SendPasskeyConfirmationyDangerousInternals().GetPasskeyRequestOptions, más los eventosPairPasskey*. El obstáculo estaba en el binding de Python (neonize), no en el sidecar Go. Además, esta cuenta no cayó en la cohorte de passkey.
Pendiente
- Poner la clave de DeepSeek en
.env(PISTACHO_LLM_API_KEY) y hacer la prueba en vivo de F2. Antes, avisar a la familia de que el texto sale hacia DeepSeek (CONSIDERACIONES §C). Sin clave el bot arranca y no responde: falla cerrado, que es lo correcto pero no sirve para conversar. - F3: herramientas y persistencia. Listas, tareas y notas en SQLite; el contexto de F2 se reemplaza por la base con retención. Es la fase que sigue.
- F4: planificador de recordatorios idempotentes con reintentos.
- F5: medios en el sidecar. El evento ya lleva el
tipo, pero un audio o una imagen llegan con eltextovacío; el contrato no cambia.