Skill: coorops25/emailagy (version 1.0.0)

--- name: smart-email-agent version: "3.0.0" description: > All-in-one Gmail agent for OpenClaw. Fuses email-reader, email-organizer, email-analyzer, email-responder, email-scheduler, and email-reporter into a single skill with token-optimizer integration and a self-improvement engine. Use this skill for ANYTHING email-related: checking inbox, searching messages, organizing labels, classifying/prioritizing, drafting replies, scheduling automation, generating reports, or reviewing costs. Triggers on: correo, email, inbox, bandeja, spam, draft, borrador, responder, organizar, etiquetar, archivar, informe, estadísticas, notificame, automatiza, revisar mensajes, prioriza, cuanto cuesta, presupuesto, mejora el agente. Requires: gog CLI (primary) or Gmail API Python scripts (fallback). tags: [email, gmail, gog, inbox, spam, organizer, analyzer, responder, scheduler, reporter, cost, optimization, self-improvement, openclaw] metadata: openclaw: emoji: "🧠📬" requires: bins: [gog] env: - name: GOG_ACCOUNT description: "Dirección Gmail (ej: [REDACTED_SECRET])" - name: ANTHROPIC_API_KEY description: "Para análisis y borradores con IA" - name: EMAIL_BUDGET_USD description: "Presupuesto mensual en USD (default: 1.00)" env_optional: - name: GMAIL_CREDENTIALS_PATH description: "Fallback: ruta a credentials.json de Gmail API" - name: NOTIFY_CHANNEL description: "Canal de notificaciones (telegram/slack/whatsapp)" - name: SAFE_BROWSING_API_KEY description: "Detección de phishing con Google Safe Browsing" --- # Smart Email Agent v3 — Gmail All-in-One Un solo skill que reemplaza los 6 skills del pack original. Punto de entrada único para toda la gestión de correo. > **Lazy loading activo:** este skill se carga completo (~500 tokens). > NO cargues los 6 skills individuales — sería 4.800 tokens desperdiciados. --- ## PARTE 1 — LEER CORREOS (email-reader) ### Herramienta principal: `gog` CLI ```bash # Verificar prerequisitos which gog || echo "Instalar: npm i -g gogcli OR brew install gogcli" echo $GOG_ACCOUNT # debe estar configurado # Autenticar si es la primera vez gog auth add $GOG_ACCOUNT ``` ### Comandos esenciales ```bash # No leídos en inbox (acción por defecto cuando el usuario dice "revisa mi correo") gog gmail search 'in:inbox is:unread' --max 5 --format minimal --json # Buscar por criterio — usar sintaxis Gmail gog gmail search 'from:[REDACTED_SECRET] newer_than:3d' --max 10 --format minimal --json gog gmail search 'subject:factura has:attachment' --max 10 --format minimal --json gog gmail search 'in:spam is:unread' --max 20 --format minimal --json # Leer correo completo gog gmail get <message_id> --format full --json # Leer hilo completo gog gmail thread <thread_id> --format minimal --json ``` ### Operadores de búsqueda Gmail `from:` `to:` `subject:` `label:` `is:unread` `is:starred` `has:attachment` `newer_than:Nd` `older_than:Nd` `in:inbox` `in:sent` `in:spam` `in:trash` `filename:ext` ### Flujo estándar de lectura 1. Verificar `gog` en PATH y `GOG_ACCOUNT` configurado 2. Construir query desde la intención del usuario (preguntar si es ambiguo) 3. Ejecutar con `--format minimal --json --max N` 4. Parsear JSON → presentar lista formateada: ``` 📬 5 correos no leídos: 1. De: Juan García <[REDACTED_SECRET]> | Asunto: Propuesta Q2 | Hace 2h Vista previa: Hola, te mando el resumen de... | ID: msg_abc123 ``` 5. Ofrecer: leer completo / buscar más / actuar sobre el mensaje ### Reglas de lectura - SIEMPRE usar `--format minimal --json --max N` (N=5 por defecto) - Nunca mostrar JSON crudo; nunca leer contenido completo sin pedirlo - Preservar IDs para acciones de seguimiento - Sin resultados → confirmar criterios, sugerir términos más amplios - Solo lectura — enviar/responder requiere la sección RESPONDER (Parte 4) - No guardar contenido de correos en MEMORY.md salvo que el usuario lo pida ### Errores comunes | Error | Causa | Solución | |---|---|---| | `gog: command not found` | gog no instalado | `npm i -g gogcli` o `brew install gogcli` | | `GOG_ACCOUNT not set` | Variable no configurada | Pedir al usuario su email Gmail | | Token expirado | OAuth vencido | `gog auth add <email>` | | API error 429 | Rate limit | Esperar 60s, reintentar con backoff | --- ## PARTE 2 — ORGANIZAR (email-organizer) ### Jerarquía corporativa Corp/ (azul) ``` Corp/ ├── Interno/ │ ├── Management ← Gerencia, Directores │ ├── Tech & Ops ← Desarrollo, Soporte, Operaciones │ ├── Commercial ← Ventas, Marketing │ ├── Admin & HR ← Jurídica, RRHH, Contabilidad │ └── Team ← Resto del equipo @empresa.com ├── Partners & Clientes/ │ └── [empresa] ← Wolkvox, Masiv, Unisanitas, Nuva, etc. ├── Proveedores/ │ └── [proveedor] ← Google, Microsoft, AWS, etc. └── Sistema/ ├── DMARC ├── Notificaciones ├── Alertas └── No-Reply ``` ### Comandos de organización con gog ```bash # Crear etiqueta gog gmail label create "Corp/Interno/Tech & Ops" # Aplicar etiqueta a mensaje gog gmail label apply <message_id> "Corp/Interno/Management" # Mover correo (quitar INBOX + aplicar etiqueta) gog gmail modify <message_id> --add-label "Corp/Partners & Clientes/Wolkvox" --remove-label INBOX # Archivar (quitar INBOX sin borrar) gog gmail modify <message_id> --remove-label INBOX # Mover a spam gog gmail modify <message_id> --add-label SPAM --remove-label INBOX # Mover a papelera gog gmail trash <message_id> # Operación batch (múltiples IDs) gog gmail batch-modify --ids id1,id2,id3 --add-label "Corp/Sistema/No-Reply" --remove-label INBOX ``` ### Reglas de routing automático Antes de llamar a la IA, aplicar estas reglas sin costo: ``` from_domain @empresa.com + from_name contiene [Linda, Rafael, Director] → Corp/Interno/Management from_domain @empresa.com + from_name contiene [Tech, Dev, Soporte] → Corp/Interno/Tech & Ops from_domain @empresa.com → Corp/Interno/Team from contiene noreply / no-reply / donotreply → Corp/Sistema/No-Reply subject contiene DMARC / SPF / DKIM → Corp/Sistema/DMARC subject contiene alerta / alert / warning → Corp/Sistema/Alertas ``` Guardar y mantener estas reglas en `corp_routing_rules.json`. ### Protocolo de confirmación NUNCA ejecutar acciones destructivas sin confirmación explícita: ``` ⚠️ Pendiente de confirmación: → Mover 22 correos a Corp/Sistema/No-Reply → Eliminar etiqueta "noreply" (ya vacía) Esto NO borra correos, solo reorganiza etiquetas. ¿Confirmas? (sí/no) ``` --- ## PARTE 3 — ANALIZAR Y CLASIFICAR (email-analyzer) ### Decisión de modelo ANTES de analizar ``` ¿Tarea es clasificar / detectar spam / routing? → claude-haiku-4-5-20251001 (batch de 10-20 correos, ~$0.00009/correo) ¿Tarea es extraer tareas y fechas de correos importantes? → claude-haiku-4-5-20251001 (body[:800], ~$0.00015/correo) ¿El presupuesto está > 80% gastado? → forzar haiku para TODO, sin borradores automáticos ¿El presupuesto está > 95% gastado? → cero llamadas IA, solo reglas locales ``` **Opus: PROHIBIDO para tareas de email.** **Sonnet: solo para borradores (ver Parte 4).** ### Pipeline de reducción de tokens (aplicar siempre) ```python # 1. Pre-filtro sin IA (resolver antes de gastar tokens) # - Dominio en corp_routing_rules.json → etiquetar directo # - from en known_spam_domains.txt → spam directo # - message_id ya en analysis_cache → reutilizar resultado # Objetivo: resolver 60-70% a costo $0.00 # 2. Recortar campos al mínimo necesario CAMPOS = { 'clasificacion': ['from', 'subject', 'snippet[:100]'], # ~30 tokens 'prioridad': ['from', 'subject', 'body[:400]'], # ~150 tokens 'tareas_fechas': ['from', 'subject', 'body[:800]'], # ~250 tokens } # 3. Limpiar texto def limpiar(texto, limite): texto = re.sub(r'<[^>]+>', '', texto) # quitar HTML texto = re.sub(r'https?://\S+', '[URL]', texto) # comprimir URLs texto = re.sub(r'\s+', ' ', texto).strip() return texto[:limite] # 4. Batch: NUNCA menos de 10 correos por llamada # Esperar hasta tener 10-20 correos pendientes BATCH_MIN = 10 BATCH_MAX = 20 ``` ### Prompt de análisis en batch (Haiku) ``` SYSTEM (idéntico siempre — para prompt caching): Eres un clasificador de correos corporativos. Analiza cada correo y devuelve SOLO JSON array. Sin texto extra. Para cada ítem: {"idx":N,"corp_label":"...","categoria":"spam|importante|informativo|sistema|otro", "prioridad":0-10,"es_spam":bool,"necesita_respuesta":bool, "tiene_phishing":bool,"tareas":[],"fecha_limite":"ISO o null","razon":"máx 10 palabras"} USER: Analiza: [JSON array de hasta 20 correos con from+subject+snippet[:100]] ``` ### Presentación de resultados ``` 🤖 Análisis — 47 correos procesados ⚡ Sin IA (pre-filtro): 31 (66%) → $0.000 🧠 Con Haiku (2 batches): 16 → $0.006 📊 Resultado: 🔵 Corp/Interno/Management: 2 (prioridad alta) 🔵 Corp/Partners & Clientes: 8 🔵 Corp/Sistema/No-Reply: 14 🗑️ Spam: 12 ⚠️ Phishing detectado: 1 → ALERTA 📋 Con tareas pendientes: 4 Críticos: [10/10] [REDACTED_SECRET] — "Aprobación contrato urgente" Tarea: confirmar antes del viernes ``` --- ## PARTE 4 — RESPONDER Y REDACTAR (email-responder) ### Cuándo usar Sonnet vs Haiku para borradores ``` Prioridad >= 8 → claude-sonnet-4-6 (calidad importa) Prioridad 5-7 → claude-haiku-4-5-20251001 (suficiente, más barato) Prioridad < 5 → NO generar borrador automático ``` Máximo 3 borradores por sesión cuando presupuesto < 60%. Máximo 1 borrador por sesión cuando presupuesto 60-80%. Cero borradores automáticos cuando presupuesto > 80%. ### Flujo de respuesta ```bash # 1. Leer el hilo completo gog gmail thread <thread_id> --format minimal --json # 2. Preparar contexto recortado para la IA # Solo: from + subject + body[:600] del último mensaje + resumen del hilo anterior ``` ### Prompt de generación de borrador ``` Redacta una respuesta profesional y concisa (máx 150 palabras). Solo el cuerpo del mensaje, sin asunto ni encabezados. Tono: profesional pero cercano. Firma: [NOMBRE_USUARIO] Hilo: [RESUMEN + ÚLTIMO MENSAJE RECORTADO] ``` ### Presentar borrador al usuario ``` ✍️ Borrador para: [REDACTED_SECRET] Re: Propuesta Q2 2026 ──────────────────────── Hola Juan, Gracias por el resumen. Me parece viable la dirección propuesta. ¿Podemos agendar una llamada esta semana? Saludos, [Tu nombre] ──────────────────────── [1] Guardar borrador [2] Editar [3] Enviar ahora [4] Descartar ``` ### Enviar con gog ```bash # Guardar como borrador gog gmail draft create --to "[REDACTED_SECRET]" \ --subject "Re: Propuesta Q2 2026" \ --body "Hola Juan,..." \ --reply-to <message_id> # Enviar borrador guardado gog gmail draft send <draft_id> # Enviar directamente (SIEMPRE pedir confirmación antes) gog gmail send --to "[REDACTED_SECRET]" --subject "..." --body "..." ``` ### Templates de respuesta rápida ``` acuse_recibo: "Recibido, te respondo a la brevedad." confirmar_reunion: "Confirmado para [fecha/hora]. Hasta entonces." solicitar_info: "Necesito más información sobre X para proceder." ausencia: "Estoy fuera hasta [fecha]. Respondo a mi regreso." ``` ### Follow-ups automáticos ```bash # Buscar correos enviados sin respuesta en últimos 5 días gog gmail search 'in:sent newer_than:5d' --max 20 --format minimal --json # Cruzar con INBOX para detectar cuáles no tienen respuesta ``` --- ## PARTE 5 — AUTOMATIZAR (email-scheduler) ### Heartbeat optimizado: 55 minutos **Por qué 55 min:** el caché de Anthropic expira a los 60 minutos. Con heartbeat de 55 min, el agente mantiene el caché caliente → cada mensaje paga cache-read en lugar de cache-write (3.75x más barato). ```json // ~/.openclaw/openclaw.json { "agents": { "email-assistant": { "heartbeat": { "every": "55m" }, "model": [REDACTED_SECRET] } } } ``` ### Cron jobs recomendados ```json { "cron": { "jobs": [ { "id": "email-priority-check", "schedule": "*/55 * * * *", "description": "Revisar correos importantes — modelo Haiku", "message": "Revisa inbox no leídos. Si hay prioridad >= 8, notifícame.", "model": [REDACTED_SECRET], "enabled": true }, { "id": "email-spam-cleanup", "schedule": "0 8 * * *", "description": "Limpieza diaria de spam — solo reglas locales, costo $0", "message": "Aplica reglas locales de spam. Sin llamadas IA.", "model": [REDACTED_SECRET], "enabled": true }, { "id": "email-weekly-report", "schedule": "0 9 * * MON", "description": "Informe semanal", "message": "Genera informe semanal de correos con email-reporter.", "model": [REDACTED_SECRET], "enabled": true } ] } } ``` **Regla de oro para crons:** SIEMPRE especificar `claude-haiku-4-5-20251001`. Usar Opus para un cron de 10 tareas/día = $17.70/mes extra innecesario. ### HEARTBEAT.md de correo ```markdown ## Email Heartbeat — Modelo: claude-haiku-4-5-20251001 ### Check de correos (cada 55 min) 1. gog gmail search 'in:inbox is:unread' --max 10 --format minimal --json 2. Aplicar reglas locales de corp_routing_rules.json 3. Si hay correo con keywords urgente/crítico/emergencia → notificar 4. Si no hay urgentes → HEARTBEAT_OK (silencio) ### Check de spam (cada 2h, sin IA) 1. gog gmail search 'in:spam is:unread' --max 50 --format minimal --json 2. Aplicar known_spam_domains.txt → mover a trash directo 3. Sin llamadas a IA Quiet hours: 23:00–07:00 → HEARTBEAT_OK automático ``` ### Gmail Push Notifications (tiempo real) ```bash # Configurar webhook Pub/Sub python3 scripts/setup_pubsub.py --topic "email-agent-notifications" # El webhook dispara cuando llega un correo nuevo: openclaw message "Nuevo correo. Revisa con gog y notifícame si es importante." ``` ### Activar automatización completa ``` Usuario: "Activa el agente de correo en modo automático" Agente: 1. Verificar: gog auth status 2. Crear cron jobs recomendados (ver arriba) 3. Copiar HEARTBEAT.md al workspace 4. Preguntar: ¿activar Gmail Push para tiempo real? 5. Confirmar canal de notificaciones (NOTIFY_CHANNEL) 6. "✅ Agente activado. Reviso cada 55 min. Te aviso si hay algo importante." ``` --- ## PARTE 6 — INFORMES Y ESTADÍSTICAS (email-reporter) ### Tipos de informe ```bash # Resumen del día (al final de sesión — SIEMPRE mostrar) # Ver sección "Resumen de costos" más abajo # Estadísticas de spam gog gmail search 'in:trash newer_than:30d' --max 100 --format minimal --json # Parsear y agrupar por dominio remitente # Tareas pendientes en correos # Consultar analysis_cache donde tareas[] no está vacío y sin respuesta # Log de prompts IA detectados cat .learnings/PROMPTS_DETECTADOS.md # Historial de acciones del agente cat email_audit.log | tail -50 # Deshacer última acción gog gmail modify <ids_from_audit_log> --remove-label TRASH --add-label INBOX ``` ### Resumen de costos — mostrar al cerrar cada sesión ``` 💰 Sesión de hoy Correos procesados: 47 ├─ Sin IA (reglas): 31 (66%) → $0.000 ├─ Haiku (2 batches): 14 → $0.005 └─ Sonnet (borradores): 2 → $0.005 Tokens consumidos: 5.090 Costo sesión: $0.010 Ahorro estimado: $0.040 (80% vs. sin optimizar) Presupuesto mes: $X.XX gastado / $Y.YY total (N%) Proyección mes: $Z.ZZ ``` --- ## PARTE 7 — MOTOR DE AUTO-MEJORA ### Cuándo capturar un aprendizaje | Evento | Archivo | ID | |---|---|---| | Usuario corrige clasificación | `.learnings/LEARNINGS.md` | `LRN-YYYYMMDD-NNN` | | Costo sesión > 2x el promedio | `.learnings/LEARNINGS.md` | `LRN-YYYYMMDD-NNN` | | Error de API (rate limit, auth) | `.learnings/ERRORS.md` | `ERR-YYYYMMDD-XXX` | | Optimización reduce costos >10% | `.learnings/LEARNINGS.md` | `LRN-YYYYMMDD-NNN` | | Remitente recurrente sin regla | `.learnings/LEARNINGS.md` | `LRN-YYYYMMDD-NNN` | ### Formato ```markdown ## [LRN-YYYYMMDD-NNN] <tipo> **Logged**: ISO timestamp **Priority**: low | medium | high **Status**: pending | applied | promoted **Area**: cost | classification | routing | api | spam | drafts ### Summary Una línea con el aprendizaje y su impacto. ### Details Qué pasó. Qué se asumía vs. qué era verdad. ### Action Cambio concreto: qué archivo editar, qué valor cambiar. ### Impact Ahorro estimado: $X/mes | Tokens -N% --- ``` ### Ciclo al cerrar sesión ``` 1. Revisar .learnings/ con Status=pending 2. ¿Learnings con Impact > $0.01/mes? → Proponer: "Aprendí que X. ¿Lo aplico a las reglas?" → Si acepta → editar corp_routing_rules.json → Status=applied 3. ¿3+ learnings del mismo dominio/patrón? → Promover a regla permanente sin preguntar → Status=promoted 4. Reportar: "Apliqué N mejoras. Ahorro estimado: $X/mes" ``` ### Efecto compuesto del aprendizaje ``` Mes 1: ~$0.50/mes (0 reglas) Mes 2: ~$0.30/mes (10 reglas aprendidas) Mes 3: ~$0.18/mes (25 reglas) Mes 6: ~$0.10/mes (60+ reglas) ``` --- ## PARTE 8 — CONTROL DE PRESUPUESTO ### Cuatro modos operativos | % gastado | Modo | Restricciones | |---|---|---| | 0–59% | Normal ✅ | Todo habilitado | | 60–79% | Ahorro leve 🟡 | Avisar. Máx 2 borradores/sesión | | 80–94% | Ahorro fuerte 🟠 | Solo Haiku. Sin borradores auto. Batch obligatorio ≥20 | | 95–100% | Emergencia 🔴 | Cero IA. Solo `gog` + reglas locales | ### Diagnósticos con token-optimizer (si está instalado) ```bash /context list # qué archivos consumen tokens ahora /usage full # tokens + costo por respuesta /usage cost # resumen acumulado de sesión /status # modelo activo, % contexto python3 scripts/token_tracker.py check # estado del budget diario python3 scripts/model_router.py "analizar correos nuevos" # qué modelo usar ``` --- ## Referencias — leer cuando necesites más detalle - `references/cost-optimization.md` — Técnicas avanzadas: prompt caching, deduplicación semántica, modo emergencia - `references/learning-patterns.md` — Patrones de auto-mejora y ciclo de vida de learnings - `hooks/openclaw-handler.js` — Inyecta estado de presupuesto + modo activo al inicio de sesión - `scripts/init_orchestrator.py` — Setup inicial: verifica gog, crea budget_tracker, SKILLS_INDEX - `assets/HEARTBEAT.email.md` — Plantilla lista para copiar al workspace

path: scripts/init_orchestrator.py
content: #!/usr/bin/env python3
"""Smart Email Agent v3 — Setup inicial."""
import json, os, shutil, sys
from datetime import datetime
from pathlib import Path

WS = Path.home() / '.openclaw' / 'workspace'

def ok(m):   print(f"  ✅ {m}")
def warn(m): print(f"  ⚠️  {m}")
def err(m):  print(f"  ❌ {m}"); 

def check_prereqs():
    print("\n── Prerequisitos")
    if shutil.which('gog'): ok("gog CLI instalado")
    else: warn("gog no encontrado — instalar: npm i -g gogcli  o  brew install gogcli")
    gog_acc = os.environ.get('GOG_ACCOUNT')
    if gog_acc: ok(f"GOG_ACCOUNT = {gog_acc}")
    else: err("GOG_ACCOUNT no configurado — requerido"); return False
    if os.environ.get('ANTHROPIC_API_KEY'): ok("ANTHROPIC_API_KEY configurado")
    else: err("ANTHROPIC_API_KEY no configurado — requerido para análisis IA"); return False
    for k in ['EMAIL_BUDGET_USD', 'NOTIFY_CHANNEL', 'SAFE_BROWSING_API_KEY']:
        v = os.environ.get(k)
        if v: ok(f"{k} = {v}")
        else: warn(f"{k} no configurado (opcional)")
    return True

def init_budget():
    print("\n── Presupuesto")
    f = WS / 'budget_tracker.json'
    if f.exists():
        d = json.loads(f.read_text())
        pct = round(d['spent_usd'] / d['budget_usd'] * 100)
        ok(f"${d['spent_usd']:.3f} / ${d['budget_usd']:.2f} ({pct}%) — mes {d['month']}")
        return
    budget = float(os.environ.get('EMAIL_BUDGET_USD', '1.00'))
    f.write_text(json.dumps({
        "month": datetime.now().strftime('%Y-%m'),
        "budget_usd": budget, "spent_usd": 0.0,
        "alert_at_pct": [60, 80, 95], "sessions": []
    }, indent=2))
    ok(f"Inicializado: ${budget:.2f}/mes")

def init_learnings():
    print("\n── Learnings")
    d = WS / '.learnings'
    d.mkdir(exist_ok=True)
    for fn in ['LEARNINGS.md', 'ERRORS.md']:
        fp = d / fn
        if not fp.exists():
            fp.write_text(f"# {fn.replace('.md','')}\n\n")
            ok(f"Creado: .learnings/{fn}")
        else:
            n = fp.read_text().count('**Status**: pending')
            ok(f".learnings/{fn} — {n} pendientes")

def check_gog_auth():
    print("\n── Autenticación Gmail")
    import subprocess
    r = subprocess.run(['gog', 'auth', 'status'], capture_output=True, text=True)
    if r.returncode == 0: ok(f"Autenticado: {r.stdout.strip()}")
    else: warn(f"No autenticado — ejecutar: gog auth add {os.environ.get('GOG_ACCOUNT', '[REDACTED_SECRET]')}")

if __name__ == '__main__':
    print("═══════════════════════════════════════")
    print("  Smart Email Agent v3 — Setup Inicial")
    print("═══════════════════════════════════════")
    if not check_prereqs(): sys.exit(1)
    init_budget()
    init_learnings()
    check_gog_auth()
    print("\n✅ Listo.")
    print("   Activar hook: openclaw hooks enable smart-email-agent\n")


path: references/cost-optimization.md
content: # Técnicas de Reducción de Costos

## Batch size — el multiplicador más importante

| Batch | Llamadas/100 correos | Costo relativo |
|---|---|---|
| 1  | 100 | 100% |
| 10 | 10  | 22% |
| 20 | 5   | 15% |

Regla: esperar mínimo 10 correos antes de llamar a la IA.
Buffer: máx 2 minutos de espera para completar el batch.

## Prompt system caching

El system prompt de análisis es siempre el mismo (~300 tokens).
No incluir fecha/hora en el system prompt — invalida el caché.
Anthropic cobra 3.75x más por cache-write vs cache-read.

## Deduplicación por subject normalizado

```python
def normalizar(s):
    s = re.sub(r'^(Re:|Fwd:|RV:)\s*', '', s, flags=re.I)
    s = re.sub(r'\b\d{4,}\b', 'NUM', s)   # números de factura/ticket
    return s.lower().strip()
# Si el subject normalizado ya fue analizado esta semana → reutilizar
```

## Análisis tiered

```
Nivel 0 — Reglas locales ($0.00)         → 60-70% de correos
Nivel 1 — Haiku: from+subject+100chars   → 20-25% de correos
Nivel 2 — Haiku: body[:400]              → correos importantes del N1
Nivel 3 — Sonnet: thread completo        → borradores prio >= 8 únicamente
```

## Cache-TTL heartbeat (55 min)

Anthropic cache TTL = 60 minutos.
Heartbeat cada 55 min = caché siempre caliente = paga cache-read.
Silencio > 60 min = siguiente mensaje paga cache-write (3.75x más caro).

## Modo emergencia (> 95% del presupuesto)

Disponible sin IA: gog para leer/buscar, reglas locales para clasificar,
sqlite para informes, borradores existentes para ver (no generar).
El usuario mantiene 70% de funcionalidad a costo $0.00.

## Proyección de ahorro acumulado

```
Mes 1: $0.50/mes  (0 reglas)
Mes 3: $0.18/mes  (25 reglas aprendidas)
Mes 6: $0.10/mes  (60+ reglas)
```
Cada regla aprendida elimina permanentemente ese costo futuro.


path: references/learning-patterns.md
content: # Patrones de Auto-Mejora

## Patrones que eliminan costo IA

**Remitente siempre ignorado** (3+ veces archivado sin leer)
→ Añadir a known_ignore_senders.txt. Auto-archivo, skip IA.

**Dominio corporativo nuevo** (2+ correos clasificados igual)
→ Añadir a corp_routing_rules.json. Routing directo, skip IA.

**Newsletter recurrente** (mismo from + subject normalizado)
→ Marcar leído + archivar automáticamente, sin IA.

## Patrones de costo

**Batches pequeños** (avg < 10 correos/llamada)
→ Activar buffer de 2 min. Ahorro: 60% en llamadas IA.

**Re-análisis de correos ya procesados**
→ Verificar analysis_cache ANTES de llamar a la IA. Ahorro: 100% de re-análisis.

**Rate limit 429 a hora fija** (ej: 9:00-9:30am)
→ Retrasar batch a 9:45am. Backoff: 30s → 60s → 120s.

## Cuándo NO registrar un learning

- Error de red único no repetido
- Clasificación correcta (solo capturar errores)
- Learning idéntico existe en los últimos 7 días
- Impacto estimado < $0.001/mes

## Ciclo de vida

```
pending → (usuario aprueba OR patrón 3x) → applied
applied → (funciona 2+ semanas)          → promoted → archivado
```

Los learnings promoted no aparecen en el resumen de sesión.
Solo visibles cuando el usuario pide el historial completo de mejoras.


path: hooks/HOOK.md
content: ---
name: smart-email-agent
description: "Inyecta estado de presupuesto, modo activo y learnings pendientes al inicio de sesión"
metadata:
  openclaw:
    emoji: "🧠📬"
    events: ["agent:bootstrap"]
---

# Smart Email Agent — Hook de Bootstrap

Inyecta al inicio de cada sesión:
1. Estado del presupuesto mensual y modo activo (Normal/Ahorro/Emergencia)
2. Modelo permitido según nivel de gasto
3. Learnings pendientes de aplicar

## Habilitar
```bash
openclaw hooks enable smart-email-agent
```
## Setup inicial
```bash
python3 ~/.openclaw/skills/smart-email-agent/scripts/init_orchestrator.py
```


path: hooks/openclaw-handler.js
content: /**
 * Smart Email Agent v3 — OpenClaw Bootstrap Hook
 * Inyecta estado de presupuesto, modo activo y learnings pendientes.
 */
const fs   = require('fs');
const path = require('path');
const WS   = path.join(process.env.HOME, '.openclaw', 'workspace');

function getBudgetInfo() {
  const f = path.join(WS, 'budget_tracker.json');
  try {
    if (!fs.existsSync(f)) return '⚠️  budget_tracker.json no encontrado. Ejecuta: python3 scripts/init_orchestrator.py';
    const d   = JSON.parse(fs.readFileSync(f, 'utf8'));
    const pct = Math.round(d.spent_usd / d.budget_usd * 100);
    const proj = (d.spent_usd / new Date().getDate() * 31).toFixed(3);
    let mode, model, restriction = null;
    if      (pct >= 95) { mode = '🔴 EMERGENCIA';    model = 'ninguno — cero IA';               restriction = 'CRITICO: No llamar a Anthropic API. Solo gog + reglas locales.'; }
    else if (pct >= 80) { mode = '🟠 AHORRO FUERTE'; model = 'claude-haiku-4-5-20251001 forzado'; restriction = 'ACTIVO: Solo Haiku. Sin borradores. Batch >= 20.'; }
    else if (pct >= 60) { mode = '🟡 AHORRO LEVE';   model = 'Haiku preferido';                 restriction = 'AVISO: Max 2 borradores/sesión.'; }
    else                { mode = '✅ NORMAL';          model = 'Haiku → clasificar | Sonnet → borradores prio≥8'; }
    const lines = [
      '## Smart Email Agent — Contexto',
      `Presupuesto: $${d.spent_usd.toFixed(3)} / $${d.budget_usd.toFixed(2)} (${pct}%) | Proyección: $${proj} | Mes: ${d.month}`,
      `Modo: ${mode} | Modelo: ${model}`,
    ];
    if (restriction) lines.push(`> ⚡ ${restriction}`);
    return lines.join('\n');
  } catch (e) { return `⚠️  Error leyendo presupuesto: ${e.message}`; }
}

function getPendingLearnings() {
  const f = path.join(WS, '.learnings', 'LEARNINGS.md');
  try {
    if (!fs.existsSync(f)) return null;
    const n = (fs.readFileSync(f, 'utf8').match(/\*\*Status\*\*: pending/g) || []).length;
    return n > 0 ? `Learnings pendientes: ${n} — revisar al cerrar sesión.` : null;
  } catch { return null; }
}

const handler = async (event) => {
  if (!event?.context || !Array.isArray(event.context.bootstrapFiles)) return;
  if (event.type !== 'agent' || event.action !== 'bootstrap') return;
  if ((event.sessionKey || '').includes(':subagent:')) return;

  const parts = [getBudgetInfo()];
  const l = getPendingLearnings();
  if (l) parts.push(l);
  parts.push('Lazy loading: NO cargar los 6 skills de email por separado. Este skill los fusiona todos.');

  event.context.bootstrapFiles.push({
    path: 'SMART_EMAIL_CTX.md',
    content: parts.join('\n'),
    virtual: true,
  });
};

module.exports = handler;
module.exports.default = handler;


path: assets/HEARTBEAT.email.md
content: # Email Heartbeat — Smart Email Agent v3
# Intervalo: 55 minutos (cache-TTL alignment)
# Modelo: SIEMPRE claude-haiku-4-5-20251001

## Check 1 — Correos importantes (cada 55 min)
gog gmail search 'in:inbox is:unread' --max 10 --format minimal --json
→ Aplicar reglas de corp_routing_rules.json (sin IA)
→ Si hay keywords urgente/crítico/emergencia en subject → notificar
→ Si no → HEARTBEAT_OK (silencio total)

## Check 2 — Spam (cada 2h, costo $0.00)
gog gmail search 'in:spam is:unread' --max 50 --format minimal --json
→ Aplicar known_spam_domains.txt → trash directo
→ Sin llamadas IA

## Check 3 — Reporte diario (8am)
→ email-reporter --period day con Haiku
→ Incluir resumen de costos del día

Quiet hours: 23:00–07:00 → HEARTBEAT_OK automático


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)