package loop import ( "context" "strings" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model" ) // injectMemory retrieves the persona's relevant long-term memories and injects // them into the first-turn context as a leading system message, so the model can // ground its answer in recalled context (design §6 / §2). It returns the input // messages unchanged when no retriever/persona is configured, retrieval fails, or // nothing relevant is found. func (l *Loop) injectMemory(ctx context.Context, messages []model.Message) []model.Message { if l.retriever == nil || l.personaID == "" { return messages } block, err := l.retriever.Inject(ctx, l.personaID, queryFromMessages(messages), 0) if err != nil { l.log.Warn("memory inject", "error", err) return messages } if block == "" { return messages } out := make([]model.Message, 0, len(messages)+1) out = append(out, model.TextMessage(model.RoleSystem, block)) out = append(out, messages...) return out } // extractMemory runs the extraction hook (design §6) over the current // conversation and persists any derived items. Errors are logged, never fatal, so // a bad extraction cannot abort the run. func (l *Loop) extractMemory(ctx context.Context, messages []model.Message, step int) { if l.extractor == nil || l.memory == nil { return } items, err := l.extractor.Extract(ctx, messages) if err != nil { l.log.Warn("memory extract", "step", step, "error", err) return } for _, it := range items { if it.PersonaID == "" && l.personaID != "" { it.PersonaID = l.personaID } if _, err := l.memory.Add(ctx, it); err != nil { l.log.Warn("memory store", "step", step, "error", err) } } } // queryFromMessages derives a retrieval query from the conversation: the last // user turn's text, falling back to the last message's text, then "". func queryFromMessages(messages []model.Message) string { for i := len(messages) - 1; i >= 0; i-- { if messages[i].Role == model.RoleUser { return messageText(messages[i]) } } if n := len(messages); n > 0 { return messageText(messages[n-1]) } return "" } // messageText returns the concatenated plain-text payload of a message's text // parts, which is the shared view used to build a retrieval query. func messageText(m model.Message) string { var b strings.Builder for _, part := range m.Content { if part.Type != model.ContentTypeText { continue } if b.Len() > 0 { b.WriteByte(' ') } b.WriteString(part.Text) } return b.String() }