Documentation
¶
Overview ¶
Package reasoningbank provides cross-session memory storage and retrieval for AI agents.
The package stores memories (learned strategies and patterns) in a vector database, enabling semantic search to surface relevant knowledge based on similarity to the current task. Memories track their usefulness through a Bayesian confidence system that learns from usage signals, explicit feedback, and task outcomes.
Core Concepts ¶
Memories are distilled strategies learned from agent interactions. Each memory has:
- Title and content describing the strategy or pattern
- Outcome: "success" (pattern to follow) or "failure" (anti-pattern to avoid)
- Confidence score (0.0-1.0) adjusted by feedback and usage
- Project isolation via database-per-project architecture
Confidence System ¶
The Bayesian confidence system learns which signals predict memory usefulness:
- Explicit feedback: User ratings (helpful/unhelpful)
- Usage signals: Memory retrieved in semantic search
- Outcome signals: Task succeeded/failed after using memory
The system learns signal weights per-project, adapting to each codebase's patterns. Memories below MinConfidence (0.7) are filtered from search results.
Memory Consolidation ¶
The Distiller detects similar memories and consolidates them into synthesized knowledge:
- Finds clusters of memories above similarity threshold (0.8 default)
- Uses LLM to merge clusters into consolidated memories
- Archives source memories with back-links for attribution
- Consolidated memories receive 20% similarity boost in search
Security ¶
The package implements defense-in-depth security:
- Multi-tenant isolation via payload-based filtering
- Fail-closed: operations require tenant context
- Per-project vectorstore databases (StoreProvider)
- Filter injection prevention (tenant_id in user filters rejected)
Usage ¶
Basic memory recording and search:
svc, err := reasoningbank.NewServiceWithStoreProvider(stores, "username", logger)
if err != nil {
log.Fatal(err)
}
// Record a new memory
memory, err := reasoningbank.NewMemory(
"projectID",
"Go error handling with context",
"Always wrap errors with context using fmt.Errorf with %w verb",
reasoningbank.OutcomeSuccess,
[]string{"go", "errors"},
)
if err := svc.Record(ctx, memory); err != nil {
log.Fatal(err)
}
// Search for relevant memories
memories, err := svc.Search(ctx, "projectID", "how to handle errors", 5)
if err != nil {
log.Fatal(err)
}
for _, mem := range memories {
fmt.Printf("%.2f: %s\n", mem.Confidence, mem.Title)
}
// Record feedback to improve confidence
if err := svc.Feedback(ctx, memories[0].ID, true); err != nil {
log.Fatal(err)
}
MCP Integration ¶
The package is exposed via MCP tools:
- memory_search: Find relevant memories by semantic similarity
- memory_record: Save new memory explicitly (bypasses distillation)
- memory_feedback: Rate memory helpfulness (helpful/unhelpful)
- memory_outcome: Report task success/failure after using memory
See CLAUDE.md for MCP tool usage patterns.
Index ¶
- Constants
- Variables
- func ComputeConfidenceFromHybrid(agg *SignalAggregate, recentSignals []Signal, weights *ProjectWeights) float64
- func CosineSimilarity(vec1, vec2 []float32) float64
- func IsValidCategory(s string) bool
- type CategoryClassifier
- type CategoryRule
- type ConfidenceCalculator
- func (c *ConfidenceCalculator) ComputeConfidence(ctx context.Context, memoryID, projectID string) (float64, error)
- func (c *ConfidenceCalculator) LearnFromFeedback(ctx context.Context, projectID, memoryID string, helpful bool) error
- func (c *ConfidenceCalculator) RecordSignal(ctx context.Context, signal *Signal) error
- type ConfigurableClassifier
- type ConfigurableClassifierOption
- type ConsolidatedMemory
- type ConsolidationOptions
- type ConsolidationResult
- type ConsolidationScheduler
- type ConsolidationType
- type Distiller
- func (d *Distiller) Consolidate(ctx context.Context, projectID string, opts ConsolidationOptions) (*ConsolidationResult, error)
- func (d *Distiller) ConsolidateAll(ctx context.Context, projectIDs []string, opts ConsolidationOptions) (*ConsolidationResult, error)
- func (d *Distiller) DistillSession(ctx context.Context, summary SessionSummary) error
- func (d *Distiller) FindSimilarClusters(ctx context.Context, projectID string, threshold float64) ([]SimilarityCluster, error)
- func (d *Distiller) MergeCluster(ctx context.Context, cluster *SimilarityCluster) (*Memory, error)
- type DistillerOption
- type Fact
- type FactExtractor
- type InMemorySignalStore
- func (s *InMemorySignalStore) GetAggregate(ctx context.Context, memoryID string) (*SignalAggregate, error)
- func (s *InMemorySignalStore) GetProjectWeights(ctx context.Context, projectID string) (*ProjectWeights, error)
- func (s *InMemorySignalStore) GetRecentSignals(ctx context.Context, memoryID string, duration time.Duration) ([]Signal, error)
- func (s *InMemorySignalStore) RollupOldSignals(ctx context.Context, memoryID string, cutoff time.Duration) error
- func (s *InMemorySignalStore) StoreAggregate(ctx context.Context, agg *SignalAggregate) error
- func (s *InMemorySignalStore) StoreProjectWeights(ctx context.Context, weights *ProjectWeights) error
- func (s *InMemorySignalStore) StoreSignal(ctx context.Context, signal *Signal) error
- type LLMClient
- type Memory
- type MemoryCategory
- type MemoryConfidence
- type MemoryConsolidator
- type MemoryGranularity
- type MemoryState
- type Outcome
- type ProjectWeights
- type RegexCategoryClassifier
- type RuleScope
- type SchedulerOption
- type ScoredMemory
- type SearchMetadata
- type Service
- func (s *Service) Count(ctx context.Context, projectID string) (int, error)
- func (s *Service) Delete(ctx context.Context, id string) error
- func (s *Service) DeleteByProjectID(ctx context.Context, projectID, memoryID string) error
- func (s *Service) Feedback(ctx context.Context, memoryID string, helpful bool) error
- func (s *Service) FlushSession(ctx context.Context, projectID, sessionID string) ([]string, error)
- func (s *Service) Get(ctx context.Context, id string) (*Memory, error)
- func (s *Service) GetByProjectID(ctx context.Context, projectID, memoryID string) (*Memory, error)
- func (s *Service) GetMemoryVector(ctx context.Context, memoryID string) ([]float32, error)
- func (s *Service) GetMemoryVectorByProjectID(ctx context.Context, projectID, memoryID string) ([]float32, error)
- func (s *Service) ListMemories(ctx context.Context, projectID string, limit, offset int) ([]Memory, error)
- func (s *Service) Record(ctx context.Context, memory *Memory) error
- func (s *Service) RecordOutcome(ctx context.Context, memoryID string, succeeded bool, sessionID string) (float64, error)
- func (s *Service) Search(ctx context.Context, projectID, query string, limit int) ([]Memory, error)
- func (s *Service) SearchWithMetadata(ctx context.Context, projectID, query string, limit int) ([]ScoredMemory, *SearchMetadata, error)
- func (s *Service) SearchWithScores(ctx context.Context, projectID, query string, limit int) ([]ScoredMemory, error)
- func (s *Service) Stats() Stats
- type ServiceOption
- func WithDefaultTenant(tenantID string) ServiceOption
- func WithEmbedder(embedder vectorstore.Embedder) ServiceOption
- func WithReranker(r reranker.Reranker) ServiceOption
- func WithSessionGranularity(extractor FactExtractor, logger *zap.Logger, maxBufferedTurns int) ServiceOption
- func WithSignalStore(ss SignalStore) ServiceOption
- type SessionBuffer
- type SessionBufferManager
- func (m *SessionBufferManager) ActiveSessions() int
- func (m *SessionBufferManager) BufferTurn(projectID, sessionID string, entry TurnEntry) error
- func (m *SessionBufferManager) Count(projectID, sessionID string) int
- func (m *SessionBufferManager) FlushBuffer(projectID, sessionID string) *SessionBuffer
- func (m *SessionBufferManager) GetBuffer(projectID, sessionID string) *SessionBuffer
- type SessionOutcome
- type SessionSummarizer
- type SessionSummary
- type Signal
- type SignalAggregate
- type SignalStore
- type SignalType
- type SimilarityCluster
- type SimpleExtractor
- type Stats
- type TurnEntry
Constants ¶
const ( // MinConfidence is the minimum confidence threshold for search results. MinConfidence = 0.7 // ExplicitRecordConfidence is the initial confidence for explicitly recorded memories. ExplicitRecordConfidence = 0.8 // DistilledConfidence is the initial confidence for distilled memories. DistilledConfidence = 0.6 // DefaultSearchLimit is the default maximum number of search results. DefaultSearchLimit = 10 )
Variables ¶
var ( ErrEmptyFactText = errors.New("fact text cannot be empty") ErrInvalidFactSubject = errors.New("fact subject cannot be empty") ErrInvalidFactPredicate = errors.New("fact predicate cannot be empty") ErrInvalidFactObject = errors.New("fact object cannot be empty") )
Common errors for fact extraction operations.
var ( ErrMemoryNotFound = errors.New("memory not found") ErrInvalidMemory = errors.New("invalid memory") ErrEmptyTitle = errors.New("memory title cannot be empty") ErrEmptyContent = errors.New("memory content cannot be empty") ErrInvalidConfidence = errors.New("confidence must be between 0.0 and 1.0") ErrInvalidOutcome = errors.New("outcome must be 'success' or 'failure'") ErrEmptyProjectID = errors.New("project ID cannot be empty") )
Common errors for ReasoningBank operations.
var (
ErrEmptyMemoryID = errors.New("memory ID cannot be empty")
)
Signal-related errors.
var ValidCategories = map[string]MemoryCategory{ "operational": CategoryOperational, "architectural": CategoryArchitectural, "debugging": CategoryDebugging, "security": CategorySecurity, "feature": CategoryFeature, "general": CategoryGeneral, }
ValidCategories maps valid category strings to their typed values.
Functions ¶
func ComputeConfidenceFromHybrid ¶ added in v0.3.0
func ComputeConfidenceFromHybrid(agg *SignalAggregate, recentSignals []Signal, weights *ProjectWeights) float64
ComputeConfidenceFromHybrid calculates confidence using both historical aggregates and recent signals.
This is the core Bayesian confidence calculation that combines: - Historical data from aggregates (signals older than 30 days, rolled up) - Recent signals (last 30 days, stored individually) - Learned project weights for each signal type
The formula uses Beta distribution: confidence = alpha / (alpha + beta)
func CosineSimilarity ¶ added in v0.3.0
CosineSimilarity computes the cosine similarity between two embedding vectors.
Cosine similarity measures the cosine of the angle between two vectors, producing a value between -1 and 1:
- 1.0: vectors point in the same direction (identical)
- 0.0: vectors are orthogonal (unrelated)
- -1.0: vectors point in opposite directions (opposite)
For embedding vectors, similarity is typically in the range [0, 1] since embeddings generally have positive components.
Formula: cos(θ) = (A · B) / (||A|| * ||B||)
Returns 0.0 for invalid inputs (empty vectors, zero-magnitude vectors, or vectors of different lengths).
func IsValidCategory ¶ added in v0.5.0
IsValidCategory returns true if the string is a recognized category.
Types ¶
type CategoryClassifier ¶ added in v0.5.0
type CategoryClassifier interface {
// Classify returns the best-matching category and a confidence score (0.0-1.0).
Classify(title, content string, tags []string) (MemoryCategory, float64)
}
CategoryClassifier assigns a category and confidence to memory content.
type CategoryRule ¶ added in v0.5.0
type CategoryRule struct {
// Pattern is a regex pattern string. Compiled at construction time.
Pattern string
// Category is the target category when the pattern matches.
Category MemoryCategory
// Confidence is the base confidence score (0.0-1.0) for this rule.
Confidence float64
}
CategoryRule is the exported version of categoryRule for use in ConfigurableClassifier. Users construct these to define custom classification rules at org/team/project level.
type ConfidenceCalculator ¶ added in v0.3.0
type ConfidenceCalculator struct {
// contains filtered or unexported fields
}
ConfidenceCalculator provides methods for computing and updating memory confidence.
func NewConfidenceCalculator ¶ added in v0.3.0
func NewConfidenceCalculator(store SignalStore) *ConfidenceCalculator
NewConfidenceCalculator creates a new confidence calculator.
func (*ConfidenceCalculator) ComputeConfidence ¶ added in v0.3.0
func (c *ConfidenceCalculator) ComputeConfidence(ctx context.Context, memoryID, projectID string) (float64, error)
ComputeConfidence calculates the current confidence for a memory.
func (*ConfidenceCalculator) LearnFromFeedback ¶ added in v0.3.0
func (c *ConfidenceCalculator) LearnFromFeedback(ctx context.Context, projectID, memoryID string, helpful bool) error
LearnFromFeedback updates project weights based on feedback accuracy.
func (*ConfidenceCalculator) RecordSignal ¶ added in v0.3.0
func (c *ConfidenceCalculator) RecordSignal(ctx context.Context, signal *Signal) error
RecordSignal stores a new signal and updates confidence.
type ConfigurableClassifier ¶ added in v0.5.0
type ConfigurableClassifier struct {
// contains filtered or unexported fields
}
ConfigurableClassifier extends RegexCategoryClassifier with hierarchical custom rules at org, team, and project levels. Rules are evaluated in priority order: project → team → org → built-in.
This mirrors the remediation service's scope hierarchy pattern where project-level rules override team, team overrides org.
Thread-safe: all rules are compiled at construction time and immutable.
func NewConfigurableClassifier ¶ added in v0.5.0
func NewConfigurableClassifier(opts ...ConfigurableClassifierOption) *ConfigurableClassifier
NewConfigurableClassifier creates a classifier with hierarchical custom rules. Custom rules are evaluated in scope priority order (project → team → org), falling back to built-in regex rules if no custom rule matches.
func (*ConfigurableClassifier) Classify ¶ added in v0.5.0
func (c *ConfigurableClassifier) Classify(title, content string, tags []string) (MemoryCategory, float64)
Classify evaluates custom rules in scope priority order, then falls back to built-in rules. Returns the best-matching category and confidence.
Evaluation order: project rules → team rules → org rules → built-in rules. Within each scope, first-match wins (same as RegexCategoryClassifier).
type ConfigurableClassifierOption ¶ added in v0.5.0
type ConfigurableClassifierOption func(*ConfigurableClassifier)
ConfigurableClassifierOption configures a ConfigurableClassifier.
func WithScopedRules ¶ added in v0.5.0
func WithScopedRules(scope RuleScope, rules []CategoryRule) ConfigurableClassifierOption
WithScopedRules adds a set of custom rules at a specific scope level. Rules are compiled at construction time; invalid patterns cause an error.
type ConsolidatedMemory ¶ added in v0.3.0
type ConsolidatedMemory struct {
// Memory is the consolidated memory record.
*Memory
// SourceIDs contains the IDs of all source memories that were consolidated.
SourceIDs []string `json:"source_ids"`
// ConsolidationType indicates the method used for consolidation.
ConsolidationType ConsolidationType `json:"consolidation_type"`
// SourceAttribution provides context about how the source memories contributed.
// This is a human-readable description generated by the LLM during synthesis.
SourceAttribution string `json:"source_attribution,omitempty"`
}
ConsolidatedMemory represents a memory created by consolidating multiple source memories.
ConsolidatedMemories are created by the Distiller when it detects similar or related memories that can be merged into more valuable synthesized knowledge. The original source memories are preserved with their ConsolidationID field pointing to this consolidated memory.
type ConsolidationOptions ¶ added in v0.3.0
type ConsolidationOptions struct {
// SimilarityThreshold is the minimum cosine similarity score (0.0-1.0) for
// memories to be considered similar enough for consolidation.
// Default: 0.8
// Higher values require more similarity, lower values allow looser grouping.
SimilarityThreshold float64 `json:"similarity_threshold"`
// MaxClustersPerRun limits the number of similarity clusters to process in
// a single consolidation run. This helps control resource usage and runtime.
// Set to 0 for no limit (process all clusters found).
MaxClustersPerRun int `json:"max_clusters_per_run"`
// DryRun, when true, performs similarity detection and reports what would be
// consolidated without actually creating consolidated memories or archiving
// source memories. Useful for previewing consolidation impact.
DryRun bool `json:"dry_run"`
// ForceAll, when true, ignores recent consolidation timestamps and re-evaluates
// all memories for consolidation, even if they were recently processed.
// Use this to force a complete re-consolidation of the project's memory base.
ForceAll bool `json:"force_all"`
}
ConsolidationOptions configures the behavior of memory consolidation operations.
These options control how consolidation runs, including similarity thresholds, resource limits, and whether to perform a dry run or force consolidation regardless of recent runs.
type ConsolidationResult ¶ added in v0.3.0
type ConsolidationResult struct {
// CreatedMemories contains the IDs of newly created consolidated memories.
CreatedMemories []string `json:"created_memories"`
// ArchivedMemories contains the IDs of source memories that were archived
// after being consolidated into new memories. These memories are preserved
// with their ConsolidationID field pointing to the consolidated memory.
ArchivedMemories []string `json:"archived_memories"`
// SkippedCount is the number of memories that were evaluated but not
// consolidated (e.g., no similar memories found, below threshold).
SkippedCount int `json:"skipped_count"`
// TotalProcessed is the total number of memories examined during consolidation.
TotalProcessed int `json:"total_processed"`
// Duration is how long the consolidation operation took to complete.
Duration time.Duration `json:"duration"`
}
ConsolidationResult contains the results of a memory consolidation operation.
This structure tracks the outcome of running memory consolidation, including which memories were created (consolidated memories), which were archived (source memories linked to consolidated versions), how many were skipped (didn't meet consolidation criteria), and performance metrics.
type ConsolidationScheduler ¶ added in v0.3.0
type ConsolidationScheduler struct {
// contains filtered or unexported fields
}
ConsolidationScheduler manages automatic scheduled memory consolidation.
The scheduler runs consolidation periodically in the background for configured projects. It provides lifecycle management (Start/Stop) with graceful shutdown and ensures consolidation runs on a predictable schedule.
Thread Safety: All public methods are thread-safe. The running state is protected by a mutex to prevent race conditions during Start/Stop operations.
func NewConsolidationScheduler ¶ added in v0.3.0
func NewConsolidationScheduler(distiller *Distiller, logger *zap.Logger, opts ...SchedulerOption) (*ConsolidationScheduler, error)
NewConsolidationScheduler creates a new consolidation scheduler.
The scheduler does not start automatically - call Start() to begin scheduled consolidation runs.
Parameters:
- distiller: The distiller to use for consolidation
- logger: Logger for structured logging
- opts: Optional configuration options
Returns:
- A new scheduler instance
- Error if distiller or logger is nil
func (*ConsolidationScheduler) Start ¶ added in v0.3.0
func (s *ConsolidationScheduler) Start() error
Start begins the background consolidation scheduler.
The scheduler runs consolidation at the configured interval until Stop() is called. This method is idempotent - calling Start() on an already running scheduler returns an error without starting a second goroutine.
Thread Safety: This method is thread-safe and can be called concurrently.
Returns:
- Error if the scheduler is already running
func (*ConsolidationScheduler) Stop ¶ added in v0.3.0
func (s *ConsolidationScheduler) Stop() error
Stop gracefully stops the consolidation scheduler.
Signals the background goroutine to stop and waits for it to finish. This method is idempotent - calling Stop() on an already stopped scheduler is a no-op.
Thread Safety: This method is thread-safe and can be called concurrently.
Returns:
- Always returns nil (for interface compatibility and future error handling)
type ConsolidationType ¶ added in v0.3.0
type ConsolidationType string
ConsolidationType represents the method used to create a consolidated memory.
const ( // ConsolidationMerged indicates memories were merged into a single synthesized memory. ConsolidationMerged ConsolidationType = "merged" // ConsolidationDeduplicated indicates duplicate or near-duplicate memories were combined. ConsolidationDeduplicated ConsolidationType = "deduplicated" // ConsolidationSynthesized indicates memories were synthesized into higher-level knowledge. ConsolidationSynthesized ConsolidationType = "synthesized" )
type Distiller ¶
type Distiller struct {
// contains filtered or unexported fields
}
Distiller extracts learnings from completed sessions and creates memories.
FR-006: Distillation pipeline for async memory extraction FR-009: Outcome differentiation (success vs failure)
func NewDistiller ¶
func NewDistiller(service *Service, logger *zap.Logger, opts ...DistillerOption) (*Distiller, error)
NewDistiller creates a new session distiller.
func (*Distiller) Consolidate ¶ added in v0.3.0
func (d *Distiller) Consolidate(ctx context.Context, projectID string, opts ConsolidationOptions) (*ConsolidationResult, error)
Consolidate runs the full memory consolidation process for a project.
This method orchestrates the complete consolidation workflow:
- Check if consolidation was run recently (unless ForceAll is set)
- Find all similarity clusters above the specified threshold
- Limit to MaxClustersPerRun if specified (0 = no limit)
- For each cluster, merge into a consolidated memory
- Link source memories to their consolidated versions
- Track last consolidation time to avoid re-processing
- Return statistics about the consolidation run
In DryRun mode, the method performs similarity detection and reports what would be consolidated without actually creating consolidated memories or archiving source memories.
Parameters:
- ctx: Context for cancellation and timeouts
- projectID: Project to consolidate memories for
- opts: Configuration options (threshold, limits, dry-run mode, etc.)
Returns:
- ConsolidationResult with statistics and outcomes
- Error if consolidation fails
func (*Distiller) ConsolidateAll ¶ added in v0.3.0
func (d *Distiller) ConsolidateAll(ctx context.Context, projectIDs []string, opts ConsolidationOptions) (*ConsolidationResult, error)
ConsolidateAll runs memory consolidation across all specified projects.
This method is designed for scheduled background runs and batch processing. It runs consolidation on each project with the same options and aggregates the results. If consolidation fails for individual projects, the error is logged and the method continues processing remaining projects.
This is useful for:
- Scheduled background consolidation (e.g., daily cron job)
- Bulk maintenance operations
- Organization-wide memory cleanup
Parameters:
- ctx: Context for cancellation and timeouts
- projectIDs: List of project IDs to consolidate
- opts: Configuration options applied to all projects
Returns:
- Aggregated ConsolidationResult combining all project results
- Error only if all projects fail (partial failures are logged)
func (*Distiller) DistillSession ¶
func (d *Distiller) DistillSession(ctx context.Context, summary SessionSummary) error
DistillSession extracts learnings from a completed session and creates memories.
This is called asynchronously after a session ends, so it should not block.
Success patterns (outcome="success") become positive memories. Failure patterns (outcome="failure") become anti-pattern warnings.
Initial confidence is set to DistilledConfidence (0.6) since distilled memories are less reliable than explicit captures (0.8).
func (*Distiller) FindSimilarClusters ¶ added in v0.3.0
func (d *Distiller) FindSimilarClusters(ctx context.Context, projectID string, threshold float64) ([]SimilarityCluster, error)
FindSimilarClusters detects groups of similar memories for a project.
Searches all memories in the project and groups those with similarity scores above the threshold. Uses greedy clustering: for each memory, finds all similar memories above threshold, forms cluster if >=2 members.
The algorithm:
- Retrieve all memories for the project
- Get embedding vectors for each memory
- For each memory, compute similarity with all other memories
- Group memories with similarity > threshold
- Form clusters only if they have >= 2 members
- Calculate cluster statistics (centroid, average similarity, min similarity)
Parameters:
- ctx: Context for cancellation and timeouts
- projectID: Project to search for similar memories
- threshold: Minimum similarity score (0.0-1.0, typically 0.8)
Returns:
- Slice of similarity clusters, each containing related memories
- Error if clustering fails
func (*Distiller) MergeCluster ¶ added in v0.3.0
MergeCluster synthesizes a cluster of similar memories into one consolidated memory.
This method uses the configured LLM client to analyze the cluster members and create a synthesized memory that captures their common themes and key insights. The process:
- Validates the cluster has at least 2 members and LLM client is configured
- Builds a consolidation prompt from cluster members
- Calls the LLM to synthesize the memories
- Parses the LLM response into a Memory struct
- Calculates consolidated confidence from source memories
- Stores the new consolidated memory
- Links source memories to the consolidated version
The consolidated memory includes source attribution and links back to the original memories via their ConsolidationID fields.
Parameters:
- ctx: Context for cancellation and timeouts
- cluster: Similarity cluster to merge (must have >= 2 members)
Returns:
- The newly created consolidated memory
- Error if LLM client not configured, synthesis fails, or storage fails
type DistillerOption ¶ added in v0.3.0
type DistillerOption func(*Distiller)
DistillerOption configures a Distiller.
func WithConsolidationWindow ¶ added in v0.3.0
func WithConsolidationWindow(window time.Duration) DistillerOption
WithConsolidationWindow sets the minimum time between consolidations. If not set, defaults to 24 hours.
func WithLLMClient ¶ added in v0.3.0
func WithLLMClient(client LLMClient) DistillerOption
WithLLMClient sets the LLM client for memory consolidation. This is required for MergeCluster to work.
type Fact ¶ added in v0.4.0
type Fact struct {
// ID is the unique fact identifier (UUID).
ID string `json:"id"`
// Subject is the entity performing or being described by the action/property.
// Example: "I", "Claude", "user", "the system"
Subject string `json:"subject"`
// Predicate is the relationship or action between subject and object.
// Example: "attended", "learned", "considering", "implemented"
Predicate string `json:"predicate"`
// Object is the entity being acted upon or the property value.
// Example: "meeting X", "Go error handling", "architecture review"
Object string `json:"object"`
// Timestamp is when this fact was extracted or when it occurred.
// Supports temporal reference resolution (e.g., "yesterday" -> absolute date).
Timestamp time.Time `json:"timestamp"`
// Confidence is a score from 0.0 to 1.0 indicating extraction reliability.
// Higher values indicate higher confidence in the extraction.
// Example: 1.0 for explicit statements, 0.7 for implicit inferences.
Confidence float64 `json:"confidence"`
// Provenance is the original source text from which this fact was extracted.
// Preserved for verification and traceability.
Provenance string `json:"provenance"`
// SourceID is the ID of the memory or message from which this fact was extracted.
// Links facts back to their source for context and attribution.
SourceID string `json:"source_id"`
// ProjectID identifies which project this fact belongs to.
ProjectID string `json:"project_id"`
// CreatedAt is when the fact was extracted.
CreatedAt time.Time `json:"created_at"`
}
Fact represents a structured triple extracted from text (subject-predicate-object).
Facts capture relationships between entities extracted from memory content or conversations. Each fact has: - Subject: The entity performing or being described by the action/property - Predicate: The relationship/action between subject and object - Object: The entity being acted upon or the property value
Facts are used to build a knowledge graph of learned information over time.
type FactExtractor ¶ added in v0.4.0
type FactExtractor interface {
// Extract parses text and returns structured facts.
//
// Supports:
// - Subject-verb-object patterns: "I went to X" -> (I, attended, X)
// - Temporal references: "yesterday", "last week" -> resolved to absolute dates
// - Implicit relations: "I'm thinking about X" -> (I, considering, X)
//
// Parameters:
// - ctx: Context for cancellation and timeouts
// - text: Source text to extract facts from
// - referenceDate: Base date for resolving temporal references
//
// Returns:
// - Slice of extracted facts with confidence scores
// - Error if extraction fails
Extract(ctx context.Context, text string, referenceDate time.Time) ([]Fact, error)
}
FactExtractor defines the interface for extracting facts from text.
Implementations extract structured triples (subject-predicate-object) from unstructured text, with support for temporal reference resolution.
type InMemorySignalStore ¶ added in v0.3.0
type InMemorySignalStore struct {
// contains filtered or unexported fields
}
InMemorySignalStore is an in-memory implementation of SignalStore for testing.
func NewInMemorySignalStore ¶ added in v0.3.0
func NewInMemorySignalStore() *InMemorySignalStore
NewInMemorySignalStore creates a new in-memory signal store.
func (*InMemorySignalStore) GetAggregate ¶ added in v0.3.0
func (s *InMemorySignalStore) GetAggregate(ctx context.Context, memoryID string) (*SignalAggregate, error)
GetAggregate retrieves an aggregate, returning empty aggregate if not found.
func (*InMemorySignalStore) GetProjectWeights ¶ added in v0.3.0
func (s *InMemorySignalStore) GetProjectWeights(ctx context.Context, projectID string) (*ProjectWeights, error)
GetProjectWeights retrieves project weights, returning defaults if not found.
func (*InMemorySignalStore) GetRecentSignals ¶ added in v0.3.0
func (s *InMemorySignalStore) GetRecentSignals(ctx context.Context, memoryID string, duration time.Duration) ([]Signal, error)
GetRecentSignals retrieves signals newer than the cutoff.
func (*InMemorySignalStore) RollupOldSignals ¶ added in v0.3.0
func (s *InMemorySignalStore) RollupOldSignals(ctx context.Context, memoryID string, cutoff time.Duration) error
RollupOldSignals moves old signals into aggregates.
func (*InMemorySignalStore) StoreAggregate ¶ added in v0.3.0
func (s *InMemorySignalStore) StoreAggregate(ctx context.Context, agg *SignalAggregate) error
StoreAggregate saves an aggregate.
func (*InMemorySignalStore) StoreProjectWeights ¶ added in v0.3.0
func (s *InMemorySignalStore) StoreProjectWeights(ctx context.Context, weights *ProjectWeights) error
StoreProjectWeights saves project weights.
func (*InMemorySignalStore) StoreSignal ¶ added in v0.3.0
func (s *InMemorySignalStore) StoreSignal(ctx context.Context, signal *Signal) error
StoreSignal adds a signal to the store.
type LLMClient ¶ added in v0.3.0
type LLMClient interface {
// Complete generates a completion from the given prompt.
//
// The context can be used for cancellation and deadline control.
// The prompt will be a structured memory consolidation request.
//
// Returns the generated text containing TITLE:, CONTENT:, TAGS:,
// OUTCOME:, and optionally SOURCE_ATTRIBUTION: fields.
//
// Returns an error if:
// - The API request fails
// - The context is cancelled or times out
// - Rate limits are exceeded (after retries)
Complete(ctx context.Context, prompt string) (string, error)
}
LLMClient provides an interface for interacting with LLM backends.
This interface allows pluggable LLM providers (Claude, OpenAI, local models) to be used for memory synthesis and consolidation tasks. Implementations should handle retries, rate limiting, and error handling internally.
Expected Prompt Format ¶
The Complete method will receive structured prompts from the distiller containing memory consolidation instructions. The prompt format is:
You are a memory consolidation assistant... ## Source Memories ### Memory 1: [Title] **Description:** [Description] **Content:** [Content] ... ## Your Task ... ## Output Format ``` TITLE: [consolidated title] CONTENT: [consolidated content] TAGS: [comma-separated tags] OUTCOME: [success|failure] SOURCE_ATTRIBUTION: [attribution note] ```
Expected Response Format ¶
The LLM response MUST include these fields in the exact format above:
- TITLE: (required) A clear, concise title
- CONTENT: (required) The synthesized content
- TAGS: (optional) Comma-separated tags
- OUTCOME: (required) Either "success" or "failure"
- SOURCE_ATTRIBUTION: (optional) Attribution note
Example Implementation ¶
type ClaudeLLMClient struct {
client *anthropic.Client
model string
}
func (c *ClaudeLLMClient) Complete(ctx context.Context, prompt string) (string, error) {
resp, err := c.client.CreateMessage(ctx, anthropic.MessageRequest{
Model: c.model,
MaxTokens: 4096,
Messages: []anthropic.Message{
{Role: "user", Content: prompt},
},
})
if err != nil {
return "", fmt.Errorf("anthropic API error: %w", err)
}
return resp.Content[0].Text, nil
}
type OpenAILLMClient struct {
client *openai.Client
model string
}
func (c *OpenAILLMClient) Complete(ctx context.Context, prompt string) (string, error) {
resp, err := c.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
Model: c.model,
Messages: []openai.ChatCompletionMessage{
{Role: "user", Content: prompt},
},
})
if err != nil {
return "", fmt.Errorf("openai API error: %w", err)
}
return resp.Choices[0].Message.Content, nil
}
Implementation Requirements ¶
- Handle rate limiting and retries internally
- Respect context cancellation and deadlines
- Return meaningful errors for debugging
- Support prompts up to ~32K tokens (for large memory clusters)
- Use temperature ~0.3-0.5 for consistent, factual outputs
type Memory ¶
type Memory struct {
// ID is the unique memory identifier (UUID).
ID string `json:"id"`
// ProjectID identifies which project this memory belongs to.
ProjectID string `json:"project_id"`
// Title is a brief summary of the memory (e.g., "Go error handling with context").
Title string `json:"title"`
// Description provides additional context about when/why this memory is useful.
Description string `json:"description,omitempty"`
// Content is the main memory content (strategy, anti-pattern, code example).
Content string `json:"content"`
// Outcome indicates if this is a success pattern or failure anti-pattern.
Outcome Outcome `json:"outcome"`
// Confidence is a score from 0.0 to 1.0 indicating reliability.
// Higher confidence memories are prioritized in search results.
// Adjusted based on feedback and usage patterns.
Confidence float64 `json:"confidence"`
// UsageCount tracks how many times this memory has been retrieved.
UsageCount int `json:"usage_count"`
// Tags are labels for categorization (e.g., "go", "error-handling", "auth").
Tags []string `json:"tags,omitempty"`
// ConsolidationID links this memory to a consolidated memory it was merged into.
// When a memory is consolidated with others, this field is set to the ID of the
// resulting ConsolidatedMemory. The original memory is preserved for attribution.
ConsolidationID *string `json:"consolidation_id,omitempty"`
// State indicates the lifecycle state of this memory (active or archived).
// Archived memories have been consolidated into other memories but are preserved
// for attribution and traceability. They are excluded from normal searches.
State MemoryState `json:"state"`
// SessionID links this memory to the session that produced it.
// Empty for turn-granularity memories recorded individually.
SessionID string `json:"session_id,omitempty"`
// SessionDate is when the session occurred, for temporal queries.
// Nil for turn-granularity memories.
SessionDate *time.Time `json:"session_date,omitempty"`
// Granularity indicates whether this memory was stored per-turn or per-session.
// Defaults to GranularityTurn for backward compatibility.
Granularity MemoryGranularity `json:"granularity,omitempty"`
// CreatedAt is when the memory was created.
CreatedAt time.Time `json:"created_at"`
// UpdatedAt is when the memory was last modified.
UpdatedAt time.Time `json:"updated_at"`
}
Memory represents a cross-session memory in the ReasoningBank.
Memories are distilled strategies learned from agent interactions. They can represent successful patterns (outcome="success") or anti-patterns to avoid (outcome="failure").
Confidence is tracked and adjusted based on feedback signals:
- Explicit ratings from users
- Implicit success (memory helped solve a task)
- Code stability (solution didn't need rework)
func (*Memory) AdjustConfidence ¶
AdjustConfidence updates the confidence based on feedback.
For helpful feedback:
- Increases confidence by up to 0.1 (capped at 1.0)
For unhelpful feedback:
- Decreases confidence by up to 0.15 (floored at 0.0)
func (*Memory) IncrementUsage ¶
func (m *Memory) IncrementUsage()
IncrementUsage increments the usage count and updates timestamp.
type MemoryCategory ¶ added in v0.5.0
type MemoryCategory string
MemoryCategory classifies the domain of a memory's content. Categories enable gap detection (e.g., missing operational knowledge) and category-aware search boosting.
const ( // CategoryOperational covers build commands, environment setup, ports, // config files, Docker/k8s commands, and other "how to run" knowledge. CategoryOperational MemoryCategory = "operational" // CategoryArchitectural covers design decisions, patterns, interfaces, // module boundaries, and structural choices. CategoryArchitectural MemoryCategory = "architectural" // CategoryDebugging covers error fixes, bug analysis, stack traces, // root cause analysis, and workarounds. CategoryDebugging MemoryCategory = "debugging" // CategorySecurity covers vulnerabilities, CVEs, OWASP patterns, // authentication, authorization, and injection prevention. CategorySecurity MemoryCategory = "security" // CategoryFeature covers feature implementations, API endpoints, // handlers, workflows, and business logic. CategoryFeature MemoryCategory = "feature" // CategoryGeneral is the fallback when no specific category matches. CategoryGeneral MemoryCategory = "general" )
type MemoryConfidence ¶ added in v0.3.0
type MemoryConfidence struct {
// MemoryID identifies the memory.
MemoryID string `json:"memory_id"`
// Alpha represents positive evidence (starts at 1 for uniform prior).
Alpha float64 `json:"alpha"`
// Beta represents negative evidence (starts at 1 for uniform prior).
Beta float64 `json:"beta"`
}
MemoryConfidence tracks confidence for a single memory using Beta distribution.
Each memory maintains its own alpha/beta counts which are updated by weighted signals. The confidence score is the Beta distribution mean: alpha / (alpha + beta).
func NewMemoryConfidence ¶ added in v0.3.0
func NewMemoryConfidence(memoryID string) *MemoryConfidence
NewMemoryConfidence creates a new MemoryConfidence with uniform prior (1:1 = 50%).
func (*MemoryConfidence) Score ¶ added in v0.3.0
func (mc *MemoryConfidence) Score() float64
Score returns the confidence score: alpha / (alpha + beta).
This is the mean of the Beta distribution, representing our best estimate of the memory's usefulness based on accumulated evidence.
func (*MemoryConfidence) Update ¶ added in v0.3.0
func (mc *MemoryConfidence) Update(signal Signal, weights *ProjectWeights)
Update adjusts confidence based on a new signal.
The signal's contribution is weighted by the project's learned weights. Positive signals increase alpha, negative signals increase beta.
type MemoryConsolidator ¶ added in v0.3.0
type MemoryConsolidator interface {
// FindSimilarClusters detects groups of similar memories for a project.
//
// Searches all memories in the project and groups those with similarity
// scores above the threshold. Uses greedy clustering: for each memory,
// finds all similar memories above threshold, forms cluster if >=2 members.
//
// Parameters:
// - ctx: Context for cancellation and timeouts
// - projectID: Project to search for similar memories
// - threshold: Minimum similarity score (0.0-1.0, typically 0.8)
//
// Returns:
// - Slice of similarity clusters, each containing related memories
// - Error if clustering fails
FindSimilarClusters(ctx context.Context, projectID string, threshold float64) ([]SimilarityCluster, error)
// MergeCluster synthesizes a cluster of similar memories into one consolidated memory.
//
// Uses an LLM to analyze the cluster members and create a synthesized memory
// that captures their common themes and key insights. The consolidated memory
// includes source attribution and links back to the original memories.
//
// Parameters:
// - ctx: Context for cancellation and timeouts
// - cluster: Similarity cluster to merge
//
// Returns:
// - The newly created consolidated memory
// - Error if synthesis or storage fails
MergeCluster(ctx context.Context, cluster *SimilarityCluster) (*Memory, error)
// Consolidate runs the full memory consolidation process for a project.
//
// Orchestrates the complete workflow:
// 1. Find all similarity clusters above threshold
// 2. Merge each cluster into a consolidated memory
// 3. Link source memories to their consolidated versions
// 4. Return statistics about the consolidation run
//
// Parameters:
// - ctx: Context for cancellation and timeouts
// - projectID: Project to consolidate memories for
// - opts: Configuration options (threshold, limits, dry-run mode, etc.)
//
// Returns:
// - ConsolidationResult with statistics and outcomes
// - Error if consolidation fails
Consolidate(ctx context.Context, projectID string, opts ConsolidationOptions) (*ConsolidationResult, error)
}
MemoryConsolidator defines the interface for memory consolidation operations.
Implementations of this interface (such as the Distiller) are responsible for detecting similar memories, merging them into consolidated entries, and orchestrating the overall consolidation process.
The consolidation workflow:
- FindSimilarClusters detects groups of similar memories above a threshold
- MergeCluster synthesizes each cluster into a single consolidated memory
- Consolidate orchestrates the full process with configurable options
Original memories are preserved with back-links to their consolidated versions via the ConsolidationID field.
type MemoryGranularity ¶ added in v0.4.0
type MemoryGranularity string
MemoryGranularity indicates the granularity at which a memory was stored.
const ( // GranularityTurn indicates the memory was recorded from a single turn/interaction. GranularityTurn MemoryGranularity = "turn" // GranularitySession indicates the memory was consolidated from an entire session. GranularitySession MemoryGranularity = "session" )
type MemoryState ¶ added in v0.3.0
type MemoryState string
MemoryState represents the lifecycle state of a memory.
const ( // MemoryStateActive indicates the memory is actively used in searches. MemoryStateActive MemoryState = "active" // MemoryStateArchived indicates the memory has been consolidated into another memory. // Archived memories are preserved for attribution but excluded from normal searches. MemoryStateArchived MemoryState = "archived" )
type ProjectWeights ¶ added in v0.3.0
type ProjectWeights struct {
// ProjectID identifies which project these weights belong to.
ProjectID string `json:"project_id"`
// ExplicitAlpha is the success count for explicit signal predictions.
ExplicitAlpha float64 `json:"explicit_alpha"`
// ExplicitBeta is the failure count for explicit signal predictions.
ExplicitBeta float64 `json:"explicit_beta"`
// UsageAlpha is the success count for usage signal predictions.
UsageAlpha float64 `json:"usage_alpha"`
// UsageBeta is the failure count for usage signal predictions.
UsageBeta float64 `json:"usage_beta"`
// OutcomeAlpha is the success count for outcome signal predictions.
OutcomeAlpha float64 `json:"outcome_alpha"`
// OutcomeBeta is the failure count for outcome signal predictions.
OutcomeBeta float64 `json:"outcome_beta"`
}
ProjectWeights tracks learned signal weights per project using Beta distributions.
Each signal type has alpha/beta parameters that form a Beta distribution. The mean of the distribution (alpha / (alpha + beta)) represents how well that signal type predicts memory usefulness.
The system learns by observing which signals correctly predict explicit feedback: - If usage signals predict helpful feedback, UsageAlpha increases - If usage signals incorrectly predict, UsageBeta increases
Initial priors (from DESIGN.md): - Explicit: 7:3 (70% weight) - trust user feedback highly - Usage: 5:5 (50% weight) - uncertain initially - Outcome: 5:5 (50% weight) - uncertain initially
func NewProjectWeights ¶ added in v0.3.0
func NewProjectWeights(projectID string) *ProjectWeights
NewProjectWeights creates a new ProjectWeights with initial priors.
Initial priors from DESIGN.md: - Explicit 7:3 (70%) - trust user feedback - Usage/Outcome 5:5 (50%) - uncertain initially
func (*ProjectWeights) ComputeWeights ¶ added in v0.3.0
func (pw *ProjectWeights) ComputeWeights() (explicit, usage, outcome float64)
ComputeWeights returns normalized weights for each signal type.
Uses Beta distribution mean: alpha / (alpha + beta) Then normalizes so all weights sum to 1.0.
func (*ProjectWeights) LearnFromFeedback ¶ added in v0.3.0
func (pw *ProjectWeights) LearnFromFeedback(helpful bool, recentSignals []Signal)
LearnFromFeedback updates weights based on whether signals correctly predicted feedback.
When explicit feedback arrives (helpful or unhelpful), we check if other signals (usage, outcome) correctly predicted this feedback. If they did, their alpha increases. If they didn't, their beta increases.
This allows the system to learn which signal types are reliable predictors of memory usefulness for this specific project.
func (*ProjectWeights) WeightFor ¶ added in v0.3.0
func (pw *ProjectWeights) WeightFor(signalType SignalType) float64
WeightFor returns the normalized weight for a specific signal type.
type RegexCategoryClassifier ¶ added in v0.5.0
type RegexCategoryClassifier struct {
// contains filtered or unexported fields
}
RegexCategoryClassifier classifies memory content using ordered regex rules. Thread-safe: all regex patterns are compiled at construction time.
func NewRegexCategoryClassifier ¶ added in v0.5.0
func NewRegexCategoryClassifier() *RegexCategoryClassifier
NewRegexCategoryClassifier creates a classifier with built-in rules.
func (*RegexCategoryClassifier) Classify ¶ added in v0.5.0
func (c *RegexCategoryClassifier) Classify(title, content string, tags []string) (MemoryCategory, float64)
Classify returns the best-matching category and confidence for the given content. If no rule matches, returns CategoryGeneral with 0.5 confidence.
type RuleScope ¶ added in v0.5.0
type RuleScope string
RuleScope defines the hierarchy level a rule set applies to. Mirrors remediation.Scope for consistency across the codebase.
const ( // RuleScopeProject applies rules at the project level (highest priority). RuleScopeProject RuleScope = "project" // RuleScopeTeam applies rules at the team level (medium priority). RuleScopeTeam RuleScope = "team" // RuleScopeOrg applies rules at the org level (lowest custom priority). RuleScopeOrg RuleScope = "org" )
type SchedulerOption ¶ added in v0.3.0
type SchedulerOption func(*ConsolidationScheduler)
SchedulerOption configures a ConsolidationScheduler.
func WithConsolidationOptions ¶ added in v0.3.0
func WithConsolidationOptions(opts ConsolidationOptions) SchedulerOption
WithConsolidationOptions sets the consolidation options. If not set, uses default options (threshold: 0.8, dry_run: false).
func WithInterval ¶ added in v0.3.0
func WithInterval(interval time.Duration) SchedulerOption
WithInterval sets the consolidation interval. If not set, defaults to 24 hours.
func WithProjectIDs ¶ added in v0.3.0
func WithProjectIDs(projectIDs []string) SchedulerOption
WithProjectIDs sets the project IDs to consolidate. If not set, the scheduler will not consolidate any projects.
type ScoredMemory ¶ added in v0.4.0
ScoredMemory pairs a Memory with its search relevance score. The Relevance score (0.0-1.0) indicates how well the memory matches the search query based on semantic similarity, distinct from the memory's Confidence which represents reliability/trustworthiness.
type SearchMetadata ¶ added in v0.4.0
type SearchMetadata struct {
// SuggestedRefinements contains recommended search terms extracted from results
// that weren't present in the original query. These terms can help refine searches.
SuggestedRefinements []string `json:"suggested_refinements"`
// QueryCoverage indicates how well the search results matched the query (0.0-1.0).
// Higher values mean the results better matched the query intent.
// Calculated as the average relevance score of returned results.
QueryCoverage float64 `json:"query_coverage"`
// EntityMatches is the count of distinct entities found in the search results.
// Entities are extracted named items (people, concepts, technologies, etc.).
// Higher counts may indicate diverse results covering multiple aspects.
EntityMatches int `json:"entity_matches"`
}
SearchMetadata provides insights into search quality and suggestions for refinement. Used to support iterative search mode where users can progressively refine queries.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service provides cross-session memory storage and retrieval.
It stores memories in Qdrant using semantic search to surface relevant strategies based on similarity to the current task. Memories can be created explicitly via Record() or extracted asynchronously from sessions via the Distiller.
The service uses a Bayesian confidence system that learns which signals (explicit feedback, usage, outcomes) best predict memory usefulness.
func NewService ¶
func NewService(store vectorstore.Store, logger *zap.Logger, opts ...ServiceOption) (*Service, error)
NewService creates a new ReasoningBank service.
func NewServiceWithStoreProvider ¶ added in v0.3.0
func NewServiceWithStoreProvider(stores vectorstore.StoreProvider, defaultTenant string, logger *zap.Logger, opts ...ServiceOption) (*Service, error)
NewServiceWithStoreProvider creates a ReasoningBank service using StoreProvider for database-per-project isolation.
The defaultTenant is used when deriving the store path from projectID. Typically this is the git username or "default" for local-first usage.
This constructor enables the new architecture where each project gets its own chromem.DB instance at a unique filesystem path, providing physical isolation.
func (*Service) Count ¶ added in v0.3.0
Count returns the number of memories for a specific project.
func (*Service) Delete ¶
Delete removes a memory by ID.
Note: This method requires the legacy single-store configuration. When using StoreProvider (database-per-project), use DeleteByProjectID instead.
func (*Service) DeleteByProjectID ¶ added in v0.3.0
DeleteByProjectID removes a memory by ID within a specific project.
This is the preferred method when using StoreProvider (database-per-project isolation) as it directly accesses the project's store without enumeration.
func (*Service) Feedback ¶
Feedback updates a memory's confidence based on user feedback.
This method: 1. Records an explicit signal for the feedback 2. Learns which signal types predicted this feedback correctly (weight learning) 3. Recalculates the memory's confidence using the Bayesian system
FR-008: Feedback loop affecting confidence FR-005: Confidence tracking
func (*Service) FlushSession ¶ added in v0.4.0
FlushSession summarizes and persists a session's buffered turns.
This method:
- Retrieves and removes the buffered turns for the session
- Summarizes them into session-level memories via SessionSummarizer
- Stores each session memory via Record() (which bypasses buffering since the resulting memories won't have SessionID set for buffering)
Returns the IDs of created session memories. Returns nil with no error if the buffer manager is nil or no buffer exists for the session.
func (*Service) Get ¶
Get retrieves a memory by ID.
This searches across all project collections to find the memory. In practice, you'd typically know the project ID, but this provides a fallback for when you only have the memory ID.
Note: This method requires the legacy single-store configuration. When using StoreProvider (database-per-project), use GetByProjectID instead.
func (*Service) GetByProjectID ¶ added in v0.3.0
GetByProjectID retrieves a memory by ID within a specific project.
This is the preferred method when using StoreProvider (database-per-project isolation) as it directly accesses the project's store without enumeration.
func (*Service) GetMemoryVector ¶ added in v0.3.0
GetMemoryVector retrieves the embedding vector for a memory by ID.
This method re-embeds the memory content to retrieve its vector representation. The content is embedded the same way as during storage (title + content).
Note: This method requires the legacy single-store configuration. When using StoreProvider (database-per-project), use GetMemoryVectorByProjectID instead.
Returns the embedding vector or an error if the memory doesn't exist or embedder is not configured.
func (*Service) GetMemoryVectorByProjectID ¶ added in v0.3.0
func (s *Service) GetMemoryVectorByProjectID(ctx context.Context, projectID, memoryID string) ([]float32, error)
GetMemoryVectorByProjectID retrieves the embedding vector for a memory within a specific project.
This is the preferred method when using StoreProvider (database-per-project isolation) as it directly accesses the project's store without enumeration.
The method re-embeds the memory content to retrieve its vector representation. The content is embedded the same way as during storage (title + content).
Returns the embedding vector or an error if the memory doesn't exist or embedder is not configured.
func (*Service) ListMemories ¶ added in v0.3.0
func (s *Service) ListMemories(ctx context.Context, projectID string, limit, offset int) ([]Memory, error)
ListMemories retrieves all memories for a project with pagination support.
This method is used by the memory consolidation system to iterate over all memories in a project. Unlike Search, it doesn't filter by semantic similarity - it returns memories in storage order.
Parameters:
- limit: Maximum number of memories to return (0 = return all)
- offset: Number of memories to skip (for pagination)
Returns memories in storage order. For large projects, use pagination to avoid loading all memories at once.
func (*Service) Record ¶
Record creates a new memory explicitly (bypasses distillation).
Sets initial confidence to ExplicitRecordConfidence (0.8) since explicit captures are more reliable than distilled ones.
FR-007: Explicit capture via memory_record FR-002: Memory schema validation
func (*Service) RecordOutcome ¶ added in v0.3.0
func (s *Service) RecordOutcome(ctx context.Context, memoryID string, succeeded bool, sessionID string) (float64, error)
RecordOutcome records a task outcome signal for a memory.
This is called by the memory_outcome MCP tool when an agent reports whether a task succeeded after using a retrieved memory.
The outcome signal contributes to the memory's confidence score through the Bayesian confidence system. Positive outcomes increase confidence, negative outcomes decrease it based on learned weights.
Returns the new confidence score after the update.
FR-005d: Outcome reporting via memory_outcome tool
func (*Service) Search ¶
Search retrieves memories by semantic similarity to the query.
Returns memories with confidence >= MinConfidence, ordered by similarity score. Filters to only memories belonging to the specified project.
FR-003: Semantic search by similarity FR-002: Memories include required fields
func (*Service) SearchWithMetadata ¶ added in v0.4.0
func (s *Service) SearchWithMetadata(ctx context.Context, projectID, query string, limit int) ([]ScoredMemory, *SearchMetadata, error)
SearchWithMetadata returns memories with search relevance scores and metadata for iterative refinement.
In addition to ranked results, provides SearchMetadata containing:
- SuggestedRefinements: Terms extracted from results that can help refine the query
- QueryCoverage: Average relevance score indicating how well results matched the query
- EntityMatches: Count of distinct entities found in results
This metadata enables iterative search where users can progressively refine queries based on what was found and suggested.
FR-128: Iterative search mode with refinement suggestions
func (*Service) SearchWithScores ¶ added in v0.4.0
func (s *Service) SearchWithScores(ctx context.Context, projectID, query string, limit int) ([]ScoredMemory, error)
SearchWithScores returns memories with their search relevance scores. Unlike Search(), this preserves the semantic similarity score from the vector search, which is useful for displaying result quality to users.
The Relevance score (0.0-1.0) indicates how well the memory matches the query semantically, distinct from the memory's Confidence which represents reliability based on feedback.
type ServiceOption ¶ added in v0.3.0
type ServiceOption func(*Service)
ServiceOption configures a Service.
func WithDefaultTenant ¶ added in v0.3.0
func WithDefaultTenant(tenantID string) ServiceOption
WithDefaultTenant sets the default tenant ID for single-store mode. Required when using a single vectorstore instead of StoreProvider.
func WithEmbedder ¶ added in v0.3.0
func WithEmbedder(embedder vectorstore.Embedder) ServiceOption
WithEmbedder sets a custom embedder for the service. Required for GetMemoryVector to re-embed memory content.
func WithReranker ¶ added in v0.4.0
func WithReranker(r reranker.Reranker) ServiceOption
WithReranker sets an optional reranker for improving search quality. If not provided, search results are not re-ranked.
func WithSessionGranularity ¶ added in v0.4.0
func WithSessionGranularity(extractor FactExtractor, logger *zap.Logger, maxBufferedTurns int) ServiceOption
WithSessionGranularity enables session-level memory storage.
When enabled, Record() calls with a SessionID buffer turns in memory instead of storing immediately. Call FlushSession() to summarize and persist buffered turns as session-level memories.
maxBufferedTurns limits how many turns can be buffered per session (0 = unlimited).
func WithSignalStore ¶ added in v0.3.0
func WithSignalStore(ss SignalStore) ServiceOption
WithSignalStore sets a custom signal store. If not provided, an in-memory signal store is used.
type SessionBuffer ¶ added in v0.4.0
type SessionBuffer struct {
SessionID string
ProjectID string
SessionDate time.Time
Turns []TurnEntry
}
SessionBuffer holds buffered turns for a single session.
type SessionBufferManager ¶ added in v0.4.0
type SessionBufferManager struct {
// contains filtered or unexported fields
}
SessionBufferManager manages in-memory buffers for session-level memory accumulation. Thread-safe for concurrent access from multiple MCP tool calls.
func NewSessionBufferManager ¶ added in v0.4.0
func NewSessionBufferManager(maxTurns int) *SessionBufferManager
NewSessionBufferManager creates a new buffer manager. maxTurns limits the number of turns per session buffer (0 = unlimited).
func (*SessionBufferManager) ActiveSessions ¶ added in v0.4.0
func (m *SessionBufferManager) ActiveSessions() int
ActiveSessions returns the number of sessions with active buffers.
func (*SessionBufferManager) BufferTurn ¶ added in v0.4.0
func (m *SessionBufferManager) BufferTurn(projectID, sessionID string, entry TurnEntry) error
BufferTurn adds a turn entry to the session buffer. Creates the buffer if it doesn't exist yet. If maxTurns is exceeded, the oldest turn is dropped.
func (*SessionBufferManager) Count ¶ added in v0.4.0
func (m *SessionBufferManager) Count(projectID, sessionID string) int
Count returns the number of buffered turns for a session. Returns 0 if no buffer exists.
func (*SessionBufferManager) FlushBuffer ¶ added in v0.4.0
func (m *SessionBufferManager) FlushBuffer(projectID, sessionID string) *SessionBuffer
FlushBuffer removes and returns the buffer for a session. Returns nil if no buffer exists.
func (*SessionBufferManager) GetBuffer ¶ added in v0.4.0
func (m *SessionBufferManager) GetBuffer(projectID, sessionID string) *SessionBuffer
GetBuffer returns the current buffer for a session. Returns nil if no buffer exists.
type SessionOutcome ¶
type SessionOutcome string
SessionOutcome represents the overall outcome of a session.
const ( // SessionSuccess indicates the session achieved its goal. SessionSuccess SessionOutcome = "success" // SessionFailure indicates the session did not achieve its goal. SessionFailure SessionOutcome = "failure" // SessionPartial indicates partial success or mixed results. SessionPartial SessionOutcome = "partial" )
type SessionSummarizer ¶ added in v0.4.0
type SessionSummarizer struct {
// contains filtered or unexported fields
}
SessionSummarizer aggregates flushed session buffers into session-level memories.
The summarization pipeline:
- Aggregate turns by outcome (success vs failure)
- Run fact extraction on aggregated content
- Build template-based summary incorporating extracted facts
- Create Memory with session metadata (SessionID, SessionDate, Granularity)
- Return memories for storage via service.Record()
func NewSessionSummarizer ¶ added in v0.4.0
func NewSessionSummarizer(extractor FactExtractor, logger *zap.Logger) (*SessionSummarizer, error)
NewSessionSummarizer creates a new session summarizer.
func (*SessionSummarizer) Summarize ¶ added in v0.4.0
func (s *SessionSummarizer) Summarize(ctx context.Context, buf *SessionBuffer) ([]*Memory, error)
Summarize processes a flushed session buffer and produces session-level memories.
The returned memories have SessionID, SessionDate, and Granularity fields set. The caller is responsible for storing them via service.Record().
Returns one memory per outcome group (success and/or failure) found in the buffer. Returns nil with no error if the buffer is empty.
type SessionSummary ¶
type SessionSummary struct {
// SessionID uniquely identifies the session.
SessionID string
// ProjectID identifies the project this session belongs to.
ProjectID string
// Outcome is the overall session result.
Outcome SessionOutcome
// Task is a brief description of what the session was trying to accomplish.
Task string
// Approach is the strategy or method used (extracted from session).
Approach string
// Result describes what happened (success details or failure reasons).
Result string
// Tags are labels for categorization (language, domain, problem type).
Tags []string
// Duration is how long the session lasted.
Duration time.Duration
// CompletedAt is when the session ended.
CompletedAt time.Time
}
SessionSummary contains distilled information from a completed session.
type Signal ¶ added in v0.3.0
type Signal struct {
// ID is the unique signal identifier.
ID string `json:"id"`
// MemoryID is the memory this signal relates to.
MemoryID string `json:"memory_id"`
// ProjectID is the project context for this signal.
ProjectID string `json:"project_id"`
// Type identifies the signal source.
Type SignalType `json:"type"`
// Positive indicates if this was a positive signal (helpful, success).
Positive bool `json:"positive"`
// SessionID is optional session context for correlation.
SessionID string `json:"session_id,omitempty"`
// Timestamp is when this signal was recorded.
Timestamp time.Time `json:"timestamp"`
}
Signal represents a single confidence event.
Signals are recorded when: - User provides explicit feedback (memory_feedback) → SignalExplicit - Memory is retrieved in search results (memory_search) → SignalUsage - Agent reports task outcome (memory_outcome) → SignalOutcome
type SignalAggregate ¶ added in v0.3.0
type SignalAggregate struct {
// MemoryID is the memory this aggregate belongs to.
MemoryID string `json:"memory_id"`
// ProjectID is the project context.
ProjectID string `json:"project_id"`
// ExplicitPos is the count of positive explicit signals.
ExplicitPos int `json:"explicit_pos"`
// ExplicitNeg is the count of negative explicit signals.
ExplicitNeg int `json:"explicit_neg"`
// UsagePos is the count of positive usage signals.
UsagePos int `json:"usage_pos"`
// UsageNeg is the count of negative usage signals.
UsageNeg int `json:"usage_neg"`
// OutcomePos is the count of positive outcome signals.
OutcomePos int `json:"outcome_pos"`
// OutcomeNeg is the count of negative outcome signals.
OutcomeNeg int `json:"outcome_neg"`
// LastRollup is when signals were last rolled up into this aggregate.
LastRollup time.Time `json:"last_rollup"`
}
SignalAggregate stores rolled-up signal counts for data older than 30 days.
Instead of storing individual events forever, old signals are aggregated into counts per signal type per memory. This provides storage efficiency while preserving the statistical information needed for confidence calculation.
func NewSignalAggregate ¶ added in v0.3.0
func NewSignalAggregate(memoryID, projectID string) *SignalAggregate
NewSignalAggregate creates a new SignalAggregate with zero counts.
func (*SignalAggregate) AddSignal ¶ added in v0.3.0
func (agg *SignalAggregate) AddSignal(signalType SignalType, positive bool)
AddSignal increments the appropriate counter based on signal type and polarity.
type SignalStore ¶ added in v0.3.0
type SignalStore interface {
// StoreSignal persists a new signal event.
StoreSignal(ctx context.Context, signal *Signal) error
// GetRecentSignals retrieves signals within the given duration.
GetRecentSignals(ctx context.Context, memoryID string, duration time.Duration) ([]Signal, error)
// StoreAggregate persists an aggregate for a memory.
StoreAggregate(ctx context.Context, agg *SignalAggregate) error
// GetAggregate retrieves the aggregate for a memory.
GetAggregate(ctx context.Context, memoryID string) (*SignalAggregate, error)
// StoreProjectWeights persists project weights.
StoreProjectWeights(ctx context.Context, weights *ProjectWeights) error
// GetProjectWeights retrieves weights for a project.
// Returns default weights if none exist.
GetProjectWeights(ctx context.Context, projectID string) (*ProjectWeights, error)
// RollupOldSignals moves signals older than the cutoff into aggregates.
// This is called by a background worker daily.
RollupOldSignals(ctx context.Context, memoryID string, cutoff time.Duration) error
}
SignalStore defines the interface for signal persistence.
Implementations can use vectorstore, SQL database, or in-memory storage. The interface supports the hybrid storage model: - Individual signals (last 30 days) - Rolled-up aggregates (older than 30 days) - Per-project weight learning
type SignalType ¶ added in v0.3.0
type SignalType string
SignalType identifies the source of a confidence signal.
const ( // SignalExplicit is from memory_feedback tool - user rates helpful/unhelpful. SignalExplicit SignalType = "explicit" // SignalUsage is from memory_search tool - memory retrieved in search results. SignalUsage SignalType = "usage" // SignalOutcome is from memory_outcome tool - agent reports task success/failure. SignalOutcome SignalType = "outcome" )
type SimilarityCluster ¶ added in v0.3.0
type SimilarityCluster struct {
// Members contains all memories in this similarity cluster.
Members []*Memory `json:"members"`
// CentroidVector is the average embedding vector of all cluster members.
// Used to represent the cluster's semantic center.
CentroidVector []float32 `json:"centroid_vector,omitempty"`
// AverageSimilarity is the mean pairwise similarity score between cluster members.
// Range: 0.0 to 1.0, where 1.0 means all members are identical.
AverageSimilarity float64 `json:"average_similarity"`
// MinSimilarity is the lowest pairwise similarity score in the cluster.
// Indicates the cluster's cohesion - higher values mean tighter clustering.
MinSimilarity float64 `json:"min_similarity"`
}
SimilarityCluster represents a group of similar memories detected during consolidation.
The Distiller uses vector similarity search to find clusters of related memories that can be merged. Each cluster contains memories above a similarity threshold and statistics about their relationships.
type SimpleExtractor ¶ added in v0.4.0
type SimpleExtractor struct {
// contains filtered or unexported fields
}
SimpleExtractor is a rule-based fact extractor using regex patterns.
This implementation uses regex-based patterns to identify common structures:
- "I went to X" or "I attended X" -> (I, attended, X)
- "I'm thinking about X" or "I'm considering X" -> (I, considering, X)
- "I learned X" -> (I, learned, X)
Temporal references are resolved against a reference date:
- "yesterday" -> reference date - 1 day
- "today" -> reference date
- "last week" -> reference date - 7 days
- "last Monday/Tuesday/etc." -> most recent occurrence of that day
Confidence scores reflect extraction certainty:
- 1.0 for explicit, well-formed patterns
- 0.8 for clear but slightly ambiguous matches
- 0.6 for implicit or derived relations
func NewSimpleExtractor ¶ added in v0.4.0
func NewSimpleExtractor() *SimpleExtractor
NewSimpleExtractor creates a new simple fact extractor.