Phase 5 (ADR-0003). pkg/concepts fixes the epistemic vocabulary (observation, fact, memory, claim, hypothesis, belief, prediction, assumption, inference, opinion), memory kinds and relation names. internal/memory stores episodic, semantic, procedural and autobiographical memories in one mind-scoped table with trust-derived credibility, importance, salience and decaying activation. Retrieval fuses full-text rank (any-term by default), optional pgvector similarity, recency, activation, importance and credibility, and returns the component scores so every recall can be explained. Decay lowers activation and never deletes; retraction and supersession keep rows for provenance. Relationships are constrained to one mind at insert time. Embeddings live in a conditional pgvector table keyed by model so vector spaces are never mixed; without an embedder the system degrades to text and graph retrieval. Attention gates encoding: the attend handler turns admitted observations into episodic memories (operator changes become autobiographical ones) with memory_created events; the retrieve process proposes recalled memories as candidates and admitted recalls are reinforced with memory_retrieved events carrying their scoring components. Memory explorer UI (search with component breakdown, timeline, filters, operator knowledge entry), memory detail with provenance, relationships, citing events and an inaccurate-memory correction; API; noemactl memory commands; decay and embed-missing background jobs.
61 lines
2.2 KiB
Go
61 lines
2.2 KiB
Go
package cognition
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
|
|
"github.com/andydixon/noema/internal/attention"
|
|
"github.com/andydixon/noema/internal/brain"
|
|
"github.com/andydixon/noema/internal/memory"
|
|
"github.com/andydixon/noema/pkg/events"
|
|
"github.com/andydixon/noema/pkg/ids"
|
|
)
|
|
|
|
// Retrieve recalls memories relevant to each new external event and proposes
|
|
// them as candidates. Retrieval scoring is deterministic and recorded on the
|
|
// candidate so the UI can show why a memory came to mind.
|
|
type Retrieve struct{}
|
|
|
|
// Name implements brain.Process.
|
|
func (Retrieve) Name() string { return "retrieve" }
|
|
|
|
// minRetrievalScore filters weak matches before they compete for attention.
|
|
const minRetrievalScore = 0.30
|
|
|
|
// Observe implements brain.Process.
|
|
func (Retrieve) Observe(ctx context.Context, s *brain.State) ([]brain.Candidate, error) {
|
|
if s.Services.Memory == nil {
|
|
return nil, nil
|
|
}
|
|
var out []brain.Candidate
|
|
seen := map[ids.ID]bool{}
|
|
for _, ev := range s.NewEvents {
|
|
if strings.HasPrefix(ev.Source, "brain") || (ev.Type != events.Observation && ev.Type != events.Perception) {
|
|
continue
|
|
}
|
|
text := ev.Summary
|
|
var p events.ObservationPayload
|
|
if json.Unmarshal(ev.Payload, &p) == nil && p.Text != "" {
|
|
text = p.Text
|
|
}
|
|
hits, err := s.Services.Memory.Retrieve(ctx, s.Mind.ID, memory.Query{Text: text, Limit: 5, Now: s.Now, Semantic: true})
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
for _, h := range hits {
|
|
m := h.Memory
|
|
if h.Score < minRetrievalScore || m.OriginEvent == ev.ID || seen[m.ID] {
|
|
continue
|
|
}
|
|
seen[m.ID] = true
|
|
out = append(out, brain.Candidate{Kind: "retrieve", Subject: string(m.ID), Summary: "recalled: " + truncate(m.Content, 160),
|
|
Content: map[string]any{"ref": string(m.ID), "memory_kind": m.Kind, "score": h.Score, "components": h.Components, "query": truncate(text, 200), "source_event": string(ev.ID)},
|
|
Features: attention.Features{Salience: h.Score * 0.8, ContextRelevance: h.Score, Confidence: m.Confidence, Novelty: 0.2, Consequence: 0.3 + 0.4*m.Importance, Urgency: 0.1},
|
|
Confidence: m.Confidence, SourceEvents: []ids.ID{ev.ID},
|
|
Evidence: []events.EvidenceRef{{Kind: "memory", ID: string(m.ID), Note: "retrieved"}, {Kind: "event", ID: string(ev.ID), Note: "cue"}}})
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|