diff --git a/.claude/plans/01-bot-tools.md b/.claude/plans/01-bot-tools.md new file mode 100644 index 0000000..de4fea2 --- /dev/null +++ b/.claude/plans/01-bot-tools.md @@ -0,0 +1,52 @@ +# Plan: Herramientas para los bots + +## Objetivo +Permitir que los bots ejecuten herramientas reales (funciones Go) como respuesta a +decisiones del LLM — patrón function calling / tool use. + +## Estado: pendiente + +--- + +## Diseño + +### Capa pura (`pkg/tools/`) +- Definir `ToolSpec` con nombre, descripción y esquema JSON de parámetros +- Definir `ToolCallAction` en `pkg/decision/` — acción pura que contiene + `ToolName string` y `Args map[string]any` +- El motor de reglas puede emitir `ToolCallAction` como cualquier otra acción + +### Capa shell (`shell/tools/`) +- `Executor` que mapea nombre → función Go real +- Ejecuta la herramienta y devuelve `ToolResult{Output string, Err error}` +- El Runner de `shell/effects/` llama al Executor cuando recibe `ToolCallAction` + +### Integración LLM +- `shell/llm/anthropic.go` y `openai.go` ya soportan tool_use / function_calling +- Mapear `[]ToolSpec` al formato nativo de cada proveedor +- Parsear la respuesta del LLM para extraer llamadas a herramientas + +### Herramientas iniciales a implementar +| Herramienta | Descripción | Shell | +|-----------------|-------------------------------------|-------------------| +| `http_get` | GET a una URL, devuelve body | `shell/tools/` | +| `http_post` | POST JSON a una URL | `shell/tools/` | +| `ssh_command` | Ejecutar comando remoto por SSH | `shell/ssh/` | +| `read_file` | Leer archivo local | `shell/tools/` | +| `matrix_send` | Enviar mensaje a una sala Matrix | `shell/matrix/` | + +--- + +## Archivos a crear/modificar +- `pkg/tools/spec.go` — ToolSpec, ToolResult +- `pkg/decision/actions.go` — añadir ToolCallAction +- `shell/tools/executor.go` — registro y ejecución de herramientas +- `shell/effects/runner.go` — manejar ToolCallAction +- `shell/llm/anthropic.go` — emitir tools en el request, parsear tool_use blocks +- `shell/llm/openai.go` — idem para function_calling +- `agents//agent.go` — registrar herramientas por agente + +## Notas +- Las herramientas se declaran en `pkg/` (pure spec) pero se implementan en `shell/` +- Un agente solo tiene acceso a las herramientas declaradas en su config +- Respetar `security.allowed_tools` del config YAML diff --git a/.claude/plans/02-bot-memory.md b/.claude/plans/02-bot-memory.md new file mode 100644 index 0000000..e614e77 --- /dev/null +++ b/.claude/plans/02-bot-memory.md @@ -0,0 +1,95 @@ +# Plan: Memoria para los bots + +## Objetivo +Que cada bot recuerde conversaciones anteriores, hechos importantes sobre usuarios +y contexto de salas. Memoria a corto plazo (ventana de conversación) y largo plazo +(SQLite persistente). + +## Estado: pendiente + +--- + +## Tipos de memoria + +### 1. Memoria de conversación (corto plazo) +- Ventana deslizante de `N` mensajes por room +- Se pasa como historial al LLM en cada llamada +- Vive en RAM; se pierde al reiniciar (aceptable) + +### 2. Memoria episódica (largo plazo) +- Hechos extraídos de conversaciones: nombre del usuario, preferencias, eventos +- Guardados en SQLite (`agents//data/memory.db`) +- El LLM puede leer y escribir hechos mediante herramientas (`remember`, `recall`) + +--- + +## Diseño capa pura (`pkg/memory/`) + +```go +// Tipos puros — sin I/O +type Message struct { + Role string // "user" | "assistant" + Content string + At time.Time +} + +type Fact struct { + Subject string + Key string + Value string + At time.Time +} + +// Ventana de conversación +type Window struct { + RoomID string + Messages []Message + MaxSize int +} + +func (w Window) Append(m Message) Window { ... } // pura +func (w Window) ToLLMMessages() []llm.Message { ... } // pura +``` + +## Diseño capa shell (`shell/memory/`) + +```go +// Acceso a SQLite — impuro +type Store interface { + SaveFact(ctx, agentID, fact) error + GetFacts(ctx, agentID, subject) ([]Fact, error) + GetHistory(ctx, agentID, roomID, limit) ([]Message, error) + AppendMessage(ctx, agentID, roomID, msg) error +} +``` + +--- + +## Herramientas LLM para memoria +- `remember(subject, key, value)` — guardar un hecho +- `recall(subject, key)` — recuperar hechos sobre alguien/algo +- `forget(subject, key)` — borrar un hecho + +--- + +## Integración con el flujo actual +1. `agents/runtime.go` mantiene un `map[roomID]memory.Window` en RAM +2. Antes de llamar al LLM, inyectar historial de la ventana al request +3. Después de la respuesta, hacer `Append` con el mensaje del bot +4. Las herramientas `remember`/`recall` van al `Store` SQLite + +--- + +## Archivos a crear/modificar +- `pkg/memory/types.go` — Message, Fact, Window (puros) +- `pkg/memory/window.go` — operaciones sobre Window (puras) +- `shell/memory/sqlite_store.go` — Store SQLite +- `shell/memory/migrations/001_init.sql` — schema +- `agents/runtime.go` — inyectar historial antes del LLM call +- `agents//agent.go` — registrar herramientas remember/recall + +## Notas +- Schema SQLite: tabla `facts(agent_id, subject, key, value, updated_at)`, + tabla `messages(agent_id, room_id, role, content, created_at)` +- El tamaño de la ventana se configura en `storage.max_context_messages` + (añadir al schema de config) diff --git a/.claude/plans/03-bot-interaction.md b/.claude/plans/03-bot-interaction.md new file mode 100644 index 0000000..eb32206 --- /dev/null +++ b/.claude/plans/03-bot-interaction.md @@ -0,0 +1,79 @@ +# Plan: Interacción entre bots en una sala compartida + +## Objetivo +Que múltiples bots convivan en una sala Matrix, se "escuchen" entre sí y puedan +colaborar en tareas — sin crear bucles infinitos. + +## Estado: pendiente + +--- + +## Problema central: evitar bucles +Si bot-A responde a bot-B y bot-B responde a bot-A, se genera un bucle. + +### Solución: reglas de anti-bucle en el motor de decisión +1. Cada mensaje lleva metadatos `sender` y opcionalmente `m.relates_to` +2. El `MatchFunc` de las reglas puede filtrar por `IsBot(sender)` y + aplicar lógica de cooldown o turno +3. El sistema de bus interno (`shell/bus/`) puede coordinar turnos + +--- + +## Diseño + +### Nuevo tipo de sala: `SharedRoom` +Configurar en `config.yaml` bajo una sección `rooms`: +```yaml +rooms: + - id: "!roomid:matrix.server.com" + type: "shared" # sala compartida entre bots + participants: # agentes que participan + - assistant-bot + - devops-bot + turn_based: false # si true, los bots se turnan por ronda + respond_to_bots: true # si false, solo responden a humanos +``` + +### Lógica en `agents/runtime.go` +- Al recibir un evento en una sala compartida, verificar si `sender` es un bot conocido +- Aplicar una regla de "bot puede responder a bot" solo si: + - La sala tiene `respond_to_bots: true` + - No hay respuesta pendiente del mismo bot en los últimos N segundos (cooldown) + - No es una respuesta a un mensaje propio + +### Coordinación con el bus (`shell/bus/`) +- Publicar en el bus interno cuando un bot habla en una sala compartida +- Los otros bots suscritos al bus pueden reaccionar sin pasar por Matrix +- Posible uso: bot-A pide ayuda a bot-B por el bus, bot-B responde en Matrix + +--- + +## Reglas puras (`pkg/decision/`) +Nuevas funciones de match: +```go +func SenderIsBot(knownBots []string) MatchFunc +func NotRepliedRecently(cooldown time.Duration) MatchFunc // requiere estado externo +func SenderIsHuman(knownBots []string) MatchFunc +``` + +--- + +## Anti-bucle: cooldown simple +En `agents/runtime.go` mantener `map[roomID]time.Time` con el último mensaje enviado. +Si `now - lastSent < cooldown`, no responder en esa sala. +Cooldown configurable: `rooms[].response_cooldown_seconds`. + +--- + +## Archivos a crear/modificar +- `internal/config/schema.go` — añadir `RoomCfg` con campos shared room +- `pkg/decision/matchers.go` — SenderIsBot, SenderIsHuman +- `agents/runtime.go` — lógica de salas compartidas y cooldown +- `shell/bus/bus.go` — publicar eventos cross-bot +- `agents//agent.go` — reglas específicas para salas compartidas + +## Notas +- Fase 1: solo cooldown simple — un bot no responde más de 1 vez por minuto en + una sala compartida +- Fase 2: turno basado en bus interno +- Fase 3: colaboración estructurada (bot-A delega tarea a bot-B y espera resultado) diff --git a/.claude/plans/04-bot-avatar.md b/.claude/plans/04-bot-avatar.md new file mode 100644 index 0000000..cae6c93 --- /dev/null +++ b/.claude/plans/04-bot-avatar.md @@ -0,0 +1,69 @@ +# Plan: Editar fotos de perfil de los bots + +## Objetivo +Poder actualizar el avatar (foto de perfil) y el display name de cada bot en Matrix +desde la CLI (`agentctl`) o desde un dev-script. + +## Estado: pendiente + +--- + +## Cómo funciona en Matrix +- Endpoint: `PUT /_matrix/client/v3/profile/{userId}/avatar_url` +- Body: `{ "avatar_url": "mxc://..." }` — URI de contenido subido al Media repo +- Para subir una imagen: `POST /_matrix/media/v3/upload` con el body binario + y `Content-Type` de la imagen +- También se puede cambiar el display name: + `PUT /_matrix/client/v3/profile/{userId}/displayname` + +La secuencia es: +1. Subir imagen → obtener `mxc://server/mediaID` +2. Establecer `avatar_url` en el perfil con esa URI + +--- + +## Diseño + +### CLI: `agentctl avatar ` +Nuevo subcomando en `cmd/agentctl/`: +``` +agentctl avatar assistant-bot /path/to/photo.png +agentctl displayname assistant-bot "Assistant Bot" +``` + +### Shell: `shell/matrix/profile.go` +```go +// UploadMedia sube un archivo y devuelve la mxc:// URI +func UploadMedia(ctx, client, filePath string) (mxcURI string, err error) + +// SetAvatar establece avatar_url en el perfil del bot +func SetAvatar(ctx, client, mxcURI string) error + +// SetDisplayName cambia el displayname +func SetDisplayName(ctx, client, name string) error +``` + +Usa el cliente `mautrix.Client` ya existente en `shell/matrix/client.go`. + +### Dev-script: `dev-scripts/avatar.sh` +```bash +#!/usr/bin/env bash +# Uso: ./dev-scripts/avatar.sh +./bin/agentctl avatar "$1" "$2" +``` + +--- + +## Archivos a crear/modificar +- `shell/matrix/profile.go` — UploadMedia, SetAvatar, SetDisplayName +- `cmd/agentctl/avatar.go` — subcomando `avatar` y `displayname` +- `cmd/agentctl/main.go` — registrar los nuevos subcomandos en Cobra +- `dev-scripts/avatar.sh` — wrapper convenience + +## Notas +- El token del bot necesita permiso de escritura en su propio perfil (normal por defecto) +- Formatos soportados: PNG, JPG, WebP — Matrix los acepta todos +- mautrix-go tiene métodos `client.UploadMedia()` y `client.SetAvatarURL()`; + usar esos directamente para evitar HTTP manual +- El comando debe cargar el token del bot desde las env vars (`MATRIX_TOKEN_`) + igual que hace `cmd/launcher/` diff --git a/.claude/plans/05-bot-cron.md b/.claude/plans/05-bot-cron.md new file mode 100644 index 0000000..ff4c9ff --- /dev/null +++ b/.claude/plans/05-bot-cron.md @@ -0,0 +1,108 @@ +# Plan: Cron scheduler para actividad autónoma de los bots + +## Objetivo +Que los bots puedan publicar mensajes, ejecutar tareas o interactuar en salas +de forma autónoma según un horario — sin que el usuario tenga que escribirles. + +## Estado: pendiente + +--- + +## Casos de uso +- Bot saluda "buenos días" en una sala a las 9:00 +- Devops-bot hace healthcheck de servidores cada hora y reporta +- Assistant-bot publica un resumen diario a las 18:00 +- Bots conversan entre sí a horas fijas para simular actividad + +--- + +## Diseño + +### Config YAML — `schedules` (ya existe en el schema) +```yaml +schedules: + - cron: "0 9 * * *" # cada día a las 9:00 + action: send_message + room: "!roomid:server.com" + template: "prompts/good-morning.md" # se envía como mensaje o como prompt al LLM + + - cron: "0 * * * *" # cada hora + action: run_tool + tool: ssh_command + args: + host: "prod-server" + command: "systemctl is-active myapp" + + - cron: "0 18 * * *" + action: llm_prompt + room: "!roomid:server.com" + prompt: "Genera un resumen del día de hoy para el equipo." +``` + +### Tipos de acción de cron +| Tipo | Descripción | +|-----------------|-------------------------------------------------------| +| `send_message` | Envía un mensaje literal o desde plantilla a una sala | +| `run_tool` | Ejecuta una herramienta (SSH, HTTP, etc.) | +| `llm_prompt` | Llama al LLM con un prompt y publica la respuesta | + +--- + +## Implementación: `shell/cron/` + +```go +// Scheduler lanza goroutines para cada schedule configurado +type Scheduler struct { + agent *agents.Agent + cfg []config.ScheduleCfg + effects *effects.Runner +} + +func (s *Scheduler) Start(ctx context.Context) +func (s *Scheduler) Stop() +``` + +Usa `time.AfterFunc` o una librería cron mínima. + +### Librería cron recomendada +`github.com/robfig/cron/v3` — ligera, soporta sintaxis cron estándar y `@every 1h`. +Sin dependencias de CGO. + +### Integración en `agents/runtime.go` +```go +type Agent struct { + ... + scheduler *cron.Scheduler // nil si no hay schedules +} + +func (a *Agent) Start(ctx) error { + ... + if len(a.cfg.Schedules) > 0 { + a.scheduler = cron.New(a, a.cfg.Schedules, a.runner) + a.scheduler.Start(ctx) + } +} +``` + +### Flujo para `llm_prompt` +1. El cron dispara +2. Construir `CompletionRequest` con el prompt del schedule +3. Llamar al LLM (usando `shell/llm/`) +4. Emitir `SendMessageAction` con la respuesta +5. El Runner lo envía a la sala Matrix configurada + +--- + +## Archivos a crear/modificar +- `shell/cron/scheduler.go` — Scheduler, parseador de ScheduleCfg +- `shell/cron/actions.go` — ejecutores de cada tipo de acción de cron +- `internal/config/schema.go` — revisar/completar `ScheduleCfg` (ya tiene campos) +- `agents/runtime.go` — instanciar y arrancar el Scheduler +- `go.mod` — añadir `github.com/robfig/cron/v3` + +## Notas +- El Scheduler corre en goroutines separadas; respetar el `ctx` de shutdown +- Los prompts de los schedules pueden ser strings inline o rutas a archivos `.md` +- Fase 1: solo `send_message` y `llm_prompt` +- Fase 2: `run_tool` con resultado incluido en el mensaje +- Fase 3: schedules de interacción entre bots (bot-A pide a bot-B que haga algo) diff --git a/.claude/plans/README.md b/.claude/plans/README.md new file mode 100644 index 0000000..6987697 --- /dev/null +++ b/.claude/plans/README.md @@ -0,0 +1,22 @@ +# Plans — Extensiones pendientes + +Cada archivo describe un feature a implementar con su diseño técnico, archivos +afectados y notas de implementación. + +| # | Feature | Archivo | Estado | +|---|----------------------------|----------------------------|-----------| +| 1 | Herramientas para los bots | [01-bot-tools.md](01-bot-tools.md) | pendiente | +| 2 | Memoria para los bots | [02-bot-memory.md](02-bot-memory.md) | pendiente | +| 3 | Interacción entre bots | [03-bot-interaction.md](03-bot-interaction.md) | pendiente | +| 4 | Fotos de perfil | [04-bot-avatar.md](04-bot-avatar.md) | pendiente | +| 5 | Cron scheduler | [05-bot-cron.md](05-bot-cron.md) | pendiente | + +## Orden de implementación sugerido + +``` +04-avatar → independiente, más simple, buen punto de partida +01-tools → base necesaria para los demás +02-memory → depende de 01-tools (herramientas remember/recall) +05-cron → depende de 01-tools (run_tool) y 02-memory (contexto) +03-interact → depende de todos los anteriores para ser útil +```