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
- func AllCategoryStrings() []string
- func BuildSummaryIndex(ctx context.Context, store SummaryStore, convID string, budget int) string
- func CategoryHalfLife(cat MemoryCategory) float64
- func ConversationIDFrom(ctx context.Context) string
- func DefaultExtractPrompt() string
- func EntryMatchesQueryScope(e *MemoryEntry, query *MemoryScope) bool
- func EntryMatchesRecallScope(e *MemoryEntry, r *RecallScope) bool
- func LoadArchivedMessages(ctx context.Context, ws workspace.Workspace, ...) ([]model.Message, error)
- func NewSummaryNodeID() string
- func RecoverArchive(ctx context.Context, ws workspace.Workspace, store Store, ...) error
- func RegisterCategory(cat MemoryCategory)
- func RegisterCategoryDescription(cat MemoryCategory, desc string)
- func RegisterTools(registry *tool.Registry, deps ToolDeps)
- func SaveManifest(ctx context.Context, ws workspace.Workspace, ...) error
- func TimeDecay(updatedAt time.Time, category MemoryCategory, now time.Time) float64
- func WithConversationID(ctx context.Context, id string) context.Context
- type ArchiveConfig
- type ArchiveManifest
- type ArchiveResult
- type ArchiveSegment
- type AssemblerConfig
- type BufferMemory
- type CandidateMemory
- type CompactConfig
- type CompactResult
- type Config
- type ConflictType
- type ContextAssembler
- type DAGConfig
- type DeduplicationAction
- type Embedder
- type EstimateCounter
- type ExtractInput
- type ExtractorConfig
- type FileLongTermStore
- func (s *FileLongTermStore) Delete(ctx context.Context, runtimeID, entryID string) error
- func (s *FileLongTermStore) List(ctx context.Context, runtimeID string, opts ListOptions) ([]*MemoryEntry, error)
- func (s *FileLongTermStore) Save(ctx context.Context, runtimeID string, entry *MemoryEntry) error
- func (s *FileLongTermStore) Search(ctx context.Context, runtimeID string, query string, opts SearchOptions) ([]*MemoryEntry, error)
- func (s *FileLongTermStore) Update(ctx context.Context, runtimeID string, entry *MemoryEntry) error
- type FileStore
- func (s *FileStore) DeleteMessages(ctx context.Context, conversationID string) error
- func (s *FileStore) GetMessageRange(ctx context.Context, conversationID string, start, end int) ([]model.Message, error)
- func (s *FileStore) GetMessages(ctx context.Context, conversationID string) ([]model.Message, error)
- func (s *FileStore) GetSummary(ctx context.Context, conversationID string) (string, int, error)
- func (s *FileStore) SaveMessages(ctx context.Context, conversationID string, messages []model.Message) error
- func (s *FileStore) SaveSummary(ctx context.Context, conversationID, summary string, msgCount int) error
- type FileSummaryStore
- func (s *FileSummaryStore) DeleteByConvID(ctx context.Context, convID, id string) error
- func (s *FileSummaryStore) GetByConvID(ctx context.Context, convID, id string) (*SummaryNode, error)
- func (s *FileSummaryStore) List(ctx context.Context, convID string, opts SummaryListOptions) ([]*SummaryNode, error)
- func (s *FileSummaryStore) ListAll(ctx context.Context, convID string) ([]*SummaryNode, error)
- func (s *FileSummaryStore) Rewrite(ctx context.Context, convID string, nodes []*SummaryNode) error
- func (s *FileSummaryStore) Save(ctx context.Context, node *SummaryNode) error
- func (s *FileSummaryStore) Search(ctx context.Context, convID, query string, topK int) ([]*SummaryNode, error)
- type HybridOption
- type HybridStore
- func (h *HybridStore) Delete(ctx context.Context, runtimeID, entryID string) error
- func (h *HybridStore) EmbedQuery(ctx context.Context, query string) ([]float32, error)
- func (h *HybridStore) List(ctx context.Context, runtimeID string, opts ListOptions) ([]*MemoryEntry, error)
- func (h *HybridStore) Save(ctx context.Context, runtimeID string, entry *MemoryEntry) error
- func (h *HybridStore) Search(ctx context.Context, runtimeID string, query string, opts SearchOptions) ([]*MemoryEntry, error)
- func (h *HybridStore) Update(ctx context.Context, runtimeID string, entry *MemoryEntry) error
- type InMemoryStore
- func (s *InMemoryStore) Close()
- func (s *InMemoryStore) DeleteMessages(_ context.Context, conversationID string) error
- func (s *InMemoryStore) GetMessages(_ context.Context, conversationID string) ([]model.Message, error)
- func (s *InMemoryStore) Len() int
- func (s *InMemoryStore) SaveMessages(_ context.Context, conversationID string, messages []model.Message) error
- type InMemoryStoreOption
- type LTStoreOption
- type ListOptions
- type LongTermConfig
- type LongTermStore
- type LosslessConfig
- type LosslessMemory
- func (m *LosslessMemory) Archive(ctx context.Context, conversationID string) (ArchiveResult, error)
- func (m *LosslessMemory) Clear(ctx context.Context, conversationID string) error
- func (m *LosslessMemory) Close()
- func (m *LosslessMemory) Load(ctx context.Context, conversationID string) ([]model.Message, error)
- func (m *LosslessMemory) Save(ctx context.Context, conversationID string, messages []model.Message) error
- type Memory
- type MemoryAwareMemory
- func (m *MemoryAwareMemory) Clear(ctx context.Context, conversationID string) error
- func (m *MemoryAwareMemory) Load(ctx context.Context, conversationID string) ([]model.Message, error)
- func (m *MemoryAwareMemory) Save(ctx context.Context, conversationID string, messages []model.Message) error
- func (m *MemoryAwareMemory) SetRuntimeID(runtimeID string)
- func (m *MemoryAwareMemory) SetScope(scope *MemoryScope)
- type MemoryCategory
- type MemoryEntry
- type MemoryExtractor
- type MemoryOption
- type MemoryPartition
- type MemoryScope
- type MemorySource
- type MessageAppender
- type Pipeline
- type PipelineExtractor
- type PipelineState
- type RangeReader
- type RecallScope
- type RecentReader
- type SearchOptions
- type Stage
- type Store
- type SummaryCacheStore
- type SummaryDAG
- func (d *SummaryDAG) Assemble(ctx context.Context, convID string, tokenBudget int) ([]llm.Message, error)
- func (d *SummaryDAG) Compact(ctx context.Context, convID string) (CompactResult, error)
- func (d *SummaryDAG) Ingest(ctx context.Context, convID string, messages []llm.Message, startSeq int) error
- type SummaryListOptions
- type SummaryNode
- type SummaryStore
- type TiktokenCounter
- type TokenCounter
- type ToolDeps
- type VectorSearcher
Constants ¶
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 ¶
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 ¶
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, archivePrefix, 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, archivePrefix, 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 ¶
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, archivePrefix, convID string, m *ArchiveManifest) error
SaveManifest writes the archive manifest.
Types ¶
type ArchiveConfig ¶
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, archivePrefix, 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.
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
// MaxRecallConcurrency limits the number of recall category searches that
// run in parallel. Zero or negative values default to defaultMaxRecallConcurrency.
MaxRecallConcurrency int
}
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
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 ¶
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.
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 ¶
NewFileStore creates a FileStore rooted at the given prefix directory within the workspace (e.g. "memory").
func (*FileStore) DeleteMessages ¶
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 (*FileStore) GetSummary ¶
func (*FileStore) SaveMessages ¶
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 HybridOption ¶ added in v0.1.4
type HybridOption func(*HybridStore)
HybridOption configures a HybridStore.
func WithEmbedder ¶ added in v0.1.4
func WithEmbedder(e Embedder) HybridOption
WithEmbedder attaches an Embedder used for pre-computing query vectors and as a fallback when SearchOptions.QueryVector is nil.
func WithVectorSearcher ¶ added in v0.1.4
func WithVectorSearcher(v VectorSearcher) HybridOption
WithVectorSearcher attaches a vector backend for similarity search.
type HybridStore ¶ added in v0.1.4
type HybridStore struct {
// contains filtered or unexported fields
}
HybridStore wraps a LongTermStore with optional embedding and vector search capabilities. When embedder and vector are nil it behaves identically to the inner store with zero overhead.
func NewHybridStore ¶ added in v0.1.4
func NewHybridStore(inner LongTermStore, opts ...HybridOption) *HybridStore
NewHybridStore creates a HybridStore wrapping inner. Panics if inner is nil.
func (*HybridStore) Delete ¶ added in v0.1.4
func (h *HybridStore) Delete(ctx context.Context, runtimeID, entryID string) error
func (*HybridStore) EmbedQuery ¶ added in v0.1.4
EmbedQuery pre-computes a query vector. Returns (nil, nil) when no Embedder is configured, which lets callers fall through without special-casing.
func (*HybridStore) List ¶ added in v0.1.4
func (h *HybridStore) List(ctx context.Context, runtimeID string, opts ListOptions) ([]*MemoryEntry, error)
func (*HybridStore) Save ¶ added in v0.1.4
func (h *HybridStore) Save(ctx context.Context, runtimeID string, entry *MemoryEntry) error
func (*HybridStore) Search ¶ added in v0.1.4
func (h *HybridStore) Search(ctx context.Context, runtimeID string, query string, opts SearchOptions) ([]*MemoryEntry, error)
Search performs BM25 search via the inner store. When a VectorSearcher is configured it additionally performs vector similarity search and merges the two result sets. If QueryVector is not provided but an Embedder is present, the embedding is computed on the fly (this path is skipped when the caller pre-computes the vector via EmbedQuery).
func (*HybridStore) Update ¶ added in v0.1.4
func (h *HybridStore) Update(ctx context.Context, runtimeID string, entry *MemoryEntry) 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 (*InMemoryStore) Len ¶
func (s *InMemoryStore) Len() int
Len returns the number of stored conversations (useful for testing/monitoring).
func (*InMemoryStore) SaveMessages ¶
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 ¶
func WithTTL(d time.Duration) InMemoryStoreOption
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.
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) 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 ¶
NewPipeline builds a pipeline from stages.
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
// QueryVector, when non-nil, is passed directly to the store implementation,
// allowing the caller to pre-compute the embedding once and share it across
// multiple category searches. Stores that do not support vector search ignore it.
QueryVector []float32
}
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.
type SummaryListOptions ¶
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 ¶
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.
type VectorSearcher ¶ added in v0.1.4
type VectorSearcher interface {
SearchByVector(ctx context.Context, runtimeID string, vec []float32, opts SearchOptions) ([]*MemoryEntry, error)
}
VectorSearcher performs similarity search over pre-indexed vectors.
Source Files
¶
- archive.go
- assembler.go
- assembler_cache.go
- aware.go
- buffer.go
- context.go
- counter.go
- extractor.go
- fact_pipeline.go
- factory.go
- file_long_term.go
- file_store.go
- long_term.go
- long_term_hybrid.go
- lossless.go
- memory.go
- pipeline.go
- pipeline_extractor.go
- pipeline_metrics.go
- recall_scope.go
- sanitize.go
- summary_dag.go
- summary_index.go
- summary_prompt.go
- summary_store.go
- tools.go