From 8f6958f856fb9468ce349a9ad34c299a4e362f7c Mon Sep 17 00:00:00 2001 From: Enmanuel Date: Wed, 8 Apr 2026 23:05:47 +0000 Subject: [PATCH] chore: agregar issues 0026-0032 y worktrees a gitignore Registra los nuevos issues pendientes en el indice y excluye la carpeta worktrees/ del control de versiones. Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 5 +- dev/issues/0026-split-runtime.md | 94 ++++++++++++ dev/issues/0027-prune-config-schema.md | 112 ++++++++++++++ dev/issues/0028-decouple-launcher.md | 109 +++++++++++++ dev/issues/0029-core-tests.md | 99 ++++++++++++ dev/issues/0030-robot-vs-agent.md | 157 +++++++++++++++++++ dev/issues/0031-expand-file-tools.md | 179 ++++++++++++++++++++++ dev/issues/0032-e2e-create-agent-skill.md | 126 +++++++++++++++ dev/issues/README.md | 7 + 9 files changed, 887 insertions(+), 1 deletion(-) create mode 100644 dev/issues/0026-split-runtime.md create mode 100644 dev/issues/0027-prune-config-schema.md create mode 100644 dev/issues/0028-decouple-launcher.md create mode 100644 dev/issues/0029-core-tests.md create mode 100644 dev/issues/0030-robot-vs-agent.md create mode 100644 dev/issues/0031-expand-file-tools.md create mode 100644 dev/issues/0032-e2e-create-agent-skill.md diff --git a/.gitignore b/.gitignore index 3666973..9bb5b30 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,7 @@ e2e/test-results/ e2e/.auth/ e2e/.env e2e/element-web/ -e2e/playwright-report/ \ No newline at end of file +e2e/playwright-report/ + +# Parallel worktrees (parallel-fix-issues skill) +worktrees/ \ No newline at end of file diff --git a/dev/issues/0026-split-runtime.md b/dev/issues/0026-split-runtime.md new file mode 100644 index 0000000..936228f --- /dev/null +++ b/dev/issues/0026-split-runtime.md @@ -0,0 +1,94 @@ +# 0026 — Refactorizar runtime.go: separar el god object + +## Objetivo + +Dividir `agents/runtime.go` (1,182 lineas, 25+ metodos) en archivos con responsabilidades claras. Reducir el archivo principal a lifecycle (New, Run, Stop) y delegar el resto a archivos especializados. + +## Contexto + +- `agents/runtime.go` concentra: lifecycle Matrix, command routing, evaluacion de reglas, invocacion LLM, loop de tool-use, gestion de memoria, carga de prompts, sanitizacion, scheduling, comunicacion inter-agente +- Funciones como `runLLM()` (131 lineas) y `handleEvent()` (100 lineas) tienen complejidad ciclomatica estimada de 10-15 +- `New()` tiene 262 lineas de inicializacion secuencial para 10+ subsistemas +- El struct `Agent` tiene 25+ campos — señal de responsabilidad excesiva +- No hay tests para runtime.go, y el tamaño dificulta añadirlos + +## Arquitectura + +``` +agents/runtime.go → solo Agent struct, New(), Run(), Stop() (~200 lineas) +agents/handler.go NEW → handleEvent(), command routing, rule evaluation +agents/llm.go NEW → runLLM(), tool-use loop, system prompt loading +agents/memory.go NEW → window management, persistence, ensureWindowLoaded() +agents/registry_build.go NEW → buildToolRegistry() y toda la logica de registro de tools +agents/commands.go → ya existe, mantener como esta +``` + +### Patron pure core / impure shell + +- `pkg/` — sin cambios (el motor de decisiones ya esta separado) +- `shell/` — sin cambios +- `agents/` — refactoring interno, zero cambios en API publica + +## Tareas + +### Fase 1: Extraer handler + +- [ ] **1.1** Crear `agents/handler.go` con `handleEvent()` y metodos de routing de comandos +- [ ] **1.2** Mover logica de evaluacion de reglas y fallback LLM +- [ ] **1.3** Verificar que `runtime.go` solo llama a `a.handleEvent()` como entry point + +### Fase 2: Extraer LLM + +- [ ] **2.1** Crear `agents/llm.go` con `runLLM()`, `expandLLMActions()`, logica de system prompt +- [ ] **2.2** Mover el loop de tool-use (iteracion + ejecucion + RBAC check) +- [ ] **2.3** Mover la carga de system prompt desde archivo + +### Fase 3: Extraer memoria + +- [ ] **3.1** Crear `agents/memory.go` con `ensureWindowLoaded()`, `appendToWindow()`, `persistMessage()` +- [ ] **3.2** Mover la inicializacion de memory store desde `New()` + +### Fase 4: Extraer registry builder + +- [ ] **4.1** Crear `agents/registry_build.go` con `buildToolRegistry()` +- [ ] **4.2** Mover todo el registro condicional de tools + +### Fase 5: Tests + +- [ ] **5.1** Tests unitarios para `handleEvent()` con MessageContext mock (command routing) +- [ ] **5.2** Tests unitarios para `runLLM()` con CompleteFunc mock (tool-use loop) +- [ ] **5.3** Tests para `buildToolRegistry()` con configs parciales + +### Fase 6: Cleanup + +- [ ] **6.1** Verificar que `runtime.go` queda < 300 lineas +- [ ] **6.2** Actualizar imports si es necesario +- [ ] **6.3** `go build -tags goolm ./...` y `go test -tags goolm ./...` pasan + +--- + +## Ejemplo de uso + +No hay cambio funcional. Antes y despues: + +```go +a, err := agents.New(cfg, rules, logger) // mismo API +a.Run(ctx) // mismo comportamiento +``` + +Solo cambia la organizacion interna. + +## Decisiones de diseno + +- **Archivos por responsabilidad, no por tamaño**: cada archivo tiene una razon de existir, no es solo "partir en pedazos" +- **Zero cambios en API publica**: `New()`, `Run()`, `Stop()`, `RegisterCommand()` mantienen firma identica +- **Metodos en Agent struct**: los metodos nuevos siguen siendo metodos del mismo struct, solo viven en otro archivo + +## Prerequisitos + +- Ninguno + +## Riesgos + +- **Merge conflicts**: si hay PRs en vuelo que tocan runtime.go, el refactor generara conflictos. Mitigacion: hacerlo en una ventana sin otros cambios pendientes +- **Regresiones**: sin tests previos, los tests E2E son la unica red de seguridad. Mitigacion: correr E2E antes y despues diff --git a/dev/issues/0027-prune-config-schema.md b/dev/issues/0027-prune-config-schema.md new file mode 100644 index 0000000..63e7995 --- /dev/null +++ b/dev/issues/0027-prune-config-schema.md @@ -0,0 +1,112 @@ +# 0027 — Limpiar config schema: eliminar codigo muerto + +## Objetivo + +Eliminar las secciones del config schema (`internal/config/schema.go`) que no estan implementadas ni referenciadas en el codebase. Reducir de 560 lineas / 61 structs a solo lo que realmente se usa. + +## Contexto + +- `internal/config/schema.go` tiene 560 lineas y 61 tipos struct +- Secciones **nunca referenciadas** en ningun archivo `.go`: + - `ObservabilityCfg` (metrics, tracing, health) — 0 usos + - `ResilienceCfg` (circuit breaker, retry, queue) — 0 usos + - `AgentsCfg` (peers, delegation, protocol) — 0 usos + - `PersonalityCfg.Communication` (18 campos: humor, quirks, catchphrases) — 0 usos +- El template `_template/config.yaml` tiene 414 lineas cuando un agente real necesita ~40 +- Esto complica el onboarding y crea confusion sobre que es funcional vs especulativo + +## Arquitectura + +``` +internal/config/schema.go → eliminar structs muertos (~180 lineas) +agents/_template/config.yaml → reducir a lo esencial (~60 lineas) +``` + +### Patron pure core / impure shell + +- `pkg/` — sin cambios +- `shell/` — sin cambios +- `agents/` — template simplificado +- `internal/config/` — poda de tipos + +## Tareas + +### Fase 1: Auditar uso real + +- [ ] **1.1** Grep cada struct/campo del schema contra todo el codebase para confirmar cuales tienen 0 referencias +- [ ] **1.2** Documentar en este issue la lista final de tipos a eliminar + +### Fase 2: Podar schema.go + +- [ ] **2.1** Eliminar `ObservabilityCfg` y todos sus sub-structs (LoggingCfg, MetricsCfg, HealthCfg, TracingCfg) +- [ ] **2.2** Eliminar `ResilienceCfg` y sub-structs (CircuitBreakerCfg, RetryCfg, ShutdownCfg, QueueCfg) +- [ ] **2.3** Eliminar `AgentsCfg` y sub-structs (PeerCfg, DelegationCfg) +- [ ] **2.4** Eliminar campos no usados de `PersonalityCfg` (Communication, Humor, Quirks, etc.) +- [ ] **2.5** Verificar que los campos eliminados no rompen el parsing YAML (yaml.v3 ignora campos extra por defecto) + +### Fase 3: Simplificar template + +- [ ] **3.1** Reescribir `agents/_template/config.yaml` con solo los campos funcionales (~60 lineas) +- [ ] **3.2** Añadir comentarios explicativos en el template para cada seccion + +### Fase 4: Tests + +- [ ] **4.1** Verificar que los configs existentes (`assistant-bot`, `asistente-2`, `meteorologo`) siguen parseando correctamente +- [ ] **4.2** `go build -tags goolm ./...` compila +- [ ] **4.3** `go test -tags goolm ./...` pasa + +### Fase 5: Cleanup + +- [ ] **5.1** Actualizar `CLAUDE.md` si se mencionan secciones eliminadas +- [ ] **5.2** Si algun config YAML existente usa campos eliminados, limpiar esas lineas + +--- + +## Ejemplo de uso + +Antes (template 414 lineas): +```yaml +personality: + tone: friendly + communication: + formality: informal # nunca se usa + humor: light # nunca se usa + quirks: ["dice 'vale'"] # nunca se usa +observability: # nunca se usa + logging: ... + metrics: ... +resilience: # nunca se usa + circuit_breaker: ... +``` + +Despues (template ~60 lineas): +```yaml +agent: + id: mi-agente + description: "Descripcion" +personality: + tone: friendly + language: es +llm: + primary: + provider: openai + model: gpt-4o +matrix: + threads: + enabled: true +``` + +## Decisiones de diseno + +- **Eliminar, no comentar**: codigo muerto se borra, no se comenta con "// TODO: implement" +- **Si se necesita en el futuro, se re-añade**: Git tiene historial. No mantener especulacion. +- **yaml.v3 es tolerante**: campos extra en YAML no causan error, asi que eliminar structs no rompe configs existentes que tengan esos campos + +## Prerequisitos + +- Ninguno + +## Riesgos + +- **Falso negativo en grep**: algun campo podria usarse via reflection o string matching. Mitigacion: buscar tambien por nombre de campo en strings +- **Configs de usuarios existentes**: si alguien tiene un config con `observability:`, no rompera (yaml.v3 ignora), pero el campo sera silenciosamente ignorado. Esto ya era el caso. diff --git a/dev/issues/0028-decouple-launcher.md b/dev/issues/0028-decouple-launcher.md new file mode 100644 index 0000000..ef3af62 --- /dev/null +++ b/dev/issues/0028-decouple-launcher.md @@ -0,0 +1,109 @@ +# 0028 — Desacoplar launcher del registro estatico de agentes + +## Objetivo + +Eliminar la necesidad de editar `cmd/launcher/main.go` cada vez que se añade un agente. Reemplazar el `rulesRegistry` hard-coded con auto-discovery basado en la convencion de directorios. + +## Contexto + +- Actualmente `cmd/launcher/main.go` importa cada paquete de agente explicitamente: + ```go + import ( + assistantagent "github.com/enmanuel/agents/agents/assistant-bot" + asistente2agent "github.com/enmanuel/agents/agents/asistente-2" + ) + var rulesRegistry = map[string]func() []decision.Rule{...} + ``` +- Cada agente nuevo requiere: añadir import + añadir entrada al map + recompilar +- El script `dev-scripts/agent/new-agent.sh` ya modifica el launcher automaticamente, pero es fragil (sed sobre codigo Go) +- Contradiccion: el launcher hace glob de `agents/*/config.yaml` para descubrir configs, pero luego necesita imports estaticos para las reglas + +## Arquitectura + +``` +agents/registry.go NEW → registro global de reglas (init-based) +agents//agent.go → cada agente se auto-registra via init() +cmd/launcher/main.go → eliminar rulesRegistry, usar agents.GetRules(id) +``` + +### Patron pure core / impure shell + +- `pkg/` — sin cambios +- `shell/` — sin cambios +- `agents/` — nuevo registry global + init() en cada agente +- `cmd/launcher/` — simplificacion + +## Tareas + +### Fase 1: Crear registry de reglas + +- [ ] **1.1** Crear `agents/registry.go` con `Register(id, rulesFn)` y `GetRules(id)` +- [ ] **1.2** Usar sync.Mutex o sync.Map para seguridad en init() + +### Fase 2: Migrar agentes a auto-registro + +- [ ] **2.1** En `agents/assistant-bot/agent.go` añadir `func init() { agents.Register("assistant-bot", Rules) }` +- [ ] **2.2** Repetir para `asistente-2` y `meteorologo` +- [ ] **2.3** Actualizar `agents/_template/agent.go` con el patron init() + +### Fase 3: Simplificar launcher + +- [ ] **3.1** Eliminar imports explicitos de agentes en `cmd/launcher/main.go` +- [ ] **3.2** Añadir blank import: `_ "github.com/enmanuel/agents/agents/assistant-bot"` (etc.) +- [ ] **3.3** Reemplazar `rulesRegistry[id]` con `agents.GetRules(id)` +- [ ] **3.4** Si no hay reglas registradas para un agent id, log warning y usar reglas vacias (command-only bot) + +### Fase 4: Actualizar scripts + +- [ ] **4.1** Simplificar `dev-scripts/agent/new-agent.sh` — ya no necesita editar el map, solo añadir blank import +- [ ] **4.2** Actualizar `.claude/rules/create_agent.md` con el nuevo patron + +### Fase 5: Tests + +- [ ] **5.1** Test para `agents/registry.go` (register, get, get-missing) +- [ ] **5.2** `go build -tags goolm ./...` compila +- [ ] **5.3** `go test -tags goolm ./...` pasa + +### Fase 6: Cleanup + +- [ ] **6.1** Actualizar `CLAUDE.md` seccion sobre registro en launcher +- [ ] **6.2** Eliminar codigo muerto del launcher + +--- + +## Ejemplo de uso + +Antes (crear agente): +```go +// cmd/launcher/main.go — editar manualmente +import newagent "github.com/enmanuel/agents/agents/new-bot" +var rulesRegistry = map[string]func() []decision.Rule{ + "new-bot": newagent.Rules, // añadir esta linea +} +``` + +Despues: +```go +// agents/new-bot/agent.go — auto-registro +func init() { + agents.Register("new-bot", Rules) +} + +// cmd/launcher/main.go — solo blank import +import _ "github.com/enmanuel/agents/agents/new-bot" +``` + +## Decisiones de diseno + +- **init() + blank import**: patron estandar en Go (database/sql drivers, image codecs). Simple y familiar +- **Blank imports en launcher**: siguen siendo estaticos en el codigo, pero son una linea trivial sin logica. El script de scaffolding puede añadirla sin riesgo de romper sintaxis Go +- **No plugin system dinamico**: Go no tiene plugins portables. init() es el mecanismo idomatic + +## Prerequisitos + +- Ninguno (puede hacerse independiente de otros issues) + +## Riesgos + +- **Orden de init()**: Go garantiza init() dentro de un paquete, pero no entre paquetes. Mitigacion: el registro es un map simple, el orden no importa +- **Olvidar blank import**: si no se añade el blank import, el agente no se registra y el launcher lo trata como command-only. Mitigacion: el script de scaffolding lo añade automaticamente diff --git a/dev/issues/0029-core-tests.md b/dev/issues/0029-core-tests.md new file mode 100644 index 0000000..62a5eda --- /dev/null +++ b/dev/issues/0029-core-tests.md @@ -0,0 +1,99 @@ +# 0029 — Tests para runtime.go y config loader + +## Objetivo + +Añadir tests unitarios para las dos piezas criticas del sistema que actualmente tienen 0% de cobertura: `agents/runtime.go` y `internal/config/`. Cubrir al menos los flujos principales (command routing, tool-use loop, config parsing). + +## Contexto + +- `agents/runtime.go` (1,182 lineas) — 0 test files, 0 coverage +- `internal/config/` (schema.go + loader.go) — 0 test files, 0 coverage +- Los tests existentes cubren bien `pkg/` (puro) y parcialmente `tools/` +- Los unicos tests de integracion son E2E con Playwright (lentos, requieren infra) +- La falta de tests hace que refactors futuros (como 0026) sean arriesgados + +## Arquitectura + +``` +agents/runtime_test.go NEW → tests de handleEvent, runLLM, tool-use loop +agents/lifecycle_test.go → ya existe con tests basicos +internal/config/loader_test.go NEW → tests de parsing y validacion +internal/config/schema_test.go NEW → tests de defaults y campos requeridos +``` + +### Patron pure core / impure shell + +- Los tests de `agents/` usaran mocks/stubs para dependencias impuras (Matrix client, LLM) +- Los tests de `internal/config/` son puros (parsing de YAML) + +## Tareas + +### Fase 1: Test infrastructure + +- [ ] **1.1** Crear helpers de test: mock `CompleteFunc` que devuelve respuestas configurables +- [ ] **1.2** Crear mock Matrix client (o interfaz minima para send) +- [ ] **1.3** Crear fixture de `MessageContext` para tests + +### Fase 2: Tests de config + +- [ ] **2.1** Test: parsear config YAML minimo (solo campos requeridos) +- [ ] **2.2** Test: parsear config completo con todas las secciones +- [ ] **2.3** Test: expansion de env vars funciona (`$VAR` y `${VAR}`) +- [ ] **2.4** Test: config con campos desconocidos no falla (forward compat) +- [ ] **2.5** Test: valores default se aplican correctamente + +### Fase 3: Tests de command routing + +- [ ] **3.1** Test: mensaje con `!help` resuelve a built-in command +- [ ] **3.2** Test: mensaje con `!unknown` devuelve error +- [ ] **3.3** Test: comando registrado con `RegisterCommand` se ejecuta +- [ ] **3.4** Test: comando custom no sobrescribe built-in + +### Fase 4: Tests de rule evaluation + LLM dispatch + +- [ ] **4.1** Test: DM sin reglas → fallback a LLM +- [ ] **4.2** Test: DM sin LLM configurado → ignora mensaje +- [ ] **4.3** Test: regla matchea → ejecuta accion correspondiente +- [ ] **4.4** Test: ActionKindReply genera respuesta estatica +- [ ] **4.5** Test: ActionKindLLM invoca CompleteFunc con mensajes correctos + +### Fase 5: Tests de tool-use loop + +- [ ] **5.1** Test: LLM responde sin tool calls → devuelve texto +- [ ] **5.2** Test: LLM pide tool call → ejecuta tool → devuelve resultado al LLM → respuesta final +- [ ] **5.3** Test: tool call falla → error se pasa al LLM como tool result +- [ ] **5.4** Test: max iterations se respeta (no loop infinito) +- [ ] **5.5** Test: RBAC deniega tool call → error al LLM + +### Fase 6: Cleanup + +- [ ] **6.1** Verificar cobertura con `go test -cover -tags goolm ./agents/... ./internal/config/...` +- [ ] **6.2** Objetivo minimo: 50% coverage en ambos paquetes + +--- + +## Ejemplo de uso + +```bash +# Correr solo los tests nuevos +go test -tags goolm -v ./agents/ -run TestHandleEvent +go test -tags goolm -v ./internal/config/ -run TestLoadConfig + +# Cobertura +go test -tags goolm -cover ./agents/... ./internal/config/... +``` + +## Decisiones de diseno + +- **Mocks simples, no frameworks**: usar funciones Go nativas, no testify/mockery. Mantener dependencias minimas +- **Tests de tabla (table-driven)**: para command routing y rule evaluation, usar sub-tests con nombre descriptivo +- **No testear Matrix I/O**: los tests de runtime usan stubs de send, no conectan a un homeserver + +## Prerequisitos + +- Idealmente despues de 0026 (split runtime.go), pero puede hacerse antes si se estructura bien + +## Riesgos + +- **Acoplamiento a internals**: tests de runtime.go dependeran de la estructura actual del Agent struct. Mitigacion: testear comportamiento (input → output), no estado interno +- **Mocks divergen**: si el API de shell/ cambia, los mocks quedan desactualizados. Mitigacion: interfaces minimas diff --git a/dev/issues/0030-robot-vs-agent.md b/dev/issues/0030-robot-vs-agent.md new file mode 100644 index 0000000..97588fa --- /dev/null +++ b/dev/issues/0030-robot-vs-agent.md @@ -0,0 +1,157 @@ +# 0030 — Separacion Robot vs Agente + +## Objetivo + +Crear un tipo `Robot` como runtime ligero para bots que solo responden comandos, sin LLM, reglas, memoria ni tools. Distinguir en config entre `type: robot` y `type: agent` para que el launcher sepa que runtime instanciar. + +## Contexto + +- Actualmente todos los bots usan el mismo `Agent` struct (1,182 lineas) con 25+ subsistemas +- Un bot de comandos simples (ej: `!deploy prod`, `!status`) no necesita LLM, memoria, knowledge, skills, sanitizacion, ni tool-use +- Si `llm.primary.provider` esta vacio, `runtime.go` loguea "running as command-only bot" pero sigue inicializando todo el subsistema +- No hay forma idiomatica de crear un bot simple sin arrastrar toda la complejidad +- Ejemplos de robots: bot de deploys, bot de health checks, bot de notificaciones, bot de CI/CD + +## Arquitectura + +``` +agents/robot.go NEW → Robot struct (~150 lineas): Matrix + Commands +agents/robot_test.go NEW → tests del runtime minimo +agents/types.go NEW → interfaz comun Runner { Run(ctx), Stop(), RegisterCommand() } +cmd/launcher/main.go → detectar type: robot y crear Robot en vez de Agent +internal/config/schema.go → añadir campo Agent.Type ("robot" | "agent") +``` + +### Patron pure core / impure shell + +- `pkg/` — sin cambios (el Robot no usa decision engine ni reglas) +- `shell/matrix/` — sin cambios (el Robot reutiliza el mismo cliente Matrix) +- `agents/robot.go` — impuro (tiene Matrix client), pero minimo +- `agents/runtime.go` — sin cambios (Agent sigue igual) + +## Tareas + +### Fase 1: Definir interfaz comun + +- [ ] **1.1** Crear `agents/types.go` con interfaz `Runner`: + ```go + type Runner interface { + Run(ctx context.Context) error + Stop() + RegisterCommand(spec command.Spec, handler CommandHandler) + } + ``` +- [ ] **1.2** Verificar que `Agent` ya satisface `Runner` (o adaptar) + +### Fase 2: Implementar Robot + +- [ ] **2.1** Crear `agents/robot.go` con struct `Robot`: + - `matrix *matrix.Client` + - `commands *command.Registry` (built-ins + custom) + - `logger *slog.Logger` + - `config config.AgentConfig` +- [ ] **2.2** Implementar `NewRobot(cfg, logger)` — solo inicializa Matrix + commands +- [ ] **2.3** Implementar `Run()` — sync loop que solo despacha comandos +- [ ] **2.4** Implementar `Stop()` — cierra Matrix client +- [ ] **2.5** Implementar `RegisterCommand()` — registra comando custom +- [ ] **2.6** En `handleEvent()`: si no es comando, ignorar silenciosamente (no hay LLM) + +### Fase 3: Config y launcher + +- [ ] **3.1** Añadir campo `Type string` a `AgentCfg` en schema.go (default: "agent") +- [ ] **3.2** En launcher: si `cfg.Agent.Type == "robot"`, crear `NewRobot()` en vez de `New()` +- [ ] **3.3** El launcher usa la interfaz `Runner` para manejar ambos tipos uniformemente + +### Fase 4: Template y scaffolding + +- [ ] **4.1** Crear `agents/_template_robot/` con config minimo para robots +- [ ] **4.2** Config de robot ejemplo (~20 lineas): + ```yaml + agent: + id: deploy-bot + type: robot + description: "Bot de deploys" + personality: + prefix: "🤖" + matrix: + threads: + enabled: true + ``` +- [ ] **4.3** Actualizar `dev-scripts/agent/create-full.sh` para aceptar flag `--robot` + +### Fase 5: Tests + +- [ ] **5.1** Test: Robot responde a `!help` con lista de comandos +- [ ] **5.2** Test: Robot responde a `!ping` con pong +- [ ] **5.3** Test: Robot ignora mensajes normales (no es error, simplemente no responde) +- [ ] **5.4** Test: Robot con comando custom registrado lo ejecuta +- [ ] **5.5** Test: `Runner` interfaz es satisfecha por ambos `Agent` y `Robot` + +### Fase 6: Documentacion + +- [ ] **6.1** Actualizar `CLAUDE.md` con seccion Robot vs Agent +- [ ] **6.2** Actualizar `.claude/rules/create_agent.md` mencionando la opcion robot +- [ ] **6.3** Añadir tabla comparativa en docs + +--- + +## Ejemplo de uso + +```yaml +# agents/deploy-bot/config.yaml +agent: + id: deploy-bot + type: robot + description: "Bot de deploys con comandos directos" + +personality: + prefix: "🚀" + +matrix: + homeserver: ${MATRIX_HOMESERVER} + user_id: "@deploy-bot:matrix-af2f3d.organic-machine.com" + access_token_env: MATRIX_TOKEN_DEPLOY_BOT +``` + +```go +// agents/deploy-bot/commands.go +package deploy + +func Commands() []agents.CommandEntry { + return []agents.CommandEntry{ + { + Spec: command.Spec{Name: "deploy", Usage: "!deploy "}, + Handler: func(ctx context.Context, msg decision.MessageContext) string { + return fmt.Sprintf("Deploying to %s...", msg.Args[0]) + }, + }, + } +} +``` + +Interaccion en Element: +``` +Usuario: !deploy staging +Bot: 🚀 Deploying to staging... + +Usuario: hola bot +Bot: (silencio — no tiene LLM) +``` + +## Decisiones de diseno + +- **Interfaz `Runner`**: permite al launcher tratar robots y agentes uniformemente sin type switches +- **Robot NO tiene reglas**: las reglas son para routing inteligente. Un robot solo hace dispatch de comandos +- **Robot NO tiene memory/knowledge/skills**: mantener el runtime lo mas ligero posible +- **Config minimo**: un robot funcional se define en ~20 lineas de YAML +- **Silencio ante mensajes normales**: un robot no responde "no entiendo", simplemente ignora. Los comandos tienen `!help` para descubrirse + +## Prerequisitos + +- Ninguno (puede hacerse independiente) +- Se beneficia de 0026 (split runtime) pero no lo requiere + +## Riesgos + +- **Duplicacion**: Robot y Agent comparten logica de Matrix, commands, lifecycle. Mitigacion: reutilizar `shell/matrix/` y `pkg/command/` sin duplicar +- **Scope creep**: tentacion de añadir "un poquito de LLM" al Robot. Mitigacion: la linea es clara — si necesita LLM, es un Agent diff --git a/dev/issues/0031-expand-file-tools.md b/dev/issues/0031-expand-file-tools.md new file mode 100644 index 0000000..1d4d7b4 --- /dev/null +++ b/dev/issues/0031-expand-file-tools.md @@ -0,0 +1,179 @@ +# 0031 — Expandir tools/file/ con write, list, append, delete + +## Objetivo + +Ampliar el paquete `tools/file/` con operaciones de escritura, listado, append y borrado. Mantener el patron deny-by-default, validacion de symlinks, y respetar el flag `read_only` del config. + +## Contexto + +- `tools/file/file.go` actualmente solo tiene `read_file` (107 lineas) +- Seguridad existente: deny-by-default, symlink resolution via `EvalSymlinks`, output truncation a 64KB +- Helpers existentes: `validatePath()` y `resolveReal()` ya estan listos para reutilizarse +- Config `FileOpsCfg` tiene campos `AllowedPaths []string` y `ReadOnly bool` — ReadOnly ya existe pero no se usa porque no hay operaciones de escritura +- Los agentes necesitan interactuar con carpetas de trabajo (workspaces, proyectos, outputs) + +## Arquitectura + +``` +tools/file/file.go → mantener read_file + validatePath (existente) +tools/file/write.go NEW → write_file tool +tools/file/list.go NEW → list_directory tool +tools/file/append.go NEW → append_file tool +tools/file/delete.go NEW → delete_file tool +tools/file/file_test.go → ampliar tests existentes +tools/file/write_test.go NEW → tests de escritura +tools/file/list_test.go NEW → tests de listado +tools/file/delete_test.go NEW → tests de borrado +agents/runtime.go → registrar nuevas tools en buildToolRegistry() +``` + +### Patron pure core / impure shell + +- `pkg/` — sin cambios +- `tools/file/` — cada tool sigue el patron Def (puro) + Exec (impuro) +- `agents/` — solo cambio en registro de tools + +## Tareas + +### Fase 1: Refactor de validacion compartida + +- [ ] **1.1** Extraer `validatePath()` y `resolveReal()` a `tools/file/validate.go` (ya son funciones internas, solo moverlas para reutilizarlas) +- [ ] **1.2** Crear helper `validateWritePath(absPath, cfg)` que ademas verifica `ReadOnly == false` + +### Fase 2: write_file + +- [ ] **2.1** Crear `tools/file/write.go` con `NewWriteFile(cfg) tools.Tool` +- [ ] **2.2** Parametros: `path` (string, required), `content` (string, required) +- [ ] **2.3** Validaciones: + - Rechazar si `cfg.ReadOnly == true` + - `validatePath()` contra AllowedPaths + - Crear directorios padre si no existen (`os.MkdirAll`) + - Limite de contenido: max 1MB de input +- [ ] **2.4** Devolver confirmacion con bytes escritos y path + +### Fase 3: list_directory + +- [ ] **3.1** Crear `tools/file/list.go` con `NewListDirectory(cfg) tools.Tool` +- [ ] **3.2** Parametros: `path` (string, required), `recursive` (boolean, optional, default false) +- [ ] **3.3** Validaciones: + - `validatePath()` contra AllowedPaths + - Limite de entries: max 500 archivos en el output + - No seguir symlinks fuera de AllowedPaths +- [ ] **3.4** Output: lista con nombre, tamaño, tipo (file/dir), fecha modificacion + +### Fase 4: append_file + +- [ ] **4.1** Crear `tools/file/append.go` con `NewAppendFile(cfg) tools.Tool` +- [ ] **4.2** Parametros: `path` (string, required), `content` (string, required) +- [ ] **4.3** Validaciones: + - Rechazar si `cfg.ReadOnly == true` + - `validatePath()` contra AllowedPaths + - Si el archivo no existe, crearlo (igual que write) + - Limite de tamaño: verificar que archivo existente + contenido nuevo < 10MB +- [ ] **4.4** Devolver confirmacion con bytes añadidos y tamaño total + +### Fase 5: delete_file + +- [ ] **5.1** Crear `tools/file/delete.go` con `NewDeleteFile(cfg) tools.Tool` +- [ ] **5.2** Parametros: `path` (string, required) +- [ ] **5.3** Validaciones: + - Rechazar si `cfg.ReadOnly == true` + - `validatePath()` contra AllowedPaths + - **Solo archivos**: no permitir borrar directorios (prevencion de `rm -rf` accidental) + - Resolver symlinks antes de borrar (no borrar el symlink si apunta fuera de AllowedPaths) +- [ ] **5.4** Devolver confirmacion con path eliminado + +### Fase 6: Registro en runtime + +- [ ] **6.1** En `agents/runtime.go` → `buildToolRegistry()`, registrar las 4 tools nuevas condicionalmente: + ```go + if cfg.Tools.FileOps.Enabled { + reg.Register(file.NewReadFile(cfg.Tools.FileOps)) + if !cfg.Tools.FileOps.ReadOnly { + reg.Register(file.NewWriteFile(cfg.Tools.FileOps)) + reg.Register(file.NewAppendFile(cfg.Tools.FileOps)) + reg.Register(file.NewDeleteFile(cfg.Tools.FileOps)) + } + reg.Register(file.NewListDirectory(cfg.Tools.FileOps)) + } + ``` +- [ ] **6.2** `list_directory` se registra siempre (no requiere escritura) + +### Fase 7: Tests + +- [ ] **7.1** Test: `write_file` crea archivo nuevo en AllowedPaths +- [ ] **7.2** Test: `write_file` rechaza si ReadOnly es true +- [ ] **7.3** Test: `write_file` rechaza paths fuera de AllowedPaths +- [ ] **7.4** Test: `write_file` rechaza contenido > 1MB +- [ ] **7.5** Test: `list_directory` lista correctamente archivos y subdirectorios +- [ ] **7.6** Test: `list_directory` respeta limite de 500 entries +- [ ] **7.7** Test: `list_directory` no sigue symlinks fuera de AllowedPaths +- [ ] **7.8** Test: `append_file` añade contenido al final +- [ ] **7.9** Test: `append_file` crea archivo si no existe +- [ ] **7.10** Test: `delete_file` borra archivo existente +- [ ] **7.11** Test: `delete_file` rechaza borrar directorios +- [ ] **7.12** Test: `delete_file` rechaza si ReadOnly es true +- [ ] **7.13** Test: symlink que apunta fuera de AllowedPaths es rechazado en todas las tools +- [ ] **7.14** Test: path traversal (`../`) es rechazado en todas las tools + +### Fase 8: Cleanup + +- [ ] **8.1** Actualizar `CLAUDE.md` seccion de tools con las nuevas herramientas +- [ ] **8.2** Actualizar `.claude/rules/create_tool.md` si hay nuevos patrones +- [ ] **8.3** `go build -tags goolm ./...` y `go test -tags goolm ./...` + +--- + +## Ejemplo de uso + +Config del agente: +```yaml +tools: + file: + enabled: true + allowed_paths: + - "/home/ubuntu/workspace/proyecto-x" + read_only: false +``` + +Interaccion en Element: +``` +Usuario: Lista los archivos en /home/ubuntu/workspace/proyecto-x/src +Bot: [usa list_directory] Encontre 12 archivos: + - main.go (2.3 KB, 2026-04-01) + - handler.go (1.1 KB, 2026-04-02) + - ... + +Usuario: Escribe un archivo test.txt con "hola mundo" +Bot: [usa write_file] Archivo creado: /home/ubuntu/workspace/proyecto-x/test.txt (10 bytes) + +Usuario: Añade una linea mas al test.txt +Bot: [usa append_file] Contenido añadido: 15 bytes (total: 25 bytes) + +Usuario: Borra el test.txt +Bot: [usa delete_file] Archivo eliminado: /home/ubuntu/workspace/proyecto-x/test.txt +``` + +Intento fuera de AllowedPaths: +``` +Usuario: Lee /etc/passwd +Bot: Error: path "/etc/passwd" not under any allowed path +``` + +## Decisiones de diseno + +- **ReadOnly como gate**: `write_file`, `append_file`, `delete_file` solo se registran si `ReadOnly == false`. `read_file` y `list_directory` siempre se registran si file tools esta habilitado +- **Solo archivos en delete**: borrar directorios es demasiado peligroso para un agente autonomo. Si necesita borrar un directorio, puede borrar archivos uno por uno +- **Limites de tamaño**: 1MB para write (evita saturar disco), 64KB para read output (evita saturar contexto LLM), 500 entries para list (evita listados enormes) +- **Crear padres automaticamente**: `write_file` hace `MkdirAll` para crear la estructura de directorios. Simplifica el uso sin riesgo de seguridad (los paths padre tambien estan bajo AllowedPaths) +- **Reutilizar validatePath()**: misma logica de seguridad para todas las operaciones. Un solo punto de validacion + +## Prerequisitos + +- Ninguno + +## Riesgos + +- **Escritura accidental**: un agente con LLM podria decidir escribir archivos incorrectos. Mitigacion: AllowedPaths restringe donde puede escribir, y el system prompt debe instruir al agente sobre cuando escribir +- **Race conditions**: dos agentes escribiendo el mismo archivo. Mitigacion: file locking no es necesario en la primera version; los agentes tipicamente tienen workspaces separados +- **Disk exhaustion**: un agente escribiendo en loop. Mitigacion: rate limiting del tool registry + limite de 1MB por write diff --git a/dev/issues/0032-e2e-create-agent-skill.md b/dev/issues/0032-e2e-create-agent-skill.md new file mode 100644 index 0000000..d86faff --- /dev/null +++ b/dev/issues/0032-e2e-create-agent-skill.md @@ -0,0 +1,126 @@ +# 0032 — E2E: verificar skill /create-agent con agente de prueba + +## Objetivo + +Crear un agente de prueba con personalidad muy marcada usando la skill `/create-agent` y escribir tests E2E con Playwright que verifiquen que el agente se creo correctamente y responde en Matrix con la personalidad esperada. Esto valida el pipeline completo: scaffold → build → register → verify → respuesta funcional. + +## Contexto + +- La skill `/create-agent` existe en `.claude/skills/create-agent/` y ejecuta `create-full.sh` internamente +- Ya hay E2E tests para `assistant-bot` y `asistente-2` en `e2e/tests/` +- Los tests E2E usan Playwright contra Element Web + homeserver real +- No hay tests que validen el pipeline de creacion de agentes — solo se testean agentes ya existentes +- El agente de prueba tendra una personalidad exagerada y facilmente verificable (ej: habla como pirata, responde siempre con rimas, etc.) para que los assertions sean robustos + +## Arquitectura + +``` +agents/test-personality/ NEW — agente creado por /create-agent +agents/test-personality/agent.go NEW — reglas puras (llm-all) +agents/test-personality/config.yaml NEW — config con personalidad marcada +agents/test-personality/prompts/ NEW — system prompt con personalidad exagerada +cmd/launcher/main.go MOD — registro del agente en rulesRegistry +e2e/tests/test-personality.spec.ts NEW — tests E2E del agente +e2e/tests/create-agent-pipeline.spec.ts NEW — tests E2E del pipeline de creacion +``` + +### Patron pure core / impure shell + +- `pkg/` — sin cambios +- `shell/` — sin cambios +- `agents/test-personality/` — composicion: agent.go puro (reglas) + config YAML + system prompt +- `tools/` — sin cambios +- `e2e/` — tests Playwright (fuera del modulo Go) + +## Tareas + +### Fase 1: Crear agente de prueba con /create-agent + +- [ ] **1.1** Ejecutar `/create-agent test-personality "Test Personality"` con los siguientes inputs: + - `agent-id`: `test-personality` + - `display-name`: `"Test Personality"` + - `description`: `"Agente de prueba con personalidad de pirata espacial para validar el pipeline de creacion"` + - `llm.provider`: `openai` (default) + - `llm.model`: `gpt-4o` (default) + - `tool_use`: `false` + - System prompt: personalidad de **pirata espacial** — siempre habla con jerga pirata mezclada con terminos de ciencia ficcion, usa emojis de calavera y cohetes, empieza cada respuesta con "¡Arrr, cosmonauta!" o variante, y termina con "¡Que la marea estelar te acompane!" +- [ ] **1.2** Verificar que `create-full.sh` completa las 4 etapas sin errores (scaffold, build, register, verify) +- [ ] **1.3** Personalizar `agents/test-personality/prompts/system.md` con la personalidad de pirata espacial (bien exagerada para que sea facilmente detectable en tests) +- [ ] **1.4** Verificar compilacion: `go build -tags goolm ./...` +- [ ] **1.5** Arrancar el servidor y verificar que el agente responde en Matrix: `./dev-scripts/server/start.sh` + +### Fase 2: E2E tests del agente + +- [ ] **2.1** Crear `e2e/tests/test-personality.spec.ts` con los siguientes tests: + - **Responde a saludo**: enviar "Hola" → verificar que la respuesta contiene jerga pirata/espacial (keywords: "arrr", "cosmonauta", "estelar", "marea", o similares) + - **Personalidad consistente**: enviar pregunta seria ("Que es la gravedad?") → verificar que responde con contenido correcto pero manteniendo la personalidad (jerga pirata/espacial presente) + - **!help funciona**: enviar `!help` → verificar que lista comandos (built-in commands) + - **!ping funciona**: enviar `!ping` → verificar respuesta + - **Sin errores de descifrado**: verificar `assertNoDecryptionErrors` en cada test +- [ ] **2.2** Seguir el patron de los tests existentes (`assistant-bot.spec.ts`) para fixtures, imports y estructura +- [ ] **2.3** Ejecutar los tests y verificar que pasan: `./dev-scripts/e2e/run.sh test-personality` + +### Fase 3: E2E test del pipeline de creacion (validacion estructural) + +- [ ] **3.1** Crear `e2e/tests/create-agent-pipeline.spec.ts` (o un test dentro de `test-personality.spec.ts`) que valide la estructura generada por el pipeline: + - Verificar que `agents/test-personality/agent.go` existe y exporta `Rules()` + - Verificar que `agents/test-personality/config.yaml` tiene `agent.id: test-personality` + - Verificar que `agents/test-personality/prompts/system.md` contiene la seccion de seguridad obligatoria + - Verificar que `cmd/launcher/main.go` contiene el import y la entrada en `rulesRegistry` +- [ ] **3.2** Estos tests pueden ser scripts bash o tests de Node.js que lean los archivos — no requieren Playwright + +### Fase 4: Tests + +- [ ] **4.1** Ejecutar suite E2E completa: `./dev-scripts/e2e/run.sh` (todos los tests, incluyendo los nuevos) +- [ ] **4.2** Verificar que los tests existentes de `assistant-bot` y `asistente-2` siguen pasando (no regresion) +- [ ] **4.3** Verificar build completo: `go build -tags goolm ./...` y `go test -tags goolm ./...` + +### Fase 5: Cleanup y docs + +- [ ] **5.1** Actualizar `CLAUDE.md` tabla de agentes con `test-personality` +- [ ] **5.2** Documentar en `e2e/README.md` el nuevo spec y la estrategia de personalidad para tests + +--- + +## Ejemplo de uso + +``` +# 1. Crear el agente con la skill +> /create-agent test-personality "Test Personality" +(skill ejecuta create-full.sh, personaliza archivos) + +# 2. Arrancar y probar manualmente +> ./dev-scripts/server/start.sh +> (en Matrix) Hola! +< ¡Arrr, cosmonauta! 🏴‍☠️🚀 Bienvenido a bordo de la nave... + ¡Que la marea estelar te acompane! + +# 3. Correr E2E +> ./dev-scripts/e2e/run.sh test-personality + ✓ responde con personalidad de pirata espacial (15s) + ✓ personalidad consistente en respuestas serias (18s) + ✓ !help muestra comandos (3s) + ✓ !ping responde (2s) + 4 passed +``` + +## Decisiones de diseno + +- **Pirata espacial como personalidad**: es suficientemente exagerada para generar keywords detectables (arrr, cosmonauta, estelar, marea) pero no tan absurda como para que el LLM la ignore. Las assertions buscan presencia de al menos una keyword de un set, no matching exacto. +- **Assertions flexibles para LLM**: las respuestas LLM son no-deterministicas, asi que verificamos presencia de keywords del tema pirata/espacial, no texto exacto. Para `!help` y `!ping` si usamos assertions estrictas (son comandos deterministicos). +- **Test de pipeline como script separado**: la validacion estructural (archivos existen, config correcto) no necesita Playwright, asi que puede ser un test de Node.js simple o bash script. Esto lo hace mas rapido y mas facil de debuggear. +- **Agente permanente**: el agente de prueba se queda en el repo como agente real. Sirve como referencia de creacion y como target permanente para E2E tests del pipeline. + +## Prerequisitos + +- E2E infrastructure funcionando (issue 0022 completado) +- Skill `/create-agent` funcionando (ya existe en `.claude/skills/create-agent/`) +- Variables de entorno del homeserver configuradas (`MATRIX_ADMIN_TOKEN`, etc.) +- Element Web disponible para tests Playwright + +## Riesgos + +- **LLM no respeta personalidad**: mitigacion — system prompt muy explicito y exagerado, keywords amplias (buscar cualquiera de un set, no todas) +- **Rate limits del LLM**: mitigacion — pocos tests con respuesta LLM (2-3), el resto son comandos directos +- **create-full.sh falla por estado previo**: mitigacion — verificar que no exista `agents/test-personality/` antes de ejecutar, o limpiar si existe +- **Flakiness en E2E por timing**: mitigacion — timeouts generosos (60s para LLM), reintentos en el pipeline de Playwright diff --git a/dev/issues/README.md b/dev/issues/README.md index 235ad36..6592c16 100644 --- a/dev/issues/README.md +++ b/dev/issues/README.md @@ -36,3 +36,10 @@ afectados y notas de implementacion. | 24b | Security loader: shell/security/ | [0024b-security-loader.md](completed/0024b-security-loader.md) | completado | | 24c | Security integration + cleanup | [0024c-security-integration.md](completed/0024c-security-integration.md) | completado | | 25 | Catálogo cron + scaffolder | [0025-cron-scaffolder.md](completed/0025-cron-scaffolder.md) | completado | +| 26 | Refactorizar runtime.go | [0026-split-runtime.md](0026-split-runtime.md) | pendiente | +| 27 | Limpiar config schema | [0027-prune-config-schema.md](0027-prune-config-schema.md) | pendiente | +| 28 | Desacoplar launcher | [0028-decouple-launcher.md](0028-decouple-launcher.md) | pendiente | +| 29 | Tests para runtime y config | [0029-core-tests.md](0029-core-tests.md) | pendiente | +| 30 | Separacion Robot vs Agente | [0030-robot-vs-agent.md](0030-robot-vs-agent.md) | pendiente | +| 31 | Expandir tools/file/ | [0031-expand-file-tools.md](0031-expand-file-tools.md) | pendiente | +| 32 | E2E: verificar skill /create-agent | [0032-e2e-create-agent-skill.md](0032-e2e-create-agent-skill.md) | pendiente |