# 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