package devagents 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 }