package runtime import ( "context" "strconv" "github.com/krow/krow-backend/go-api/internal/authctx" "github.com/krow/krow-backend/go-api/internal/memory" ) // Recaller is the memory store's read half, as the loop needs it. // // An interface rather than the struct so the runtime does not depend on // memory's internals and a test can drive the loop without a database — the // same shape Retriever has, for the same reason. type Recaller interface { Recall(ctx context.Context, who authctx.Identity, question string, limit int) ([]memory.Record, string, error) } // WithMemory gives the executor a long-term memory to read. // // Optional, like the retriever. Nil means a deployment that has not migrated // 000017, or has chosen not to remember, and the loop behaves exactly as it // did before memory existed. func (m *ModelExecutor) WithMemory(r Recaller) *ModelExecutor { m.memory = r return m } // recall returns the memory block for this question, or "". // // FAILS QUIET, LOUDLY RECORDED. A memory store that is unreachable must not // take the run with it: the answer without memory is worse, not wrong, and the // alternative is an outage in the knowledge layer becoming an outage in the // product. The trajectory says what happened, so an answer that reads thin is // explainable afterwards rather than mysterious. // // The degraded note from the store — "no embedder is configured; these // memories are the most recent rather than the most relevant" — is recorded // too. A reader comparing two answers needs to know which one got relevance // and which got recency. func (m *ModelExecutor) recall(ctx context.Context, rec *Recorder, input ExecutionInput, question string) string { if m.memory == nil { return "" } records, degraded, err := m.memory.Recall(ctx, input.Identity, question, memory.DefaultRecall) if err != nil { rec.Error("memory.failed", err.Error()) return "" } if degraded != "" { rec.Error("memory.degraded", degraded) } if len(records) == 0 { return "" } rec.Error("runtime.memory_recalled", pluralMemories(len(records))+" carried into this run") return memory.Render(records) } func pluralMemories(n int) string { if n == 1 { return "1 memory" } return strconv.Itoa(n) + " memories" }