# 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
