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>
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) soportasend_messageyllm_promptcomo 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 viaSendAndWait, pero no esta integrado con el cron. ScheduledActioneninternal/config/schema.godefine los campos parasend_messageyllm_promptpero 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 structRemindery 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.gocon structReminder:- 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
- Campos:
-
1.2 Crear
shell/reminder/store.goconStorestruct:- Constructor
New(dbPath string) (*Store, error)— abre SQLite, crea tabla si no existe Create(ctx, Reminder) error— inserta un reminderDelete(ctx, id string) error— borrado logico (marcar como deleted)List(ctx, roomID string) ([]Reminder, error)— listar activos de una roomMarkFired(ctx, id string) error— marcar como disparado con timestampLoadActive(ctx) ([]Reminder, error)— cargar todos los activos (para startup)- Auto-crear tabla
remindersen init (CREATE TABLE IF NOT EXISTS)
- Constructor
-
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.goconNewCreateReminder(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
- Params:
-
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
- Params:
-
2.4 Registrar tools en
devagents/runtime.go:- Condicion: nueva seccion
tools.reminders.enableden config - Pasar referencia al store y al scheduler
- Condicion: nueva seccion
-
2.5 Anadir
ReminderToolCfgaToolsCfgeninternal/config/schema.go:- Campos:
Enabled bool,MaxPerRoom int(limite de reminders activos por room, default 50),DBPath string(default:data/reminders.db)
- Campos:
-
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) erroral Scheduler:- Registra un nuevo schedule en el cron runner en caliente
- Guardar referencia al
cron.EntryIDpara poder remover despues - Thread-safe (mutex sobre el mapa de entries)
-
3.2 Anadir
RemoveSchedule(id string) erroral Scheduler:- Remover entry del cron runner por EntryID
- Limpiar del mapa interno
-
3.3 Implementar action kind
reminderenshell/cron/actions.go:- Envia mensaje personalizado al room:
"<prefix> @<user> Recordatorio: <mensaje>" - Nuevos campos en
ScheduledAction:UserID string(para mention),ReminderID string(para tracking)
- Envia mensaje personalizado al room:
-
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"
- Despues de disparar un reminder one-shot, auto-remover del cron via
-
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)
- Despues de crear el Scheduler, llamar
-
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
ScheduledActioneninternal/config/schema.go:TargetAgent string— ID del agente destinoPromptTemplate string— path al archivo .md con el prompt (reutilizar campoTemplate)
-
4.2 Inyectar
shell/bus.Buscomo dependencia del Scheduler:- Nuevo campo
bus *bus.Busen Scheduler struct - Parametro opcional en
New()(nil si no hay bus disponible)
- Nuevo campo
-
4.3 Implementar action kind
agent_callenshell/cron/actions.go:- Leer prompt desde
PromptTemplateo inlinePrompt - Enviar via
bus.SendAndWait()al agente destino con kind"task" - El agente destino procesa el prompt via su LLM
- Enviar la respuesta al
OutputRoomconfigurado - Timeout configurable (default 2 minutos)
- Leer prompt desde
-
4.4 Documentar ejemplo de config en
crons/README.mdo similar -
4.5 Tests en
shell/cron/scheduler_test.go:- Test
agent_callcon 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
- Test
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.mdcon 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
-
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.
-
Parsing de tiempo natural — enfoque progresivo: empezar con formatos simples (ISO datetime
2026-04-10 15:00, hora del dia15: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. -
One-shot auto-delete: los reminders que se disparan una vez se marcan como
fireden el store (para auditoria) y se remueven del scheduler. Evita acumulacion de entries fantasma en el cron runner. -
agent_callusa el bus existente: no se necesita protocolo nuevo.SendAndWaitya implementa el patron request-reply con timeout. El scheduler actua como el "from" agent, el target procesa via su pipeline LLM normal. -
Tools en subpackage
tools/reminder/: sigue el patron detools/file/,tools/ssh/, etc. Cada tool recibe sus dependencias (store, scheduler) via constructor. -
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 extendershell/bus/— bus inter-agente paraagent_callinternal/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. |