memory

package
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Apr 15, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package memory provides conversation memory management with lossless short-term memory (DAG-based summarization) and long-term memory (automatic extraction, deduplication, context injection).

Index

Constants

View Source
const DefaultDeduplicationPrompt = `` /* 683-byte string literal not displayed */

DefaultDeduplicationPrompt is the built-in batch deduplication prompt.

Variables

This section is empty.

Functions

func AllCategoryStrings

func AllCategoryStrings() []string

AllCategoryStrings returns all category names as strings.

func BuildSummaryIndex

func BuildSummaryIndex(ctx context.Context, store SummaryStore, convID string, budget int) string

BuildSummaryIndex generates a summary index string from the top-level summaries of a conversation. The result is intended to be injected into the LLM system prompt via the workflow.VarSummaryIndex board variable.

Returns an empty string when no summaries exist or the store is nil. The budget parameter controls the maximum character length of the output; older summaries are omitted (with a note) when the budget is exceeded.

func CategoryHalfLife

func CategoryHalfLife(cat MemoryCategory) float64

CategoryHalfLife returns the BM25 time-decay half-life (in days) for a memory category. Unknown categories default to 90 days.

func ConversationIDFrom

func ConversationIDFrom(ctx context.Context) string

ConversationIDFrom retrieves the conversation ID from the context.

func DefaultExtractPrompt

func DefaultExtractPrompt() string

DefaultExtractPrompt returns the built-in extraction prompt, dynamically including all registered categories.

func EntryMatchesQueryScope

func EntryMatchesQueryScope(e *MemoryEntry, query *MemoryScope) bool

EntryMatchesQueryScope reports whether e belongs to the bucket described by query. query nil matches all entries (legacy stores / uncoped queries).

func EntryMatchesRecallScope

func EntryMatchesRecallScope(e *MemoryEntry, r *RecallScope) bool

EntryMatchesRecallScope reports whether e belongs to any partition in r. nil r matches all entries (legacy).

func LoadArchivedMessages

func LoadArchivedMessages(ctx context.Context, ws workspace.Workspace, prefix, convID string, startSeq, endSeq int) ([]model.Message, error)

LoadArchivedMessages reads messages from gzip archive segments.

func NewSummaryNodeID

func NewSummaryNodeID() string

NewSummaryNodeID generates a unique ID for a summary node.

func RecoverArchive

func RecoverArchive(ctx context.Context, ws workspace.Workspace, store Store, prefix, convID string) error

RecoverArchive checks for incomplete archive operations and completes them. Call this at startup before any new archive operations.

func RegisterCategory

func RegisterCategory(cat MemoryCategory)

RegisterCategory adds a custom memory category to the global registry. Must be called before pipeline usage (typically in init or startup).

func RegisterCategoryDescription

func RegisterCategoryDescription(cat MemoryCategory, desc string)

RegisterCategoryDescription associates a description with a custom category for use in the default extraction prompt.

func RegisterTools

func RegisterTools(registry *tool.Registry, deps ToolDeps)

RegisterTools registers memory_expand and memory_compact. Summary index is now auto-injected into the LLM system prompt via workflow.VarSummaryIndex board variable, so memory_search is no longer needed.

func SaveManifest

func SaveManifest(ctx context.Context, ws workspace.Workspace, prefix, convID string, m *ArchiveManifest) error

SaveManifest writes the archive manifest.

func TimeDecay

func TimeDecay(updatedAt time.Time, category MemoryCategory, now time.Time) float64

TimeDecay returns an exponential decay factor based on how old the entry is. The half-life varies by category: stable facts (profile) decay very slowly, while transient facts (events) decay quickly.

func WithConversationID

func WithConversationID(ctx context.Context, id string) context.Context

WithConversationID injects the conversation ID into the context.

Types

type ArchiveConfig

type ArchiveConfig struct {
	ArchiveThreshold int
	ArchiveBatchSize int
	ArchivePrefix    string
}

ArchiveConfig controls message archiving behavior.

type ArchiveManifest

type ArchiveManifest struct {
	Segments    []ArchiveSegment `json:"segments"`
	HotStartSeq int              `json:"hot_start_seq"`
}

ArchiveManifest tracks archived message segments.

func LoadManifest

func LoadManifest(ctx context.Context, ws workspace.Workspace, prefix, convID string) (*ArchiveManifest, error)

LoadManifest reads the archive manifest for a conversation.

type ArchiveResult

type ArchiveResult struct {
	MessagesArchived int    `json:"messages_archived"`
	ArchiveFile      string `json:"archive_file,omitempty"`
	HotStartSeq      int    `json:"hot_start_seq"`
}

ArchiveResult holds the result of an archive operation.

func Archive

func Archive(ctx context.Context, ws workspace.Workspace, store Store, prefix, convID string, cfg ArchiveConfig) (ArchiveResult, error)

Archive moves old messages to gzip-compressed archive files. Uses an intent file for crash recovery (see RecoverArchive).

type ArchiveSegment

type ArchiveSegment struct {
	File      string    `json:"file"`
	StartSeq  int       `json:"start_seq"`
	EndSeq    int       `json:"end_seq"`
	Count     int       `json:"count"`
	CreatedAt time.Time `json:"created_at"`
}

ArchiveSegment describes a single archived file.

type AssemblerConfig

type AssemblerConfig struct {
	MaxEntries       int
	PinnedCategories []MemoryCategory
	RecallCategories []MemoryCategory
	// RecallPartitions maps category → partition union for recall Search/List.
	// nil or absent keys default to user-only.
	RecallPartitions map[MemoryCategory][]MemoryPartition
	PinnedCacheTTL   time.Duration
	RecallCacheTTL   time.Duration
	ScopeEnabled     bool
	GlobalCategories []MemoryCategory
}

AssemblerConfig controls pinned vs recall behavior and cache TTLs.

type BufferMemory

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

BufferMemory keeps the last N messages.

func NewBufferMemory

func NewBufferMemory(store Store, maxMessages int) *BufferMemory

NewBufferMemory creates a buffer memory with a maximum message count.

func (*BufferMemory) Clear

func (m *BufferMemory) Clear(ctx context.Context, conversationID string) error

func (*BufferMemory) Load

func (m *BufferMemory) Load(ctx context.Context, conversationID string) ([]model.Message, error)

func (*BufferMemory) Save

func (m *BufferMemory) Save(ctx context.Context, conversationID string, messages []model.Message) error

type CandidateMemory

type CandidateMemory struct {
	Category  MemoryCategory `json:"category"`
	Content   string         `json:"content"`
	Keywords  []string       `json:"keywords"`
	Timestamp string         `json:"timestamp,omitempty"`
}

CandidateMemory represents a memory entry extracted by the LLM before deduplication.

type CompactConfig

type CompactConfig struct {
	CompactThreshold int
	PruneLeafContent bool
	RequireParent    bool
}

CompactConfig controls the compact behavior.

type CompactResult

type CompactResult struct {
	DeletedRemoved int `json:"deleted_removed"`
	LeafPruned     int `json:"leaf_pruned"`
	TotalRemaining int `json:"total_remaining"`
}

CompactResult holds the result of a compact operation.

type Config

type Config struct {
	Type        string         `json:"type,omitempty"` // deprecated: ignored, always lossless
	MaxMessages int            `json:"max_messages,omitempty"`
	LongTerm    LongTermConfig `json:"long_term,omitempty"`
	Lossless    LosslessConfig `json:"lossless,omitempty"`
}

Config configures the conversation memory. All memory is lossless by default; the Type field is deprecated and ignored (kept for backward compatibility).

type ConflictType

type ConflictType string

ConflictType classifies a contradiction between new and existing memory.

const (
	ConflictNone              ConflictType = ""
	ConflictFactualCorrection ConflictType = "factual_correction"
	ConflictPreferenceChange  ConflictType = "preference_change"
	ConflictContextDependent  ConflictType = "context_dependent"
)

type ContextAssembler

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

ContextAssembler loads and formats long-term memory for system prompt injection.

func NewContextAssembler

func NewContextAssembler(ltStore LongTermStore, config AssemblerConfig) *ContextAssembler

NewContextAssembler builds an assembler. Zero TTLs default to aware.go constants.

func (*ContextAssembler) Assemble

func (a *ContextAssembler) Assemble(ctx context.Context, runtimeID string, scope *MemoryScope, msgs []model.Message) (string, error)

Assemble returns formatted long-term memory text, or empty string if none.

type DAGConfig

type DAGConfig struct {
	ChunkSize         int
	CondenseThreshold int
	CondenseGroupSize int
	MaxDepth          int
	TokenBudget       int
	RecentRatio       float64
	MidRatio          float64
	Compact           CompactConfig
	Archive           ArchiveConfig
}

DAGConfig controls the summary DAG behavior.

func DefaultDAGConfig

func DefaultDAGConfig() DAGConfig

DefaultDAGConfig returns a DAGConfig with sensible defaults.

type DeduplicationAction

type DeduplicationAction string

DeduplicationAction defines how a candidate memory should be handled.

const (
	ActionSkip   DeduplicationAction = "skip"
	ActionCreate DeduplicationAction = "create"
	ActionMerge  DeduplicationAction = "merge"
	ActionDelete DeduplicationAction = "delete"
)

type EstimateCounter

type EstimateCounter struct{}

EstimateCounter uses a heuristic: ~4 ASCII chars/token, ~1.5 CJK chars/token.

func (*EstimateCounter) Count

func (c *EstimateCounter) Count(text string) int

func (*EstimateCounter) CountMessages

func (c *EstimateCounter) CountMessages(msgs []model.Message) int

type ExtractInput

type ExtractInput struct {
	RuntimeID       string
	Messages        []llm.Message // Messages to extract from (typically incremental).
	ContextMessages []llm.Message // Optional broader context window for the LLM.
	Source          MemorySource
	Scope           MemoryScope

	// StripSessionIDFromSearch, when true, clears SessionID on the scope used in
	// the search/dedup stage so existing memories match across conversation threads.
	// TimelineSessionID (or Source.ConversationID when it differs from Scope.UserID)
	// is still persisted on new entries unless the category is global.
	StripSessionIDFromSearch bool

	// TimelineSessionID is written to MemoryEntry.Scope.SessionID for non-global
	// categories (which chat/thread produced this memory). It does not affect the
	// search stage when StripSessionIDFromSearch is true.
	TimelineSessionID string

	// ScopeInExtract, when non-nil, overrides LongTermConfig.ScopeEnabled for
	// the pipeline search stage (shared extractor vs per-agent settings).
	ScopeInExtract *bool

	// GlobalCategories, when non-empty, overrides LongTermConfig.GlobalCategories
	// for scope normalization in this run (shared extractor vs per-agent settings).
	GlobalCategories []MemoryCategory

	// LongTermCategories, when non-empty, overrides LongTermConfig.Categories for
	// the search/persist pipeline (shared extractor vs per-agent settings).
	LongTermCategories []string
}

ExtractInput is the input to MemoryExtractor.Extract.

type ExtractorConfig

type ExtractorConfig struct {
	// ExtractPrompt is the prompt template for extraction.
	// Must contain one %s placeholder for the conversation text.
	ExtractPrompt string

	// DeduplicationPrompt is the prompt template for batch deduplication.
	// Must contain one %s placeholder for the comparison text.
	DeduplicationPrompt string

	// FormatMessages converts messages into the conversation text passed to the LLM.
	// When nil, the default "role: content\n" format is used.
	FormatMessages func(messages []llm.Message, source MemorySource) string

	// PostProcess is called after LLM extraction and before deduplication.
	// It can add timestamps, transform categories, filter entries, etc.
	PostProcess func(candidates []CandidateMemory, source MemorySource) []CandidateMemory

	// MaxConversationRunes limits the conversation text length (in runes) sent to the LLM.
	// Defaults to 8000 when zero.
	MaxConversationRunes int
}

ExtractorConfig customizes the extraction behavior of MemoryExtractor. Zero values fall back to built-in defaults.

type FileLongTermStore

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

FileLongTermStore persists long-term memory as JSONL files, organized by memory/long_term/{runtimeID}/{category}.jsonl. Uses textsearch.CorpusStats for BM25 scoring. Thread-safe via sync.RWMutex.

func NewFileLongTermStore

func NewFileLongTermStore(ws workspace.Workspace, prefix string, opts ...LTStoreOption) *FileLongTermStore

NewFileLongTermStore creates a file-based long-term memory store.

func (*FileLongTermStore) Delete

func (s *FileLongTermStore) Delete(ctx context.Context, runtimeID, entryID string) error

func (*FileLongTermStore) List

func (s *FileLongTermStore) List(ctx context.Context, runtimeID string, opts ListOptions) ([]*MemoryEntry, error)

func (*FileLongTermStore) Save

func (s *FileLongTermStore) Save(ctx context.Context, runtimeID string, entry *MemoryEntry) error

func (*FileLongTermStore) Search

func (s *FileLongTermStore) Search(ctx context.Context, runtimeID string, query string, opts SearchOptions) ([]*MemoryEntry, error)

func (*FileLongTermStore) Update

func (s *FileLongTermStore) Update(ctx context.Context, runtimeID string, entry *MemoryEntry) error

type FileStore

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

FileStore is a Workspace-backed Store that persists messages as JSONL files. Each conversation is stored at {prefix}/{conversationID}/messages.jsonl with one JSON-encoded model.Message per line. Saves are incremental.

func NewFileStore

func NewFileStore(ws workspace.Workspace, prefix string) *FileStore

NewFileStore creates a FileStore rooted at the given prefix directory within the workspace (e.g. "memory").

func (*FileStore) DeleteMessages

func (s *FileStore) DeleteMessages(ctx context.Context, conversationID string) error

func (*FileStore) GetMessageRange

func (s *FileStore) GetMessageRange(ctx context.Context, conversationID string, start, end int) ([]model.Message, error)

GetMessageRange returns messages in the range [start, end).

func (*FileStore) GetMessages

func (s *FileStore) GetMessages(ctx context.Context, conversationID string) ([]model.Message, error)

func (*FileStore) GetSummary

func (s *FileStore) GetSummary(ctx context.Context, conversationID string) (string, int, error)

func (*FileStore) SaveMessages

func (s *FileStore) SaveMessages(ctx context.Context, conversationID string, messages []model.Message) error

func (*FileStore) SaveSummary

func (s *FileStore) SaveSummary(ctx context.Context, conversationID, summary string, msgCount int) error

type FileSummaryStore

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

FileSummaryStore is a Workspace-backed SummaryStore using JSONL files. It caches parsed nodes per conversation to avoid repeated disk reads.

func NewFileSummaryStore

func NewFileSummaryStore(ws workspace.Workspace, prefix string) *FileSummaryStore

NewFileSummaryStore creates a FileSummaryStore rooted at the given prefix.

func (*FileSummaryStore) DeleteByConvID

func (s *FileSummaryStore) DeleteByConvID(ctx context.Context, convID, id string) error

func (*FileSummaryStore) GetByConvID

func (s *FileSummaryStore) GetByConvID(ctx context.Context, convID, id string) (*SummaryNode, error)

func (*FileSummaryStore) List

func (s *FileSummaryStore) List(ctx context.Context, convID string, opts SummaryListOptions) ([]*SummaryNode, error)

func (*FileSummaryStore) ListAll

func (s *FileSummaryStore) ListAll(ctx context.Context, convID string) ([]*SummaryNode, error)

func (*FileSummaryStore) Rewrite

func (s *FileSummaryStore) Rewrite(ctx context.Context, convID string, nodes []*SummaryNode) error

func (*FileSummaryStore) Save

func (s *FileSummaryStore) Save(ctx context.Context, node *SummaryNode) error

func (*FileSummaryStore) Search

func (s *FileSummaryStore) Search(ctx context.Context, convID, query string, topK int) ([]*SummaryNode, error)

type InMemoryStore

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

InMemoryStore is a simple in-memory message store for development and testing. It supports a maximum conversation count and TTL-based eviction.

func NewInMemoryStore

func NewInMemoryStore(opts ...InMemoryStoreOption) *InMemoryStore

NewInMemoryStore creates a new in-memory message store with optional limits.

func (*InMemoryStore) Close

func (s *InMemoryStore) Close()

Close stops the background cleanup goroutine.

func (*InMemoryStore) DeleteMessages

func (s *InMemoryStore) DeleteMessages(_ context.Context, conversationID string) error

func (*InMemoryStore) GetMessages

func (s *InMemoryStore) GetMessages(_ context.Context, conversationID string) ([]model.Message, error)

func (*InMemoryStore) Len

func (s *InMemoryStore) Len() int

Len returns the number of stored conversations (useful for testing/monitoring).

func (*InMemoryStore) SaveMessages

func (s *InMemoryStore) SaveMessages(_ context.Context, conversationID string, messages []model.Message) error

type InMemoryStoreOption

type InMemoryStoreOption func(*InMemoryStore)

InMemoryStoreOption configures an InMemoryStore.

func WithMaxConversations

func WithMaxConversations(n int) InMemoryStoreOption

WithMaxConversations sets the upper bound on stored conversations. When exceeded, the least-recently-accessed conversation is evicted.

func WithTTL

WithTTL sets how long an idle conversation is kept before eviction.

type LTStoreOption

type LTStoreOption func(*FileLongTermStore)

LTStoreOption configures a FileLongTermStore.

func WithMaxEntries

func WithMaxEntries(n int) LTStoreOption

WithMaxEntries sets the maximum number of entries per category. 0 means no limit.

type ListOptions

type ListOptions struct {
	Category MemoryCategory
	Limit    int
	// Scope, when non-nil, restricts rows to entries in that scope bucket.
	// nil preserves legacy behavior (no scope filter).
	// If Recall is set, Scope is ignored for filtering (Recall wins).
	Scope *MemoryScope
	// Recall selects one or more partitions (user bucket, global bucket, or union).
	// When nil, [EffectiveRecallForList] derives partitioning from Scope for backward compatibility.
	Recall *RecallScope
}

ListOptions configures listing of long-term memory entries.

type LongTermConfig

type LongTermConfig struct {
	Enabled    bool     `json:"enabled"`
	Categories []string `json:"categories,omitempty"`
	MaxEntries int      `json:"max_entries,omitempty"`

	// PinnedCategories are always fully injected regardless of query relevance.
	// Empty uses default: [profile, preferences].
	PinnedCategories []MemoryCategory `json:"pinned_categories,omitempty"`

	// RecallCategories are searched by query relevance.
	// Empty uses default: [entities, events, cases, patterns].
	RecallCategories []MemoryCategory `json:"recall_categories,omitempty"`

	// RecallPartitions overrides which partitions each recall category searches.
	// nil or missing keys default to user-only ([PartitionUser]).
	// Example: shared "adventure" rows live in the global bucket — set
	// RecallPartitions["adventure"] = []MemoryPartition{PartitionUser, PartitionGlobal}.
	RecallPartitions map[MemoryCategory][]MemoryPartition `json:"recall_partitions,omitempty"`

	// ScopeEnabled, when true, causes ContextAssembler to use MemoryScope for
	// List/Search (requires SetScope on MemoryAwareMemory before Load).
	ScopeEnabled bool `json:"scope_enabled,omitempty"`

	// GlobalCategories are stored and queried in the runtime-global bucket
	// (empty UserID). When nil, DefaultGlobalCategories is used.
	GlobalCategories []MemoryCategory `json:"global_categories,omitempty"`
}

LongTermConfig controls long-term memory extraction and injection.

type LongTermStore

type LongTermStore interface {
	Save(ctx context.Context, runtimeID string, entry *MemoryEntry) error
	List(ctx context.Context, runtimeID string, opts ListOptions) ([]*MemoryEntry, error)
	Search(ctx context.Context, runtimeID string, query string, opts SearchOptions) ([]*MemoryEntry, error)
	Update(ctx context.Context, runtimeID string, entry *MemoryEntry) error
	Delete(ctx context.Context, runtimeID, entryID string) error
}

LongTermStore is the persistence interface for runtime-scoped long-term memory.

type LosslessConfig

type LosslessConfig struct {
	ChunkSize         int     `json:"chunk_size,omitempty"`
	CondenseThreshold int     `json:"condense_threshold,omitempty"`
	MaxDepth          int     `json:"max_depth,omitempty"`
	TokenBudget       int     `json:"token_budget,omitempty"`
	RecentRatio       float64 `json:"recent_ratio,omitempty"`
	CompactThreshold  int     `json:"compact_threshold,omitempty"`
	PruneLeafContent  *bool   `json:"prune_leaf_content,omitempty"`
	ArchiveThreshold  int     `json:"archive_threshold,omitempty"`
	ArchiveBatchSize  int     `json:"archive_batch_size,omitempty"`
}

LosslessConfig controls the lossless DAG memory behavior.

type LosslessMemory

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

LosslessMemory implements the Memory interface with lossless context management. All original messages are persisted; a DAG of summaries provides compressed context windows while allowing on-demand expansion.

func NewLosslessMemory

func NewLosslessMemory(store Store, dag *SummaryDAG, config DAGConfig, ws workspace.Workspace, prefix string) *LosslessMemory

NewLosslessMemory creates a new LosslessMemory.

func (*LosslessMemory) Archive

func (m *LosslessMemory) Archive(ctx context.Context, conversationID string) (ArchiveResult, error)

Archive manually triggers archiving for this conversation.

func (*LosslessMemory) Clear

func (m *LosslessMemory) Clear(ctx context.Context, conversationID string) error

func (*LosslessMemory) Close

func (m *LosslessMemory) Close()

Close waits for all pending async ingest/archive goroutines to complete.

func (*LosslessMemory) Load

func (m *LosslessMemory) Load(ctx context.Context, conversationID string) ([]model.Message, error)

func (*LosslessMemory) Save

func (m *LosslessMemory) Save(ctx context.Context, conversationID string, messages []model.Message) error

type Memory

type Memory interface {
	Load(ctx context.Context, conversationID string) ([]model.Message, error)
	Save(ctx context.Context, conversationID string, messages []model.Message) error
	Clear(ctx context.Context, conversationID string) error
}

Memory is the strategy-layer interface that decides which messages to return to the LLM.

func NewWithLLM

func NewWithLLM(cfg Config, store Store, l llm.LLM, ltStore LongTermStore, opts ...MemoryOption) (Memory, error)

NewWithLLM creates a Memory instance. All memory types are unified to lossless; deprecated type values (buffer, window, summary, token) emit a warning and are treated as lossless. When LLM is nil, lossless degrades to buffer.

type MemoryAwareMemory

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

MemoryAwareMemory injects long-term memory into LLM context via ContextAssembler.

func NewMemoryAwareMemory

func NewMemoryAwareMemory(inner Memory, assembler *ContextAssembler, ltConfig LongTermConfig) *MemoryAwareMemory

NewMemoryAwareMemory wraps inner Memory with long-term awareness.

func NewMemoryAwareMemoryCompat

func NewMemoryAwareMemoryCompat(inner Memory, ltStore LongTermStore, runtimeID string, ltConfig LongTermConfig) *MemoryAwareMemory

NewMemoryAwareMemoryCompat preserves the pre-P0 constructor signature. It builds a ContextAssembler from ltStore and LongTermConfig defaults.

func (*MemoryAwareMemory) Clear

func (m *MemoryAwareMemory) Clear(ctx context.Context, conversationID string) error

func (*MemoryAwareMemory) Load

func (m *MemoryAwareMemory) Load(ctx context.Context, conversationID string) ([]model.Message, error)

func (*MemoryAwareMemory) Save

func (m *MemoryAwareMemory) Save(ctx context.Context, conversationID string, messages []model.Message) error

func (*MemoryAwareMemory) SetRuntimeID

func (m *MemoryAwareMemory) SetRuntimeID(runtimeID string)

SetRuntimeID sets the runtime ID for long-term memory lookups.

func (*MemoryAwareMemory) SetScope

func (m *MemoryAwareMemory) SetScope(scope *MemoryScope)

SetScope sets the memory scope for subsequent Load calls when ScopeEnabled.

type MemoryCategory

type MemoryCategory string

MemoryCategory classifies a long-term memory entry.

const (
	CategoryProfile     MemoryCategory = "profile"
	CategoryPreferences MemoryCategory = "preferences"
	CategoryEntities    MemoryCategory = "entities"
	CategoryEvents      MemoryCategory = "events"
	CategoryCases       MemoryCategory = "cases"
	CategoryPatterns    MemoryCategory = "patterns"
)

func AllCategories

func AllCategories() []MemoryCategory

AllCategories returns all built-in and registered memory categories.

func DefaultGlobalCategories

func DefaultGlobalCategories() []MemoryCategory

DefaultGlobalCategories is used when LongTermConfig.GlobalCategories is empty.

type MemoryEntry

type MemoryEntry struct {
	ID        string         `json:"id"`
	Category  MemoryCategory `json:"category"`
	Content   string         `json:"content"`
	Keywords  []string       `json:"keywords,omitempty"`
	Source    MemorySource   `json:"source"`
	CreatedAt time.Time      `json:"created_at"`
	UpdatedAt time.Time      `json:"updated_at"`
	Scope     MemoryScope    `json:"scope,omitempty"`
}

MemoryEntry is a single long-term memory record.

type MemoryExtractor

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

MemoryExtractor automatically extracts long-term memory entries from conversations using LLM analysis and deduplication.

func NewMemoryExtractor

func NewMemoryExtractor(resolver llm.LLMResolver, store LongTermStore, config LongTermConfig, ecfg ExtractorConfig) *MemoryExtractor

NewMemoryExtractor creates an extractor with the given configuration. Pass ExtractorConfig{} for default behavior.

func (*MemoryExtractor) Extract

func (e *MemoryExtractor) Extract(ctx context.Context, input ExtractInput) error

Extract analyzes messages and persists long-term memory entries. Uses batch deduplication to minimize LLM calls: at most 1 extraction + 1 batch dedup call regardless of candidate count.

type MemoryOption

type MemoryOption func(*memoryOptions)

MemoryOption configures optional dependencies for memory creation.

func WithCounter

func WithCounter(c TokenCounter) MemoryOption

WithCounter injects a TokenCounter.

func WithPrefix

func WithPrefix(p string) MemoryOption

WithPrefix sets the storage prefix for lossless memory files.

func WithWorkspace

func WithWorkspace(ws workspace.Workspace) MemoryOption

WithWorkspace injects a Workspace (required for lossless).

type MemoryPartition

type MemoryPartition string

MemoryPartition names a logical partition for long-term List/Search (recall path). It is orthogonal to MemoryScope (row persistence). Implementations combine partitions as a union: an entry matches if it satisfies any listed partition.

const (
	// PartitionUser matches rows whose Scope.UserID equals RecallScope.UserID
	// (and SessionID rules when RecallScope.SessionID is non-empty).
	PartitionUser MemoryPartition = "user"
	// PartitionGlobal matches runtime-global rows (empty UserID and SessionID on the entry).
	PartitionGlobal MemoryPartition = "global"
)

func NormalizePartitions

func NormalizePartitions(parts []MemoryPartition) []MemoryPartition

NormalizePartitions returns a deduplicated slice; empty input defaults to user-only.

type MemoryScope

type MemoryScope struct {
	RuntimeID string `json:"runtime_id,omitempty"`
	UserID    string `json:"user_id,omitempty"`
	SessionID string `json:"session_id,omitempty"`
}

MemoryScope partitions long-term memory within a runtime (e.g. per end user). Empty UserID and SessionID denotes the runtime-global bucket (shared memories).

func NormalizeScopeForCategory

func NormalizeScopeForCategory(cat MemoryCategory, in MemoryScope, global []MemoryCategory) MemoryScope

NormalizeScopeForCategory returns the scope stored on an entry for the given category.

func (MemoryScope) CacheKey

func (s MemoryScope) CacheKey() string

CacheKey returns a stable key for assembler caches (must include runtime + user/session).

func (MemoryScope) IsGlobal

func (s MemoryScope) IsGlobal() bool

IsGlobal returns true when this scope refers only to the runtime-wide bucket.

type MemorySource

type MemorySource struct {
	RuntimeID      string    `json:"runtime_id,omitempty"`
	ConversationID string    `json:"conversation_id,omitempty"`
	Timestamp      time.Time `json:"timestamp,omitempty"`
}

MemorySource records the origin of a memory entry.

type MessageAppender

type MessageAppender interface {
	AppendMessages(ctx context.Context, conversationID string, messages []model.Message) error
}

MessageAppender is an optional interface for stores that can append only the newly generated messages without reloading the full history.

type Pipeline

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

Pipeline executes an ordered list of stages.

func NewFactPipeline

func NewFactPipeline(resolver llm.LLMResolver, store LongTermStore, ltConfig LongTermConfig, ecfg ExtractorConfig) *Pipeline

NewFactPipeline builds the default fact extraction pipeline.

func NewPipeline

func NewPipeline(stages ...Stage) *Pipeline

NewPipeline builds a pipeline from stages.

func (*Pipeline) Execute

func (p *Pipeline) Execute(ctx context.Context, state *PipelineState) error

Execute runs all stages in order.

type PipelineExtractor

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

PipelineExtractor orchestrates the synchronous fact extraction pipeline.

func NewPipelineExtractor

func NewPipelineExtractor(fact *Pipeline) *PipelineExtractor

NewPipelineExtractor builds an extractor around a fact pipeline.

func (*PipelineExtractor) Extract

func (pe *PipelineExtractor) Extract(ctx context.Context, input ExtractInput) error

Extract runs the fact pipeline.

type PipelineState

type PipelineState struct {
	Input                  ExtractInput
	LLM                    llm.LLM
	Candidates             []CandidateMemory
	DirectSave             []CandidateMemory
	ToDedup                []dedupItem
	Actions                []deduplicationResult
	Saved                  []*MemoryEntry
	DedupFallbackCreateAll bool
}

PipelineState carries data between extraction stages.

type RangeReader

type RangeReader interface {
	GetMessageRange(ctx context.Context, conversationID string, start, end int) ([]model.Message, error)
}

RangeReader is an optional interface for stores that support reading a subset of messages by sequence range.

type RecallScope

type RecallScope struct {
	RuntimeID  string            `json:"runtime_id,omitempty"`
	UserID     string            `json:"user_id,omitempty"`
	SessionID  string            `json:"session_id,omitempty"`
	Partitions []MemoryPartition `json:"partitions,omitempty"`
}

RecallScope describes which partitions to include in a List or Search call. At least one partition should be set; NormalizePartitions defaults to PartitionUser.

func EffectiveRecallForList

func EffectiveRecallForList(opts ListOptions, runtimeID string) *RecallScope

EffectiveRecallForList builds the active recall scope for List from ListOptions. If opts.Recall is set, it wins (normalized). Else if opts.Scope is set: global scope maps to PartitionGlobal only; otherwise user partition with that scope's UserID/SessionID. If both are nil, returns nil (legacy: no scope filter).

func EffectiveRecallForSearch

func EffectiveRecallForSearch(opts SearchOptions, runtimeID string) *RecallScope

EffectiveRecallForSearch is the Search counterpart of EffectiveRecallForList.

func (*RecallScope) CacheKey

func (r *RecallScope) CacheKey() string

CacheKey returns a stable key for assembler recall caches (partitions order-independent).

type RecentReader

type RecentReader interface {
	GetRecentMessages(ctx context.Context, conversationID string, limit int) ([]model.Message, error)
}

RecentReader is an optional interface for stores that can efficiently read only the most recent N messages for a conversation.

type SearchOptions

type SearchOptions struct {
	Category  MemoryCategory
	TopK      int
	Threshold float64
	// Scope, when non-nil, restricts hits to entries in that scope bucket.
	// If Recall is set, Scope is ignored for filtering (Recall wins).
	Scope *MemoryScope
	// Recall selects partitions for Search; nil derives from Scope via [EffectiveRecallForSearch].
	Recall *RecallScope
}

SearchOptions configures search of long-term memory entries.

type Stage

type Stage interface {
	Name() string
	Run(ctx context.Context, state *PipelineState) error
}

Stage is a single step in the extraction pipeline.

type Store

type Store interface {
	GetMessages(ctx context.Context, conversationID string) ([]model.Message, error)
	SaveMessages(ctx context.Context, conversationID string, messages []model.Message) error
	DeleteMessages(ctx context.Context, conversationID string) error
}

Store is the persistence-layer interface for short-term message storage.

type SummaryCacheStore

type SummaryCacheStore interface {
	GetSummary(ctx context.Context, conversationID string) (summary string, msgCount int, err error)
	SaveSummary(ctx context.Context, conversationID, summary string, msgCount int) error
}

SummaryCacheStore is an optional interface that Store implementations can satisfy to persist summary caches alongside messages.

type SummaryDAG

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

SummaryDAG manages the multi-layer summary DAG for a conversation.

func NewSummaryDAG

func NewSummaryDAG(store SummaryStore, msgStore Store, l llm.LLM, cfg DAGConfig, counter TokenCounter) *SummaryDAG

NewSummaryDAG creates a new SummaryDAG.

func (*SummaryDAG) Assemble

func (d *SummaryDAG) Assemble(ctx context.Context, convID string, tokenBudget int) ([]llm.Message, error)

Assemble constructs the context window from summaries + recent messages.

func (*SummaryDAG) Compact

func (d *SummaryDAG) Compact(ctx context.Context, convID string) (CompactResult, error)

Compact removes deleted nodes and prunes leaf content.

func (*SummaryDAG) Ingest

func (d *SummaryDAG) Ingest(ctx context.Context, convID string, messages []llm.Message, startSeq int) error

Ingest processes new messages and generates leaf summaries.

type SummaryListOptions

type SummaryListOptions struct {
	Depth  *int
	MinSeq int
	MaxSeq int
	Limit  int
}

SummaryListOptions controls List filtering.

type SummaryNode

type SummaryNode struct {
	ID             string    `json:"id"`
	ConversationID string    `json:"conversation_id"`
	Depth          int       `json:"depth"`
	Content        string    `json:"content"`
	ExpandHint     string    `json:"expand_hint,omitempty"`
	SourceIDs      []string  `json:"source_ids,omitempty"`
	EarliestSeq    int       `json:"earliest_seq"`
	LatestSeq      int       `json:"latest_seq"`
	TokenCount     int       `json:"token_count"`
	CreatedAt      time.Time `json:"created_at"`
	Deleted        bool      `json:"deleted,omitempty"`
}

SummaryNode represents a node in the summary DAG.

type SummaryStore

type SummaryStore interface {
	Save(ctx context.Context, node *SummaryNode) error
	GetByConvID(ctx context.Context, convID, id string) (*SummaryNode, error)
	List(ctx context.Context, convID string, opts SummaryListOptions) ([]*SummaryNode, error)
	Search(ctx context.Context, convID, query string, topK int) ([]*SummaryNode, error)
	DeleteByConvID(ctx context.Context, convID, id string) error
	ListAll(ctx context.Context, convID string) ([]*SummaryNode, error)
	Rewrite(ctx context.Context, convID string, nodes []*SummaryNode) error
}

SummaryStore persists and retrieves summary DAG nodes. All operations are scoped by conversation ID.

type TiktokenCounter

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

TiktokenCounter uses tiktoken-go for precise BPE token counting.

func NewTiktokenCounter

func NewTiktokenCounter(model string) (*TiktokenCounter, error)

NewTiktokenCounter creates a TiktokenCounter for the given model name (e.g. "gpt-4o", "gpt-4", "gpt-3.5-turbo"). Falls back to cl100k_base encoding if the model is not recognized.

func NewTiktokenCounterFromEncoding

func NewTiktokenCounterFromEncoding(encoding string) (*TiktokenCounter, error)

NewTiktokenCounterFromEncoding creates a TiktokenCounter for a specific encoding name (e.g. "cl100k_base", "o200k_base").

func (*TiktokenCounter) Count

func (c *TiktokenCounter) Count(text string) int

func (*TiktokenCounter) CountMessages

func (c *TiktokenCounter) CountMessages(msgs []model.Message) int

type TokenCounter

type TokenCounter interface {
	Count(text string) int
	CountMessages(msgs []model.Message) int
}

TokenCounter estimates or calculates the token count for text and messages.

type ToolDeps

type ToolDeps struct {
	SummaryStore SummaryStore
	MessageStore Store
	Workspace    workspace.Workspace
	Prefix       string
	Config       DAGConfig
}

ToolDeps holds dependencies for memory tools.

Jump to

Keyboard shortcuts

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