Files
agents_and_robots/dev/issues/0039-dynamic-reminders-cron.md
T
egutierrez 52d5632d89 docs: crear issues 0036-0041 — nuevas features del sistema
Issues planificados:
- 0036: Claude Code streaming de progreso en Matrix
- 0037: Agente que crea otros agentes/bots via Matrix
- 0038: Webapps y dashboards embebidos en Element via widgets
- 0039: Recordatorios dinámicos y crons que invocan agentes
- 0040: Soporte para mensajes de voz (audio → STT)
- 0041: Videollamadas con agentes via LiveKit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 21:19:09 +00:00

13 KiB

0039 — Recordatorios dinamicos y crons que invocan agentes

Estado: pendiente

Objetivo

Extender el sistema cron para soportar (1) recordatorios dinamicos creados en runtime via tool calls del LLM ("recuerdame a las 3pm que...") y (2) un nuevo action kind agent_call que invoca a otro agente con un prompt, habilitando workflows automatizados agente-a-agente en horario.

Contexto

  • El scheduler actual (shell/cron/scheduler.go) soporta send_message y llm_prompt como action kinds, configurados estaticamente via YAML.
  • No existe forma de crear schedules en runtime: si un usuario pide "recuerdame X", el agente no puede programar un disparo futuro sin editar config.
  • El bus inter-agente (shell/bus/) ya permite comunicacion entre agentes via SendAndWait, pero no esta integrado con el cron.
  • ScheduledAction en internal/config/schema.go define los campos para send_message y llm_prompt pero no tiene campos para invocacion de agentes.
  • Las tools existentes siguen el patron subpackage en tools/ (ej: tools/file/, tools/ssh/, tools/clock/).
  • SQLite ya esta disponible via modernc (pure-Go, CGO_ENABLED=0) con el shim en cmd/launcher/sqlite.go.

Arquitectura

Fase 1 — Tipos puros y storage de reminders

pkg/reminder/types.go       NEW — tipo Reminder puro (datos, sin I/O)
shell/reminder/store.go     NEW — SQLite-backed store (Create, Delete, List, MarkFired, LoadActive)
shell/reminder/store_test.go NEW — tests CRUD del store

Pure core / impure shell:

  • pkg/reminder/ es 100% puro: solo define el struct Reminder y constantes. Sin imports de I/O.
  • shell/reminder/ es impuro: abre conexion SQLite, lee/escribe en disco.

Fase 2 — Tools de recordatorios

tools/reminder/reminder.go      NEW — create_reminder, delete_reminder, list_reminders
tools/reminder/reminder_test.go NEW — tests de validacion de params y parsing de tiempo
devagents/runtime.go               MOD — registrar reminder tools cuando config lo habilita

Fase 3 — Scheduler dinamico

shell/cron/scheduler.go     MOD — AddSchedule, RemoveSchedule, soporte para IDs dinamicos
shell/cron/actions.go       MOD — nuevo action kind "reminder" (mensaje personalizado a room/usuario)
shell/cron/scheduler_test.go MOD — tests de add/remove dinamico y one-shot auto-cleanup

Fase 4 — Agent-to-agent cron calls

internal/config/schema.go   MOD — campos AgentCall en ScheduledAction
shell/cron/actions.go       MOD — nuevo action kind "agent_call" usando shell/bus/
shell/cron/scheduler.go     MOD — inyeccion del bus como dependencia

Tareas

Fase 1 — Tipos puros y storage de reminders

  • 1.1 Crear pkg/reminder/types.go con struct Reminder:

    • Campos: ID string, UserID string, RoomID string, Message string, CronExpr string, OneShot bool, CreatedAt time.Time, FiredAt *time.Time
    • Constantes para estados: StatusActive, StatusFired, StatusDeleted
    • Sin imports de I/O, sin side effects
  • 1.2 Crear shell/reminder/store.go con Store struct:

    • Constructor New(dbPath string) (*Store, error) — abre SQLite, crea tabla si no existe
    • Create(ctx, Reminder) error — inserta un reminder
    • Delete(ctx, id string) error — borrado logico (marcar como deleted)
    • List(ctx, roomID string) ([]Reminder, error) — listar activos de una room
    • MarkFired(ctx, id string) error — marcar como disparado con timestamp
    • LoadActive(ctx) ([]Reminder, error) — cargar todos los activos (para startup)
    • Auto-crear tabla reminders en init (CREATE TABLE IF NOT EXISTS)
  • 1.3 Tests del store en shell/reminder/store_test.go:

    • Test CRUD completo: crear, listar, marcar fired, borrar
    • Test que LoadActive no retorna reminders fired ni deleted
    • Test de filtrado por roomID en List
    • Usar tmpdir para base de datos de test

Fase 2 — Tools de recordatorios

  • 2.1 Crear tools/reminder/reminder.go con NewCreateReminder(store, scheduler):

    • Params: message (string, required), time (string, required — "15:00", "2026-04-10 15:00", "en 30 minutos"), recurring (bool, optional, default false), cron (string, optional — expresion cron para recurrentes)
    • Parsear expresiones de tiempo naturales a cron expressions o timestamps absolutos
    • Generar ID unico (UUID o nanoid)
    • Persistir en store y registrar en scheduler
  • 2.2 Crear NewListReminders(store):

    • Sin params requeridos (usa roomID del contexto del mensaje)
    • Retorna lista formateada de reminders activos de la room
  • 2.3 Crear NewDeleteReminder(store, scheduler):

    • Params: id (string, required)
    • Borrar del store y remover del scheduler
    • Validar que el reminder pertenece a la room del solicitante
  • 2.4 Registrar tools en devagents/runtime.go:

    • Condicion: nueva seccion tools.reminders.enabled en config
    • Pasar referencia al store y al scheduler
  • 2.5 Anadir ReminderToolCfg a ToolsCfg en internal/config/schema.go:

    • Campos: Enabled bool, MaxPerRoom int (limite de reminders activos por room, default 50), DBPath string (default: data/reminders.db)
  • 2.6 Tests en tools/reminder/reminder_test.go:

    • Validacion de params requeridos
    • Parsing de formatos de tiempo: "15:00", "2026-04-10 15:00", "en 30 minutos", "manana a las 9"
    • Error si formato no reconocido
    • Rate limit: error si se excede MaxPerRoom

Fase 3 — Scheduler dinamico

  • 3.1 Anadir AddSchedule(id string, sc ScheduleCfg) error al Scheduler:

    • Registra un nuevo schedule en el cron runner en caliente
    • Guardar referencia al cron.EntryID para poder remover despues
    • Thread-safe (mutex sobre el mapa de entries)
  • 3.2 Anadir RemoveSchedule(id string) error al Scheduler:

    • Remover entry del cron runner por EntryID
    • Limpiar del mapa interno
  • 3.3 Implementar action kind reminder en shell/cron/actions.go:

    • Envia mensaje personalizado al room: "<prefix> @<user> Recordatorio: <mensaje>"
    • Nuevos campos en ScheduledAction: UserID string (para mention), ReminderID string (para tracking)
  • 3.4 Logica one-shot:

    • Despues de disparar un reminder one-shot, auto-remover del cron via RemoveSchedule
    • Marcar como fired en el store via MarkFired
    • Loguear: "reminder_fired", "reminder_auto_removed"
  • 3.5 On startup: cargar reminders persistidos en devagents/runtime.go:

    • Despues de crear el Scheduler, llamar store.LoadActive()
    • Registrar cada reminder activo via scheduler.AddSchedule()
    • Descartar reminders one-shot cuya hora ya paso (marcar como fired)
  • 3.6 Tests en shell/cron/scheduler_test.go:

    • Test AddSchedule + RemoveSchedule (verificar que el cron entry existe/no existe)
    • Test reminder action kind (mock MatrixSender, verificar mensaje con mention)
    • Test one-shot auto-cleanup (verificar que despues de fire se remueve)

Fase 4 — Agent-to-agent cron calls

  • 4.1 Anadir campos a ScheduledAction en internal/config/schema.go:

    • TargetAgent string — ID del agente destino
    • PromptTemplate string — path al archivo .md con el prompt (reutilizar campo Template)
  • 4.2 Inyectar shell/bus.Bus como dependencia del Scheduler:

    • Nuevo campo bus *bus.Bus en Scheduler struct
    • Parametro opcional en New() (nil si no hay bus disponible)
  • 4.3 Implementar action kind agent_call en shell/cron/actions.go:

    • Leer prompt desde PromptTemplate o inline Prompt
    • Enviar via bus.SendAndWait() al agente destino con kind "task"
    • El agente destino procesa el prompt via su LLM
    • Enviar la respuesta al OutputRoom configurado
    • Timeout configurable (default 2 minutos)
  • 4.4 Documentar ejemplo de config en crons/README.md o similar

  • 4.5 Tests en shell/cron/scheduler_test.go:

    • Test agent_call con mock bus: verificar que envia mensaje correcto al agente destino
    • Test timeout: verificar que si el agente no responde, se loguea error
    • Test con bus nil: verificar que se loguea warning y se salta

Fase 5 — Tests de integracion y cleanup

  • 5.1 Test de integracion: crear reminder via tool → verificar que el scheduler lo tiene → fire → verificar store actualizado
  • 5.2 Documentar nuevos action kinds en el system prompt de agentes que usen reminders
  • 5.3 Actualizar CLAUDE.md con la nueva seccion de reminder tools si aplica
  • 5.4 Verificar que go build -tags goolm ./... compila sin errores
  • 5.5 Verificar que go test -tags goolm ./... pasa sin errores

Ejemplo de uso

Recordatorio one-shot via LLM

Usuario: "Recuerdame manana a las 9am que tengo reunion con el equipo"

Agente: [tool_call] create_reminder(message="Reunion con el equipo", time="2026-04-10 09:00", recurring=false)
Agente: "Listo, te recordare manana a las 9:00 AM."

→ 2026-04-10 09:00:
Agente envia: "⏰ @usuario Recordatorio: Reunion con el equipo"
→ Reminder auto-borrado del scheduler y marcado como fired en store.

Recordatorio recurrente

Usuario: "Recuerdame todos los lunes a las 10am hacer el standup"

Agente: [tool_call] create_reminder(message="Hacer el standup", cron="0 10 * * 1", recurring=true)
Agente: "Configurado. Cada lunes a las 10:00 AM te recordare."

→ Cada lunes 10:00:
Agente envia: "⏰ @usuario Recordatorio: Hacer el standup"

Listar y borrar reminders

Usuario: "Que recordatorios tengo?"

Agente: [tool_call] list_reminders()
Agente:
"Tienes 2 recordatorios activos:
1. [abc123] Reunion con el equipo — 2026-04-10 09:00 (one-shot)
2. [def456] Hacer el standup — lunes 10:00 (recurrente)"

Usuario: "Borra el del standup"

Agente: [tool_call] delete_reminder(id="def456")
Agente: "Recordatorio eliminado."

Agent-to-agent cron call

Config en agents/asistente-2/config.yaml:

schedules:
  - name: daily-analysis
    cron: "0 18 * * *"
    action:
      kind: agent_call
      target_agent: "asistente-2"
      prompt_template: "crons/daily-summary/prompts/prompt.md"
    output_room: "!room:matrix-af2f3d.organic-machine.com"

Resultado: cada dia a las 18:00, el scheduler envia el prompt al agente asistente-2 via bus. El agente procesa con su LLM y envia la respuesta al room configurado.

Decisiones de diseno

  1. SQLite para persistencia de reminders: ya tenemos el driver modernc configurado y probado. Un reminder es un dato simple (ID, mensaje, cron, estado). No justifica una dependencia nueva.

  2. Parsing de tiempo natural — enfoque progresivo: empezar con formatos simples (ISO datetime 2026-04-10 15:00, hora del dia 15:00, expresiones cron). Anadir expresiones relativas (en 30 minutos, manana a las 9) como mejora incremental. No intentar NLP completo — el LLM ya interpreta la intencion, la tool solo necesita parsear el formato final.

  3. One-shot auto-delete: los reminders que se disparan una vez se marcan como fired en el store (para auditoria) y se remueven del scheduler. Evita acumulacion de entries fantasma en el cron runner.

  4. agent_call usa el bus existente: no se necesita protocolo nuevo. SendAndWait ya implementa el patron request-reply con timeout. El scheduler actua como el "from" agent, el target procesa via su pipeline LLM normal.

  5. Tools en subpackage tools/reminder/: sigue el patron de tools/file/, tools/ssh/, etc. Cada tool recibe sus dependencias (store, scheduler) via constructor.

  6. Reminders scoped a room: un reminder solo es visible y gestionable desde la room donde se creo. Esto evita que un usuario en room A borre reminders de room B.

Prerequisitos

  • Ninguno critico. Todo usa infraestructura existente:
    • shell/cron/scheduler.go — scheduler a extender
    • shell/bus/ — bus inter-agente para agent_call
    • internal/config/schema.go — config a extender
    • SQLite via modernc (ya disponible)
    • Pattern de tools en tools/ (ya establecido)

Riesgos

Riesgo Mitigacion
Parsing de tiempo natural es complejo Empezar simple (ISO, hora, cron). El LLM normaliza la entrada antes de llamar la tool. Anadir formatos relativos iterativamente.
Timezone handling Usar timezone del servidor inicialmente. Documentar la limitacion. Anadir soporte per-user TZ en un issue futuro si hay demanda.
Bus no disponible para agent_call Si el bus es nil (agente standalone), loguear warning y saltar la ejecucion. Nunca crashear.
Tabla de reminders crece sin limite One-shot se marcan fired (no se borran fisicamente para auditoria). Anadir retention policy (borrar fired > 30 dias) como cleanup task.
Scheduler concurrency con AddSchedule robfig/cron es thread-safe para AddFunc/Remove. Proteger el mapa interno de IDs con mutex propio.
Reminder con cron invalido Validar la expresion cron en el tool create_reminder antes de persistir. Retornar error claro al LLM si la expresion es invalida.