Files
agents_and_robots/agents/memory.go
T
egutierrez 42251ecada refactor: separar runtime.go en archivos por responsabilidad
Divide agents/runtime.go (1188 lineas) en 5 archivos especializados:

- runtime.go (350 lineas): struct Agent, New(), Run(), Stop(), lifecycle
- handler.go: handleEvent(), executeActions(), command routing, bus, sanitizacion
- llm.go: runLLM(), tool-use loop, system prompt, initLLM(), prompt-commands
- memory.go: ensureWindowLoaded(), appendToWindow(), persistMessage(), ClearWindow()
- registry_build.go: buildToolRegistry(), initToolDeps(), initRateLimiter()

Zero cambios en API publica. Todos los metodos siguen siendo del struct Agent,
solo viven en archivos separados por responsabilidad.

Funciones helper extraidas de New() para reducir su tamaño:
- initCrypto(): inicializacion E2EE
- initLLM(): cliente LLM con fallback
- initMemoryStore(): store SQLite + window size
- initToolDeps(): knowledge, MCP, skills
- initRateLimiter(): rate limiting de tools

Reduccion: 1188 → 350 lineas en runtime.go (70% menos).
2026-04-09 00:21:42 +00:00

120 lines
3.4 KiB
Go

package agents
import (
"context"
"fmt"
"log/slog"
"path/filepath"
coretypes "github.com/enmanuel/agents/pkg/llm"
"github.com/enmanuel/agents/pkg/memory"
shellmem "github.com/enmanuel/agents/shell/memory"
)
// ClearWindow resets the conversation window for a room and deletes persisted
// messages from SQLite so the agent starts fresh. Implements toolmemory.WindowClearer.
func (a *Agent) ClearWindow(roomID string) {
a.windowsMu.Lock()
a.windows[roomID] = memory.NewWindow(a.windowSize)
a.windowsMu.Unlock()
if a.memStore != nil {
if err := a.memStore.DeleteMessages(
context.Background(), a.cfg.Agent.ID, &roomID,
); err != nil {
a.logger.Warn("failed to delete persisted messages on clear", "room", roomID, "err", err)
}
}
}
// ensureWindowLoaded loads the conversation window from SQLite on first access for a room.
func (a *Agent) ensureWindowLoaded(ctx context.Context, roomID string) {
a.windowsMu.Lock()
defer a.windowsMu.Unlock()
if _, ok := a.windows[roomID]; ok {
return
}
w := memory.NewWindow(a.windowSize)
if a.memStore != nil {
msgs, err := a.memStore.LoadMessages(ctx, a.cfg.Agent.ID, roomID, a.windowSize)
if err != nil {
a.logger.Warn("failed to load message history", "room", roomID, "err", err)
} else {
for _, m := range msgs {
w = w.Append(coretypes.Message{Role: m.Role, Content: m.Content})
}
if len(msgs) > 0 {
a.logger.Debug("loaded message history", "room", roomID, "count", len(msgs))
}
}
}
a.windows[roomID] = w
}
// appendToWindow adds a message to the in-memory conversation window.
func (a *Agent) appendToWindow(roomID string, msg coretypes.Message) {
a.windowsMu.Lock()
defer a.windowsMu.Unlock()
w, ok := a.windows[roomID]
if !ok {
w = memory.NewWindow(a.windowSize)
}
a.windows[roomID] = w.Append(msg)
}
// getWindowMessages returns a copy of the conversation window for a room.
func (a *Agent) getWindowMessages(roomID string) []coretypes.Message {
a.windowsMu.RLock()
defer a.windowsMu.RUnlock()
w, ok := a.windows[roomID]
if !ok {
return nil
}
return w.ToLLMMessages()
}
// persistMessage saves a message to the SQLite store (no-op if store is nil).
func (a *Agent) persistMessage(ctx context.Context, roomID string, role coretypes.Role, content string) {
if a.memStore == nil {
return
}
if err := a.memStore.SaveMessage(ctx, memory.HistoryMessage{
AgentID: a.cfg.Agent.ID,
RoomID: roomID,
Role: role,
Content: content,
}); err != nil {
a.logger.Warn("failed to persist message", "room", roomID, "err", err)
}
}
// memoryInit holds the results of memory subsystem initialization.
type memoryInit struct {
store memory.Store
windowSize int
}
// initMemoryStore creates the memory store and resolves window size from config.
// Returns a zero-value memoryInit if memory is disabled.
func initMemoryStore(enabled bool, windowSizeCfg int, dbPathCfg string, dataBase string, logger *slog.Logger) (memoryInit, error) {
if !enabled {
return memoryInit{windowSize: defaultWindowSize}, nil
}
windowSize := windowSizeCfg
if windowSize <= 0 {
windowSize = defaultWindowSize
}
dbPath := dbPathCfg
if dbPath == "" {
dbPath = filepath.Join(dataBase, "memory.db")
}
store, err := shellmem.New(dbPath, logger)
if err != nil {
return memoryInit{}, fmt.Errorf("memory store: %w", err)
}
logger.Info("memory enabled", "window_size", windowSize, "db", dbPath)
return memoryInit{store: store, windowSize: windowSize}, nil
}