memory

package
v0.5.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package memory: automation.go implements the G7 automatic-operation features: project context injection (project_context), session capture (capture --summary) and intelligent decay (maintain). See PRD §9.1.

Package memory: llm.go wires the optional LLM client to memory features: auto topic extraction at remember time and project summarization (memory compression). All features degrade gracefully when no LLM is configured.

Package memory defines the core domain model and storage interfaces for forgetmenot: persistent, structured, semantically searchable memory.

Package memory: service.go implements the core memory operations used by the MCP layer: Remember (with dedupe), Recall (semantic search), Forget, Update and Stats. All policy lives here, not in the MCP handlers.

Package memory: session.go implements session lifecycle and cross-session topic correlation (PRD M4): Timeline, current-session state file, and topic assignment at remember time.

Index

Constants

View Source
const DefaultProject = "global"

DefaultProject is used when no explicit project namespace is given.

View Source
const MaxContentLen = 4000

MaxContentLen caps how much content a single memory can hold, both to keep memories focused and to bound context-injection surface.

View Source
const SessionStoreFile = "current_session.json"

SessionStoreFile is the default location for the current-session marker used by hooks (SessionStart writes it, Stop reads it via capture).

Variables

View Source
var ErrNotFound = errors.New("memory not found")

ErrNotFound is returned when a memory ID does not exist.

ValidTypes is the canonical set of memory types.

Functions

func Cosine

func Cosine(a, b []float64) float64

Cosine returns cosine similarity between two vectors. Zero-length vectors score 0. Vectors may be unnormalized.

func NewID

func NewID() string

NewID returns a random hex identifier (32 chars).

func Sanitize

func Sanitize(s string) string

Sanitize is the exported form of sanitizeContent, used by CLI write paths (e.g. bridge import) that bypass the Service.

func SessionStatePath

func SessionStatePath(dbPath string) string

SessionStatePath resolves the state file path under the same base as the DB directory (default XDG data dir) so hooks can find it.

Types

type Conflict

type Conflict struct {
	ID         string         `json:"id"`
	MemoryA    string         `json:"memory_a"`
	MemoryB    string         `json:"memory_b"`
	Status     ConflictStatus `json:"status"`
	Winner     string         `json:"winner,omitempty"`
	CreatedAt  time.Time      `json:"created_at"`
	ResolvedAt *time.Time     `json:"resolved_at,omitempty"`
}

Conflict records a contradiction between two memories. The winner is set when the conflict is resolved.

type ConflictStatus

type ConflictStatus string

ConflictStatus tracks a contradiction between two memories.

const (
	ConflictOpen     ConflictStatus = "open"
	ConflictResolved ConflictStatus = "resolved"
)

type CurrentSession

type CurrentSession struct {
	ID      string `json:"id"`
	Project string `json:"project"`
}

CurrentSession holds the marker written by `session start` and consumed by `capture` / `remember` so memories attach to the right session.

type Embedder

type Embedder interface {
	Embed(ctx context.Context, texts []string) ([][]float64, error)
}

Embedder turns text into embedding vectors. Implementations: OllamaEmbedder (local) and OpenAICompatEmbedder (remote fallback) in internal/embed.

type Memory

type Memory struct {
	ID             string            `json:"id"`
	Type           Type              `json:"type"`
	Content        string            `json:"content"`
	Project        string            `json:"project"`
	Importance     float64           `json:"importance"`
	AccessCount    int               `json:"access_count"`
	LastAccessedAt time.Time         `json:"last_accessed_at"`
	CreatedAt      time.Time         `json:"created_at"`
	UpdatedAt      time.Time         `json:"updated_at"`
	Source         string            `json:"source"`
	Trust          Trust             `json:"trust"`
	SessionID      string            `json:"session_id,omitempty"`
	Metadata       map[string]string `json:"metadata"`
}

Memory is a single stored memory entry. Embeddings are kept out of the struct on purpose: they live on the storage side and are managed by the core service, not exposed to tool handlers.

type RecallInput

type RecallInput struct {
	Query   string
	Project string // "" = search everything
	Type    Type   // "" = any type
	Limit   int
}

RecallInput is the validated request for semantic search.

type Relation

type Relation struct {
	ID        string       `json:"id"`
	FromID    string       `json:"from_id"`
	ToID      string       `json:"to_id"`
	Kind      RelationKind `json:"kind"`
	CreatedAt time.Time    `json:"created_at"`
}

Relation links two memories.

type RelationKind

type RelationKind string

RelationKind describes how two memories relate. See PRD §6.3.

const (
	// RelationRelated is a loose semantic link (same topic, complement).
	RelationRelated RelationKind = "related"
	// RelationSupersedes means FromID is replaced by ToID. When recalling,
	// superseded memories are not suggested by default.
	RelationSupersedes RelationKind = "supersedes"
	// RelationPartOf means FromID is a component of ToID (entity -> entity).
	RelationPartOf RelationKind = "part_of"
)

type RememberInput

type RememberInput struct {
	Content    string
	Type       Type
	Project    string
	Importance float64
	Source     string
	Trust      Trust  // defaults to TrustHigh
	SessionID  string // optional; falls back to the current-session marker
	Topics     []string
	AutoTopics bool // extract topics with the LLM (requires s.LLM)
	Metadata   map[string]string
}

RememberInput is the validated request for storing a memory.

type SQLiteStore

type SQLiteStore struct {
	// contains filtered or unexported fields
}

SQLiteStore is the default persistent Store, backed by a single SQLite file.

func NewSQLiteStore

func NewSQLiteStore(path string) (*SQLiteStore, error)

NewSQLiteStore opens (or creates) the database at path and runs migrations.

func (*SQLiteStore) AddRelation

func (s *SQLiteStore) AddRelation(ctx context.Context, r *Relation) error

func (*SQLiteStore) AddTopic

func (s *SQLiteStore) AddTopic(ctx context.Context, t *Topic) error

func (*SQLiteStore) All

func (s *SQLiteStore) All(ctx context.Context, project string) ([]*Memory, [][]float64, error)

func (*SQLiteStore) AllMeta added in v0.5.3

func (s *SQLiteStore) AllMeta(ctx context.Context, project string) ([]*Memory, error)

AllMeta returns every memory in a project ("" = all) WITHOUT decoding the embedding BLOBs. Use it for read paths that only need memory fields (project_context, list, export-md, decay, timeline...): skipping the vector decode saves tens of MB and keeps the SessionStart hook fast at scale.

func (*SQLiteStore) AssignTopic

func (s *SQLiteStore) AssignTopic(ctx context.Context, memoryID, topicID string) error

func (*SQLiteStore) Close

func (s *SQLiteStore) Close() error

Close releases the underlying database.

func (*SQLiteStore) Count

func (s *SQLiteStore) Count(ctx context.Context, project string) (int, error)

func (*SQLiteStore) CountProjects

func (s *SQLiteStore) CountProjects(ctx context.Context) (int, error)

func (*SQLiteStore) CreateConflict

func (s *SQLiteStore) CreateConflict(ctx context.Context, a, b string) (string, error)

func (*SQLiteStore) CreateSession

func (s *SQLiteStore) CreateSession(ctx context.Context, sess *Session) error

func (*SQLiteStore) Delete

func (s *SQLiteStore) Delete(ctx context.Context, id string) error

func (*SQLiteStore) EndSession

func (s *SQLiteStore) EndSession(ctx context.Context, id string) error

func (*SQLiteStore) EndSessionWithSummary added in v0.3.0

func (s *SQLiteStore) EndSessionWithSummary(ctx context.Context, id, summary string) error

func (*SQLiteStore) Get

func (s *SQLiteStore) Get(ctx context.Context, id string) (*Memory, []float64, error)

func (*SQLiteStore) GetSession

func (s *SQLiteStore) GetSession(ctx context.Context, id string) (*Session, error)

func (*SQLiteStore) Insert

func (s *SQLiteStore) Insert(ctx context.Context, m *Memory, embedding []float64) error

func (*SQLiteStore) MemoriesByTopic

func (s *SQLiteStore) MemoriesByTopic(ctx context.Context, topicName, project string) ([]*Memory, error)

func (*SQLiteStore) OpenConflicts

func (s *SQLiteStore) OpenConflicts(ctx context.Context) ([]Conflict, error)

func (*SQLiteStore) RelationsFrom

func (s *SQLiteStore) RelationsFrom(ctx context.Context, memoryID string) ([]Relation, error)

func (*SQLiteStore) ResolveConflict

func (s *SQLiteStore) ResolveConflict(ctx context.Context, id, winner string) error

func (*SQLiteStore) SessionsForProject

func (s *SQLiteStore) SessionsForProject(ctx context.Context, project string) ([]Session, error)

func (*SQLiteStore) SetEmbedding added in v0.4.0

func (s *SQLiteStore) SetEmbedding(ctx context.Context, id string, vec []float64, mode string) error

SetEmbedding replaces a memory's vector and records embedding provenance in its metadata. Used by recall to heal vectors written by a different provider (an Ollama outage or a config switch) so they stay searchable.

func (*SQLiteStore) SupersededIDs

func (s *SQLiteStore) SupersededIDs(ctx context.Context) ([]string, error)

func (*SQLiteStore) TopicsForMemories added in v0.3.0

func (s *SQLiteStore) TopicsForMemories(ctx context.Context, memoryIDs []string) (map[string][]Topic, error)

func (*SQLiteStore) TopicsForMemory

func (s *SQLiteStore) TopicsForMemory(ctx context.Context, memoryID string) ([]Topic, error)

func (*SQLiteStore) Update

func (s *SQLiteStore) Update(ctx context.Context, id string, patch UpdatePatch) error

type SearchResult

type SearchResult struct {
	Memory *Memory
	Score  float64
	Topics []Topic
}

SearchResult pairs a memory with its similarity score.

type Service

type Service struct {
	Store    Store
	Embedder Embedder
	LLM      llm.Client // optional; nil disables auto-topics/summarize

	// DedupeThreshold: two memories with cosine similarity >= this value in
	// the same project are considered duplicates; the new write reinforces
	// the existing entry instead of inserting a copy. PRD §7.3.
	DedupeThreshold float64
	// MinRecallScore: recall results below this similarity are dropped.
	MinRecallScore float64
	// ConflictThreshold: a new memory whose similarity to an existing one of
	// the same type/project falls in [ConflictThreshold, DedupeThreshold) is
	// treated as a possible contradiction and opens a conflict. PRD §7.3.
	ConflictThreshold float64
	// contains filtered or unexported fields
}

Service is the core memory engine. It owns the store and the embedder and exposes the operations the MCP tools call.

func NewService

func NewService(store Store, emb Embedder) *Service

NewService returns a Service with sensible defaults.

func (*Service) AssignTopics

func (s *Service) AssignTopics(ctx context.Context, memoryID, project string, topics []string) error

AssignTopics attaches topic labels to a memory. Topics come from the remember input; lowercased and trimmed.

func (*Service) AutoTopics added in v0.3.0

func (s *Service) AutoTopics(ctx context.Context, content, project string) ([]string, error)

AutoTopics extracts topic labels for a memory using the configured LLM. Returns nil (no error) when no LLM is configured, so callers treat it as "no topics auto-detected". PRD v0.3.

func (*Service) CaptureSummary

func (s *Service) CaptureSummary(ctx context.Context, project, summary, source string) (string, error)

CaptureSummary stores a session summary as an episode memory. This is what the Stop/SessionEnd hook calls so that "what happened" is persisted without the user doing anything. If no embedder is configured (hooks run without Ollama), the summary is stored with an empty vector: it stays visible in project_context and list, just not in semantic recall. PRD §9.1.

func (*Service) Conflicts

func (s *Service) Conflicts(ctx context.Context) ([]Conflict, error)

Conflicts lists all open conflicts.

func (*Service) CurrentSessionID

func (s *Service) CurrentSessionID() string

CurrentSessionID returns the active session id, if any.

func (*Service) Decay

func (s *Service) Decay(ctx context.Context, olderThan time.Duration, minImportance float64) (int, error)

Decay lowers the importance of stale, rarely-accessed episode/context memories so they stop dominating recall. It is safe to run periodically (maintain). Returns how many memories were touched. PRD §9.1, §10.2.

func (*Service) EndSession

func (s *Service) EndSession(ctx context.Context, id, summary string) error

EndSession ends the current session (by marker) or the given id. An optional summary (e.g. the session capture) is stored on the session.

func (*Service) Forget

func (s *Service) Forget(ctx context.Context, id string) error

Forget deletes a memory by ID.

func (s *Service) Link(ctx context.Context, fromID, toID, kind string) error

Link creates a relation between two memories. If a matching relation already exists, it is a no-op.

func (*Service) ProjectContext

func (s *Service) ProjectContext(ctx context.Context, project string, limit, budget int) (string, []Memory, error)

ProjectContext returns a concise, ready-to-inject summary of what we know about a project: the most relevant memories ranked by importance, recency and access. Used by the SessionStart hook so an agent starts a session already knowing the project. budget <= 0 means unlimited. PRD §9.1, M3.

func (*Service) Recall

func (s *Service) Recall(ctx context.Context, in RecallInput) ([]SearchResult, error)

Recall finds the top-K memories most similar to query, filtered by project and type, sorted by score descending. Brute-force for M0 (fine up to tens of thousands of memories); the vector index arrives in M1.

func (*Service) Relations

func (s *Service) Relations(ctx context.Context, memoryID string) ([]Relation, error)

Relations returns all relations originating from a memory.

func (*Service) Remember

func (s *Service) Remember(ctx context.Context, in RememberInput) (string, bool, error)

Remember stores a memory. If a near-duplicate already exists in the same project, the existing entry's access is bumped instead (reinforcement). Returns the ID that ended up holding the memory and whether it was a new insert or a reinforcement of an existing entry.

func (*Service) ResolveConflict

func (s *Service) ResolveConflict(ctx context.Context, conflictID, winnerID string) error

ResolveConflict marks a conflict resolved with the given winner, and records the losing memory as superseded by the winner.

func (*Service) SetDBPath

func (s *Service) SetDBPath(path string)

SetDBPath records the database path so the session marker is written next to it. Call from main after parsing flags.

func (*Service) StartSession

func (s *Service) StartSession(ctx context.Context, project string) (*Session, error)

StartSession creates a session, persists the current-session marker, and returns the session.

func (*Service) Stats

func (s *Service) Stats(ctx context.Context) (Stats, error)

func (*Service) SummarizeProject added in v0.3.0

func (s *Service) SummarizeProject(ctx context.Context, project string, olderThan time.Duration) (string, error)

SummarizeProject compresses old episode memories into a single context summary using the LLM. Episodes older than olderThan that have low recent access are the candidates. The summary is stored as a `context` memory tagged with the project's topics. Returns the summary text.

func (*Service) Timeline

func (s *Service) Timeline(ctx context.Context, project, topic string, limit int) ([]TimelineEntry, error)

Timeline returns the memories about a topic (or all project memories when topic is empty) across sessions, oldest first, with session context. The topic is normalized the same way AssignTopics stores it (lowercased, trimmed), so "Auth" and " auth " find the "auth" timeline.

func (*Service) Update

func (s *Service) Update(ctx context.Context, id string, patch UpdatePatch) error

Update applies a patch to a memory by ID.

type Session

type Session struct {
	ID        string     `json:"id"`
	Project   string     `json:"project"`
	StartedAt time.Time  `json:"started_at"`
	EndedAt   *time.Time `json:"ended_at,omitempty"`
	Summary   string     `json:"summary,omitempty"`
}

Session groups memories captured during one agent session, enabling cross-session topic correlation. PRD M4.

type Stats

type Stats struct {
	Count        int `json:"count"`
	ProjectCount int `json:"project_count"`
}

Stats returns simple health metrics for the memory.

type Store

type Store interface {
	Insert(ctx context.Context, m *Memory, embedding []float64) error
	Update(ctx context.Context, id string, patch UpdatePatch) error
	Delete(ctx context.Context, id string) error
	Get(ctx context.Context, id string) (*Memory, []float64, error)
	// All returns every memory in a project ("" = all projects) together
	// with its embedding. Used for brute-force semantic search in M0.
	All(ctx context.Context, project string) ([]*Memory, [][]float64, error)
	// AllMeta returns every memory in a project WITHOUT decoding embeddings.
	// Use for metadata-only read paths (context, list, timeline, decay...).
	AllMeta(ctx context.Context, project string) ([]*Memory, error)
	// SetEmbedding replaces a memory's vector and records which embedder
	// produced it (mode: "semantic" or "lexical"). Recall uses it to heal
	// vectors written by a different provider instead of silently missing
	// them.
	SetEmbedding(ctx context.Context, id string, vec []float64, mode string) error
	Count(ctx context.Context, project string) (int, error)
	CountProjects(ctx context.Context) (int, error)

	// Relations.
	AddRelation(ctx context.Context, r *Relation) error
	RelationsFrom(ctx context.Context, memoryID string) ([]Relation, error)
	// SupersededIDs returns ALL memory IDs that are superseded by something.
	// Single query; avoids N+1 lookups in recall/context paths.
	SupersededIDs(ctx context.Context) ([]string, error)

	// Conflicts.
	// CreateConflict records a contradiction. If an open conflict between the
	// two memories already exists, it returns the existing conflict's ID.
	CreateConflict(ctx context.Context, a, b string) (string, error)
	OpenConflicts(ctx context.Context) ([]Conflict, error)
	ResolveConflict(ctx context.Context, id, winner string) error

	// Sessions (PRD M4): group memories per agent session for cross-session
	// topic correlation.
	CreateSession(ctx context.Context, s *Session) error
	EndSession(ctx context.Context, id string) error
	EndSessionWithSummary(ctx context.Context, id, summary string) error
	GetSession(ctx context.Context, id string) (*Session, error)
	SessionsForProject(ctx context.Context, project string) ([]Session, error)

	// Topics (PRD M4): subject labels for cross-session correlation.
	AddTopic(ctx context.Context, t *Topic) error
	AssignTopic(ctx context.Context, memoryID, topicID string) error
	TopicsForMemory(ctx context.Context, memoryID string) ([]Topic, error)
	MemoriesByTopic(ctx context.Context, topicName, project string) ([]*Memory, error)
	// TopicsForMemories returns topic labels keyed by memory id, in one query.
	// Avoids N+1 lookups in list/timeline paths.
	TopicsForMemories(ctx context.Context, memoryIDs []string) (map[string][]Topic, error)

	Close() error
}

Store is the persistence contract. The SQLite implementation lives in store.go; tests and future backends implement this interface.

type TimelineEntry

type TimelineEntry struct {
	Memory  *Memory
	Session *Session // nil if the memory is not linked to a session
}

TimelineEntry is one step in a topic's evolution across sessions.

type Topic

type Topic struct {
	ID      string `json:"id"`
	Name    string `json:"name"`
	Project string `json:"project"`
}

Topic is a subject label attached to memories, used to correlate content across sessions. PRD M4.

type Trust

type Trust string

Trust marks how much a memory should be trusted when its content is fed to an LLM. PRD M3: memories can be a prompt-injection vector, so low-trust content must be visibly flagged in recall and injection points.

const (
	TrustHigh Trust = "high" // explicit user/agent input; safe to treat as instructions-adjacent
	TrustLow  Trust = "low"  // auto-captured or external content; treat as data, not instructions
)

type Type

type Type string

Type identifies the kind of a memory entry. See PRD §6.1.

const (
	TypeFact       Type = "fact"
	TypePreference Type = "preference"
	TypeDecision   Type = "decision"
	TypeEntity     Type = "entity"
	TypeContext    Type = "context"
	TypeEpisode    Type = "episode"
)

type UpdatePatch

type UpdatePatch struct {
	Content    *string
	Type       *Type
	Project    *string
	Importance *float64
	Trust      *Trust
	SessionID  *string
	Metadata   map[string]string // merged into existing metadata
	// BumpAccess signals recall: access_count+1 and last_accessed_at=now.
	BumpAccess bool
}

UpdatePatch describes the fields to change in an existing memory.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL