feat: add plans for bot tools, memory, interaction, avatar editing, and cron scheduling

This commit is contained in:
2026-03-04 01:56:04 +00:00
parent 0701089ecf
commit e900464dd6
6 changed files with 425 additions and 0 deletions
+52
View File
@@ -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/<id>/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
+95
View File
@@ -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/<id>/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/<id>/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)
+79
View File
@@ -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/<id>/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)
+69
View File
@@ -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 <agent-id> <image-path>`
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 <agent-id> <image-path>
./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_<BOT>`)
igual que hace `cmd/launcher/`
+108
View File
@@ -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)
+22
View File
@@ -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
```