package memory import ( "fmt" "strings" "time" ) type MemoryInjector struct { store *Store } func NewInjector(store *Store) *MemoryInjector { return &MemoryInjector{store: store} } func (mi *MemoryInjector) BuildContextBlock(query string) (string, error) { var contextParts []string preferences, err := mi.store.RecallPreferences() if err == nil && len(preferences) > 0 { var prefLines []string for _, p := range preferences { prefLines = append(prefLines, fmt.Sprintf("- %s: %s", p.Key, p.Content)) } contextParts = append(contextParts, "[User Preferences]\n"+strings.Join(prefLines, "\n")) } facts, err := mi.store.RecallFacts() if err == nil && len(facts) > 0 { var factLines []string for _, f := range facts { factLines = append(factLines, fmt.Sprintf("- %s: %s", f.Key, f.Content)) } contextParts = append(contextParts, "[Known Facts]\n"+strings.Join(factLines, "\n")) } if query != "" { relevant, err := mi.store.Recall(query, 5) if err == nil && len(relevant) > 0 { var relLines []string for _, r := range relevant { relLines = append(relLines, fmt.Sprintf("- [%s] %s: %s", r.Type, r.Key, truncate(r.Content, 150))) } contextParts = append(contextParts, "[Relevant Memories]\n"+strings.Join(relLines, "\n")) } } recentCutoff := time.Now().Add(-24 * time.Hour) recent, err := mi.store.RecallRecent(recentCutoff, 5) if err == nil && len(recent) > 0 { var recentLines []string for _, r := range recent { recentLines = append(recentLines, fmt.Sprintf("- [%s] %s", r.Type, truncate(r.Content, 100))) } contextParts = append(contextParts, "[Recent Context]\n"+strings.Join(recentLines, "\n")) } if len(contextParts) == 0 { return "", nil } return fmt.Sprintf("\n[System note: NOT new user input — recalled context]\n%s\n", strings.Join(contextParts, "\n\n")), nil } func (mi *MemoryInjector) BuildSystemPromptBlock() (string, error) { preferences, err := mi.store.RecallPreferences() if err != nil || len(preferences) == 0 { return "", nil } var lines []string lines = append(lines, "Known user preferences:") for _, p := range preferences { lines = append(lines, fmt.Sprintf("- %s: %s", p.Key, p.Content)) } return strings.Join(lines, "\n"), nil } func (mi *MemoryInjector) ExtractAndStore(userMessage, assistantMessage string) error { pref := extractPreference(userMessage) if pref != "" { if err := mi.store.StorePreference("detected", pref); err != nil { return fmt.Errorf("store preference: %w", err) } } if assistantMessage != "" { ctx := extractContext(assistantMessage) if ctx != "" { if err := mi.store.StoreContext("conversation", ctx); err != nil { return fmt.Errorf("store context: %w", err) } } } return nil } func extractPreference(message string) string { indicators := []string{ "i prefer", "i like", "i always", "i never", "my favorite", "i use", "je préfère", "j'aime", "toujours", "jamais", } lower := strings.ToLower(message) for _, ind := range indicators { if strings.Contains(lower, ind) { idx := strings.Index(lower, ind) end := idx + len(ind) + 100 if end > len(message) { end = len(message) } return truncate(message[idx:end], 200) } } return "" } func extractContext(message string) string { if len(message) < 50 { return "" } if len(message) > 500 { return truncate(message, 500) } return message } func truncate(s string, maxLen int) string { if len(s) <= maxLen { return s } return s[:maxLen] + "..." }