aba07fef60
Mueve el issue a dev/issues/completed/ y actualiza el README. Issue completado: runtime.go dividido en 5 archivos especializados con tests para buildToolRegistry.
3.8 KiB
3.8 KiB
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.goconcentra: 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) yhandleEvent()(100 lineas) tienen complejidad ciclomatica estimada de 10-15 New()tiene 262 lineas de inicializacion secuencial para 10+ subsistemas- El struct
Agenttiene 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 cambiosagents/— refactoring interno, zero cambios en API publica
Tareas
Fase 1: Extraer handler
- 1.1 Crear
agents/handler.goconhandleEvent()y metodos de routing de comandos - 1.2 Mover logica de evaluacion de reglas y fallback LLM
- 1.3 Verificar que
runtime.gosolo llama aa.handleEvent()como entry point
Fase 2: Extraer LLM
- 2.1 Crear
agents/llm.goconrunLLM(),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.goconensureWindowLoaded(),appendToWindow(),persistMessage() - 3.2 Mover la inicializacion de memory store desde
New()
Fase 4: Extraer registry builder
- 4.1 Crear
agents/registry_build.goconbuildToolRegistry() - 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.goqueda < 300 lineas - 6.2 Actualizar imports si es necesario
- 6.3
go build -tags goolm ./...ygo test -tags goolm ./...pasan
Ejemplo de uso
No hay cambio funcional. Antes y despues:
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