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
- Variables
- func Cosine(a, b []float64) float64
- func NewID() string
- func Sanitize(s string) string
- func SessionStatePath(dbPath string) string
- type Conflict
- type ConflictStatus
- type CurrentSession
- type Embedder
- type Memory
- type RecallInput
- type Relation
- type RelationKind
- type RememberInput
- type SQLiteStore
- func (s *SQLiteStore) AddRelation(ctx context.Context, r *Relation) error
- func (s *SQLiteStore) AddTopic(ctx context.Context, t *Topic) error
- func (s *SQLiteStore) All(ctx context.Context, project string) ([]*Memory, [][]float64, error)
- func (s *SQLiteStore) AllMeta(ctx context.Context, project string) ([]*Memory, error)
- func (s *SQLiteStore) AssignTopic(ctx context.Context, memoryID, topicID string) error
- func (s *SQLiteStore) Close() error
- func (s *SQLiteStore) Count(ctx context.Context, project string) (int, error)
- func (s *SQLiteStore) CountProjects(ctx context.Context) (int, error)
- func (s *SQLiteStore) CreateConflict(ctx context.Context, a, b string) (string, error)
- func (s *SQLiteStore) CreateSession(ctx context.Context, sess *Session) error
- func (s *SQLiteStore) Delete(ctx context.Context, id string) error
- func (s *SQLiteStore) EndSession(ctx context.Context, id string) error
- func (s *SQLiteStore) EndSessionWithSummary(ctx context.Context, id, summary string) error
- func (s *SQLiteStore) Get(ctx context.Context, id string) (*Memory, []float64, error)
- func (s *SQLiteStore) GetSession(ctx context.Context, id string) (*Session, error)
- func (s *SQLiteStore) Insert(ctx context.Context, m *Memory, embedding []float64) error
- func (s *SQLiteStore) MemoriesByTopic(ctx context.Context, topicName, project string) ([]*Memory, error)
- func (s *SQLiteStore) OpenConflicts(ctx context.Context) ([]Conflict, error)
- func (s *SQLiteStore) RelationsFrom(ctx context.Context, memoryID string) ([]Relation, error)
- func (s *SQLiteStore) ResolveConflict(ctx context.Context, id, winner string) error
- func (s *SQLiteStore) SessionsForProject(ctx context.Context, project string) ([]Session, error)
- func (s *SQLiteStore) SetEmbedding(ctx context.Context, id string, vec []float64, mode string) error
- func (s *SQLiteStore) SupersededIDs(ctx context.Context) ([]string, error)
- func (s *SQLiteStore) TopicsForMemories(ctx context.Context, memoryIDs []string) (map[string][]Topic, error)
- func (s *SQLiteStore) TopicsForMemory(ctx context.Context, memoryID string) ([]Topic, error)
- func (s *SQLiteStore) Update(ctx context.Context, id string, patch UpdatePatch) error
- type SearchResult
- type Service
- func (s *Service) AssignTopics(ctx context.Context, memoryID, project string, topics []string) error
- func (s *Service) AutoTopics(ctx context.Context, content, project string) ([]string, error)
- func (s *Service) CaptureSummary(ctx context.Context, project, summary, source string) (string, error)
- func (s *Service) Conflicts(ctx context.Context) ([]Conflict, error)
- func (s *Service) CurrentSessionID() string
- func (s *Service) Decay(ctx context.Context, olderThan time.Duration, minImportance float64) (int, error)
- func (s *Service) EndSession(ctx context.Context, id, summary string) error
- func (s *Service) Forget(ctx context.Context, id string) error
- func (s *Service) Link(ctx context.Context, fromID, toID, kind string) error
- func (s *Service) ProjectContext(ctx context.Context, project string, limit, budget int) (string, []Memory, error)
- func (s *Service) Recall(ctx context.Context, in RecallInput) ([]SearchResult, error)
- func (s *Service) Relations(ctx context.Context, memoryID string) ([]Relation, error)
- func (s *Service) Remember(ctx context.Context, in RememberInput) (string, bool, error)
- func (s *Service) ResolveConflict(ctx context.Context, conflictID, winnerID string) error
- func (s *Service) SetDBPath(path string)
- func (s *Service) StartSession(ctx context.Context, project string) (*Session, error)
- func (s *Service) Stats(ctx context.Context) (Stats, error)
- func (s *Service) SummarizeProject(ctx context.Context, project string, olderThan time.Duration) (string, error)
- func (s *Service) Timeline(ctx context.Context, project, topic string, limit int) ([]TimelineEntry, error)
- func (s *Service) Update(ctx context.Context, id string, patch UpdatePatch) error
- type Session
- type Stats
- type Store
- type TimelineEntry
- type Topic
- type Trust
- type Type
- type UpdatePatch
Constants ¶
const DefaultProject = "global"
DefaultProject is used when no explicit project namespace is given.
const MaxContentLen = 4000
MaxContentLen caps how much content a single memory can hold, both to keep memories focused and to bound context-injection surface.
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 ¶
var ErrNotFound = errors.New("memory not found")
ErrNotFound is returned when a memory ID does not exist.
var ValidTypes = map[Type]bool{ TypeFact: true, TypePreference: true, TypeDecision: true, TypeEntity: true, TypeContext: true, TypeEpisode: true, }
ValidTypes is the canonical set of memory types.
Functions ¶
func Cosine ¶
Cosine returns cosine similarity between two vectors. Zero-length vectors score 0. Vectors may be unnormalized.
func Sanitize ¶
Sanitize is the exported form of sanitizeContent, used by CLI write paths (e.g. bridge import) that bypass the Service.
func SessionStatePath ¶
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 ¶
CurrentSession holds the marker written by `session start` and consumed by `capture` / `remember` so memories attach to the right session.
type Embedder ¶
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) AllMeta ¶ added in v0.5.3
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) CountProjects ¶
func (s *SQLiteStore) CountProjects(ctx context.Context) (int, error)
func (*SQLiteStore) CreateConflict ¶
func (*SQLiteStore) CreateSession ¶
func (s *SQLiteStore) CreateSession(ctx context.Context, sess *Session) 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) GetSession ¶
func (*SQLiteStore) MemoriesByTopic ¶
func (*SQLiteStore) OpenConflicts ¶
func (s *SQLiteStore) OpenConflicts(ctx context.Context) ([]Conflict, error)
func (*SQLiteStore) RelationsFrom ¶
func (*SQLiteStore) ResolveConflict ¶
func (s *SQLiteStore) ResolveConflict(ctx context.Context, id, winner string) error
func (*SQLiteStore) SessionsForProject ¶
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 (*SQLiteStore) TopicsForMemory ¶
func (*SQLiteStore) Update ¶
func (s *SQLiteStore) Update(ctx context.Context, id string, patch UpdatePatch) error
type SearchResult ¶
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 ¶
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
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) CurrentSessionID ¶
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 ¶
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) Link ¶
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) Remember ¶
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 ¶
ResolveConflict marks a conflict resolved with the given winner, and records the losing memory as superseded by the winner.
func (*Service) SetDBPath ¶
SetDBPath records the database path so the session marker is written next to it. Call from main after parsing flags.
func (*Service) StartSession ¶
StartSession creates a session, persists the current-session marker, and returns the session.
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.
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 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.
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.