No description
  • Python 60%
  • Go 36.5%
  • Dockerfile 1.8%
  • Shell 1.7%
Find a file
2026-10-18 15:03:45 -03:00
bot F2. Cerrada 2026-10-18 15:03:45 -03:00
docs F2. Cerrada 2026-10-18 15:03:45 -03:00
wa F1. Cerrada 2026-10-13 14:57:12 -03:00
.env.example F2. Cerrada 2026-10-18 15:03:45 -03:00
.gitignore Sidecar funcional. Enlaza con cuenta whatsapp. 2026-10-13 00:26:12 -03:00
docker-compose.yml F2. Cerrada 2026-10-18 15:03:45 -03:00
ops Inicial. Estructura minima 2026-10-12 00:32:39 -03:00
README.md F2. Cerrada 2026-10-18 15:03:45 -03:00

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 por politica/ (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 su Agent), detrás de lenguaje/. 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 @lid y 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 build y go test pasan.
  • Contrato de la frontera wa ↔ bot fijado. El evento normalizado es el struct Evento de wa/cmd/wa/whatsapp.go (los tags json son la especificación): lleva las dos formas del identificador —imprescindibles para reconocer a quien escribe como @lid— y sale por dos canales, consola y POST /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), con thinking apagado 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 con pytest, 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, SendPasskeyConfirmation y DangerousInternals().GetPasskeyRequestOptions, más los eventos PairPasskey*. 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

  1. 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.
  2. 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.
  3. F4: planificador de recordatorios idempotentes con reintentos.
  4. F5: medios en el sidecar. El evento ya lleva el tipo, pero un audio o una imagen llegan con el texto vacío; el contrato no cambia.