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.
This commit is contained in:
Andy Dixon
2026-06-20 23:47:50 +01:00
parent e9c1b25c80
commit 03631e5685
30 changed files with 1957 additions and 25 deletions
+3
View File
@@ -0,0 +1,3 @@
DROP TABLE IF EXISTS memory_embeddings;
DROP TABLE IF EXISTS memory_relationships;
DROP TABLE IF EXISTS memories;
+66
View File
@@ -0,0 +1,66 @@
-- Long-term memory: one table, four kinds, explicit epistemic status, decay
-- without deletion, text search, relationships, and optional pgvector
-- embeddings (ADR-0003).
CREATE TABLE memories (
id uuid PRIMARY KEY,
mind_id uuid NOT NULL REFERENCES minds(id),
kind text NOT NULL CHECK (kind IN ('episodic','semantic','procedural','autobiographical')),
epistemic text NOT NULL,
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','retracted','superseded','archived')),
content text NOT NULL,
detail jsonb NOT NULL DEFAULT '{}'::jsonb,
source text NOT NULL DEFAULT '',
trust text NOT NULL DEFAULT 'low',
credibility double precision NOT NULL DEFAULT 0.5,
confidence double precision NOT NULL DEFAULT 0.5 CHECK (confidence >= 0 AND confidence <= 1),
importance double precision NOT NULL DEFAULT 0.5 CHECK (importance >= 0 AND importance <= 1),
salience double precision NOT NULL DEFAULT 0.5,
activation double precision NOT NULL DEFAULT 1,
valence double precision,
arousal double precision,
occurred_at timestamptz NOT NULL DEFAULT now(),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
retrieval_count integer NOT NULL DEFAULT 0,
last_retrieved_at timestamptz,
origin_event uuid,
supersedes uuid REFERENCES memories(id),
search tsvector GENERATED ALWAYS AS (to_tsvector('english', content)) STORED
);
CREATE INDEX memories_mind_kind_idx ON memories (mind_id, kind, occurred_at DESC);
CREATE INDEX memories_mind_status_idx ON memories (mind_id, status);
CREATE INDEX memories_search_idx ON memories USING gin (search);
CREATE INDEX memories_origin_idx ON memories (origin_event) WHERE origin_event IS NOT NULL;
CREATE TABLE memory_relationships (
id uuid PRIMARY KEY,
mind_id uuid NOT NULL REFERENCES minds(id),
from_memory uuid NOT NULL REFERENCES memories(id),
to_memory uuid NOT NULL REFERENCES memories(id),
relation text NOT NULL,
confidence double precision NOT NULL DEFAULT 0.5,
source text NOT NULL DEFAULT '',
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (from_memory, to_memory, relation)
);
CREATE INDEX memory_relationships_to_idx ON memory_relationships (to_memory);
-- Embeddings only when pgvector is present. The vector column is untyped so
-- several models with different dimensions can coexist; rows carry the model
-- name and dimension so representations are never mixed.
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM pg_available_extensions WHERE name = 'vector') THEN
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE memory_embeddings (
memory_id uuid NOT NULL REFERENCES memories(id),
model text NOT NULL,
dims integer NOT NULL,
embedding vector NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (memory_id, model)
);
CREATE INDEX memory_embeddings_model_idx ON memory_embeddings (model);
END IF;
END $$;