Files
noema/cmd/noemactl/memory.go
T
Andy Dixon 03631e5685 feat: add long-term memory systems with fused retrieval and memory explorer
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.
2026-06-20 23:47:50 +01:00

86 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"encoding/json"
"flag"
"fmt"
"net/url"
"os"
"strings"
)
func init() {
extraCommands = append(extraCommands, command{"memory", "search <mind> <query>|list <mind>|inspect <mind> <id>|add <mind> [-kind k] [-epistemic e] <text> – long-term memory", cmdMemory})
}
func cmdMemory(args []string) int {
if len(args) < 2 {
fmt.Fprintln(os.Stderr, "usage: noemactl memory search <mind> <query...> | list <mind> [-kind k] | inspect <mind> <id> | add <mind> [-kind semantic] [-epistemic fact] [-confidence 0.8] <text...>")
return exitUsage
}
mind := args[1]
switch args[0] {
case "search":
var out struct{ Items []map[string]any }
if code, err := apiRequest("GET", "/api/v1/minds/"+mind+"/memories?q="+url.QueryEscape(strings.Join(args[2:], " ")), nil, &out); err != nil {
return exitFor(code, err)
}
for _, h := range out.Items {
m, _ := h["memory"].(map[string]any)
fmt.Printf("%.2f [%s/%s] %s (%s)\n", num(h["score"]), m["kind"], m["epistemic"], m["content"], m["id"])
if comps, ok := h["components"].(map[string]any); ok {
var parts []string
for k, v := range comps {
parts = append(parts, fmt.Sprintf("%s=%.2f", k, num(v)))
}
fmt.Printf(" %s\n", strings.Join(parts, " "))
}
}
return exitOK
case "list":
fs := flag.NewFlagSet("memory list", flag.ContinueOnError)
kind := fs.String("kind", "", "kind filter")
n := fs.Int("n", 50, "count")
if fs.Parse(args[2:]) != nil {
return exitUsage
}
var out struct{ Items []map[string]any }
if code, err := apiRequest("GET", fmt.Sprintf("/api/v1/minds/%s/memories?kind=%s&limit=%d", mind, *kind, *n), nil, &out); err != nil {
return exitFor(code, err)
}
for _, m := range out.Items {
fmt.Printf("%s [%s/%s conf %.2f imp %.2f act %.2f] %s (%s)\n", fmt.Sprint(m["occurred_at"])[:16], m["kind"], m["epistemic"], num(m["confidence"]), num(m["importance"]), num(m["activation"]), m["content"], m["id"])
}
return exitOK
case "inspect":
if len(args) < 3 {
return exitUsage
}
var d map[string]any
if code, err := apiRequest("GET", "/api/v1/minds/"+mind+"/memories/"+args[2], nil, &d); err != nil {
return exitFor(code, err)
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.Encode(d) //nolint:errcheck
return exitOK
case "add":
fs := flag.NewFlagSet("memory add", flag.ContinueOnError)
kind := fs.String("kind", "semantic", "memory kind")
epi := fs.String("epistemic", "fact", "epistemic kind")
conf := fs.Float64("confidence", 0.8, "confidence")
if fs.Parse(args[2:]) != nil || fs.NArg() == 0 {
fmt.Fprintln(os.Stderr, "usage: noemactl memory add <mind> [-kind k] [-epistemic e] [-confidence c] <text...>")
return exitUsage
}
var m map[string]any
if code, err := apiRequest("POST", "/api/v1/minds/"+mind+"/memories", map[string]any{"kind": *kind, "epistemic": *epi, "confidence": *conf, "content": strings.Join(fs.Args(), " ")}, &m); err != nil {
return exitFor(code, err)
}
fmt.Printf("stored %v memory %v\n", m["kind"], m["id"])
return exitOK
}
fmt.Fprintln(os.Stderr, "unknown memory subcommand", args[0])
return exitUsage
}