# 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