Documentation
¶
Overview ¶
Package hindsight: chat.go provides a thin wrapper around the cortexdb session/message API that optionally auto-triggers fact extraction.
Usage pattern:
// 1. Register extractor (once at startup)
sys.SetFactExtractor(myExtractor)
// 2. Enable auto-retain
sys.SetAutoRetain(&hindsight.AutoRetainConfig{
Enabled: true,
WindowSize: 6, // last 6 messages sent to extractor
TriggerEvery: 2, // fire after every 2 messages (one turn)
})
// 3. Use sys.AddMessage instead of db.Vector().AddMessage
sys.AddMessage(ctx, "bank-1", &core.Message{...})
// ↑ stores message normally + fires async extraction when threshold is met
Package hindsight provides a Hindsight-style AI agent memory system built on cortexdb.
It implements three core operations: Retain, Recall, and Reflect. This is a pure memory system - no LLM or HTTP dependencies. The caller is responsible for generating embeddings and extracting entities.
Package hindsight: hooks.go defines two extensibility hooks for injecting LLM-backed logic without coupling the package to any specific provider.
Hook 1 – FactExtractorFn
Plugs into RetainFromText: the caller's hook receives raw conversation messages, extracts structured facts (with pre-computed embeddings) and returns them for persistence via Retain. Typical implementations: an OpenAI/Anthropic structured-output call, a local Ollama model, or a rule-based extractor used in tests.
Hook 2 – RerankerFn
Plugs into Recall: applied after TEMPR+RRF fusion to reorder candidates using a cross-encoder or LLM relevance scorer. Errors silently fall back to the RRF-ranked order so Recall always returns a valid result.
Package hindsight provides a Hindsight-style AI agent memory system built on cortexdb.
It implements three core operations: Retain, Recall, and Reflect. This is a pure memory system - no LLM or HTTP dependencies. The caller is responsible for generating embeddings and extracting entities.
Index ¶
- Variables
- type AutoRetainConfig
- type Bank
- type Config
- type ContextRequest
- type ContextResponse
- type Disposition
- type ExtractResult
- type ExtractedFact
- type FactExtractorFn
- type Memory
- type MemoryType
- type Observation
- type ObservationType
- type RecallRequest
- type RecallResult
- type RecallStrategy
- type ReflectRequest
- type ReflectResponse
- type RerankerFn
- type System
- func (s *System) AddMessage(ctx context.Context, bankID string, msg *core.Message) error
- func (s *System) AddObservation(ctx context.Context, obs *Observation) error
- func (s *System) Close() error
- func (s *System) CreateBank(ctx context.Context, bank *Bank) error
- func (s *System) CreateSession(ctx context.Context, session *core.Session) error
- func (s *System) DB() *cortexdb.DB
- func (s *System) DeleteBank(bankID string) error
- func (s *System) GetBank(bankID string) (*Bank, bool)
- func (s *System) GetObservations(ctx context.Context, bankID string) ([]*Observation, error)
- func (s *System) Graph() *graph.GraphStore
- func (s *System) ListBanks() []*Bank
- func (s *System) Observe(ctx context.Context, req *ReflectRequest) (*ReflectResponse, error)
- func (s *System) Recall(ctx context.Context, req *RecallRequest) ([]*RecallResult, error)
- func (s *System) Reflect(ctx context.Context, req *ContextRequest) (*ContextResponse, error)
- func (s *System) Retain(ctx context.Context, mem *Memory) error
- func (s *System) RetainFromText(ctx context.Context, bankID string, messages []*core.Message) (*ExtractResult, error)
- func (s *System) RetainObservation(ctx context.Context, obs *Observation) error
- func (s *System) SetAutoRetain(cfg *AutoRetainConfig)
- func (s *System) SetFactExtractor(fn FactExtractorFn)
- func (s *System) SetReranker(fn RerankerFn)
- func (s *System) Store() core.Store
- type TemporalFilter
Constants ¶
This section is empty.
Variables ¶
var ErrNoFactExtractor = errors.New("hindsight: no FactExtractorFn configured; call SetFactExtractor first")
ErrNoFactExtractor is returned by RetainFromText when no FactExtractorFn is configured on the System.
var ErrNoReranker = errors.New("hindsight: no RerankerFn configured")
ErrNoReranker is returned by callers that programmatically check whether a reranker is active.
Functions ¶
This section is empty.
Types ¶
type AutoRetainConfig ¶
type AutoRetainConfig struct {
// Enabled turns auto-retain on or off. Defaults to false until explicitly enabled.
Enabled bool
// WindowSize is the number of recent messages passed to FactExtractorFn each
// time a trigger fires. A larger window increases context at the cost of more
// tokens sent to the extractor. Default: 6.
WindowSize int
// TriggerEvery fires extraction after every N messages that match RoleFilter.
// Set to 1 to extract after every matching message, 2 for every pair, etc.
// Default: 2 (extract after each user+assistant turn).
TriggerEvery int
// RoleFilter restricts which message roles increment the counter.
// Nil or empty means all roles count.
// Example: []string{"user"} to trigger only on user messages.
RoleFilter []string
}
AutoRetainConfig controls the automatic retain behaviour triggered by AddMessage.
type Bank ¶
type Bank struct {
// ID is the unique identifier for this bank
ID string
// Name is a human-readable name
Name string
// Disposition configures how this bank forms opinions
*Disposition
// Description provides context about this bank's purpose
Description string
// Background provides additional context for the bank
Background string
// CreatedAt is the Unix timestamp when this bank was created
CreatedAt int64
}
Bank represents a memory bank - an isolated memory space for an agent. Multiple agents can have separate memory banks with different dispositions.
func NewBankWithDisposition ¶
func NewBankWithDisposition(id, name string, disp *Disposition) *Bank
NewBankWithDisposition creates a new memory bank with custom disposition.
type Config ¶
type Config struct {
// DBPath is the path to the SQLite database
DBPath string
// VectorDim is the embedding vector dimension (0 for auto-detect)
VectorDim int
// Collection is the cortexdb collection name for memories
Collection string
}
Config configures the Hindsight system.
func DefaultConfig ¶
DefaultConfig returns a config with sensible defaults.
type ContextRequest ¶
type ContextRequest struct {
// BankID specifies which memory bank to use
BankID string
// Query is the question or topic to reflect on
Query string
// QueryVector is the embedding of the query (provided by caller)
QueryVector []float32
// Strategy configures which TEMPR strategies to use
Strategy *RecallStrategy
// TopK is the maximum memories to include in context
TopK int
// TokenBudget is the approximate max tokens for output (0 = no limit)
TokenBudget int
}
ContextRequest is the input for the Reflect operation. Reflect returns formatted context for Agent reasoning (no LLM calls).
type ContextResponse ¶
type ContextResponse struct {
// Context is the formatted text ready for LLM consumption
Context string
// Memories are the retrieved memories that formed the context
Memories []*Memory
// TokenCount is the approximate token count of the context
TokenCount int
}
ContextResponse contains the formatted context for Agent reasoning.
type Disposition ¶
type Disposition struct {
// Skepticism: 1=Trusting, 5=Skeptical
// Affects how strongly contradictory evidence is considered
Skepticism int
// Literalism: 1=Flexible interpretation, 5=Literal interpretation
// Affects how strictly facts are treated vs interpreted
Literalism int
// Empathy: 1=Detached, 5=Empathetic
// Affects how user-centric vs objective the context is
Empathy int
}
Disposition traits influence how opinions are weighted during reflection. These traits only affect context formatting, not recall operations.
func DefaultDisposition ¶
func DefaultDisposition() *Disposition
DefaultDisposition returns a disposition with balanced traits.
func (*Disposition) Validate ¶
func (d *Disposition) Validate() bool
Validate checks if disposition values are in valid range.
type ExtractResult ¶
type ExtractResult struct {
// Retained is the count of facts successfully persisted.
Retained int `json:"retained"`
// Skipped is the count of facts not persisted (missing ID or Vector).
Skipped int `json:"skipped"`
// Errors collects non-fatal per-fact errors.
Errors []error `json:"errors,omitempty"`
}
ExtractResult reports the outcome of a RetainFromText call. A non-nil Errors slice does not mean total failure; partial success is possible — the Retained counter reflects successful persistence.
func (*ExtractResult) Err ¶
func (r *ExtractResult) Err() error
Err returns a combined error if any per-fact errors occurred, or nil.
type ExtractedFact ¶
type ExtractedFact struct {
// ID is a stable identifier scoped to the bank (e.g., "user_pref_lang").
ID string `json:"id"`
// Type categorises the memory epistemically. Defaults to WorldMemory if zero.
Type MemoryType `json:"type"`
// Content is the human-readable fact string.
Content string `json:"content"`
// Vector is the embedding for Content (caller must compute this).
Vector []float32 `json:"vector,omitempty"`
// Entities are related people, places, or concepts.
Entities []string `json:"entities,omitempty"`
// Confidence is the belief strength (0–1); meaningful for OpinionMemory.
Confidence float64 `json:"confidence,omitempty"`
// Metadata is optional arbitrary key-value data to attach.
Metadata map[string]any `json:"metadata,omitempty"`
}
ExtractedFact is a single structured fact produced by FactExtractorFn. Both ID and Vector must be non-empty; facts missing either are skipped by RetainFromText and counted in ExtractResult.Skipped.
type FactExtractorFn ¶
type FactExtractorFn func(ctx context.Context, bankID string, messages []*core.Message) ([]ExtractedFact, error)
FactExtractorFn is a caller-provided hook that extracts structured facts from raw conversation messages for a given memory bank.
The implementation is responsible for:
- Parsing messages for factual claims, preferences, and entities.
- Computing vector embeddings for each extracted fact.
- Returning a deterministic ID per fact (used as the memory node key).
Example wiring (OpenAI structured output):
sys.SetFactExtractor(func(ctx context.Context, bankID string, msgs []*core.Message) ([]ExtractedFact, error) {
resp, err := openaiClient.Chat(ctx, buildPrompt(msgs))
if err != nil { return nil, err }
facts := parseResponse(resp)
for i := range facts {
facts[i].Vector, _ = embedText(ctx, facts[i].Content)
}
return facts, nil
})
type Memory ¶
type Memory struct {
// ID is the unique identifier for this memory
ID string
// BankID identifies which memory bank this memory belongs to
BankID string
// Type categorizes the memory epistemically
Type MemoryType
// Content is the original text content
Content string
// Vector is the embedding vector (provided by caller)
Vector []float32
// Entities are related people, places, concepts (provided by caller)
Entities []string
// Confidence is the belief strength (0-1, only used for Opinion type)
Confidence float64
// Metadata holds additional information like timestamps, sources, etc.
Metadata map[string]any
// CreatedAt is when this memory was stored
CreatedAt time.Time
}
Memory represents a single stored memory. The caller provides pre-processed data (vectors, entities).
type MemoryType ¶
type MemoryType string
MemoryType represents the epistemic category of a memory. Hindsight separates memories by type for clarity:
- World: Objective facts received ("Alice works at Google")
- Bank: Bank's own actions ("I recommended Python to Bob")
- Opinion: Formed beliefs with confidence ("Python is best for ML" with 0.85 confidence)
- Observation: Complex mental models derived from reflection
const ( WorldMemory MemoryType = "world" // Objective facts about the world BankMemory MemoryType = "bank" // Agent's own experiences and actions OpinionMemory MemoryType = "opinion" // Formed beliefs with confidence scores ObservationMemory MemoryType = "observation" // Mental models derived from reflection )
type Observation ¶
type Observation struct {
// ID is the unique identifier
ID string
// BankID is the memory bank this observation belongs to
BankID string
// Content is the derived insight/observation
Content string
// Vector is the embedding of the observation
Vector []float32
// SourceMemoryIDs are the memories that led to this observation
SourceMemoryIDs []string
// Confidence is the confidence in this observation (0-1)
Confidence float64
// ObservationType categorizes the observation
ObservationType ObservationType
// Reasoning explains how this observation was derived
Reasoning string
// CreatedAt is when this observation was created
CreatedAt time.Time
}
Observation represents a mental model derived from reflection. Observations connect multiple memories to form new insights.
type ObservationType ¶
type ObservationType string
ObservationType represents the category of observation.
const ( // PatternObservation: Recognized patterns across experiences PatternObservation ObservationType = "pattern" // CausalObservation: Understood cause-effect relationships CausalObservation ObservationType = "causal" // GeneralizationObservation: General rules from specific instances GeneralizationObservation ObservationType = "generalization" // PreferenceObservation: Learned user/system preferences PreferenceObservation ObservationType = "preference" // RiskObservation: Identified risks or potential issues RiskObservation ObservationType = "risk" // StrategyObservation: Effective strategies learned StrategyObservation ObservationType = "strategy" )
type RecallRequest ¶
type RecallRequest struct {
// BankID specifies which memory bank to search
BankID string
// Query is the search query text
Query string
// QueryVector is the embedding of the query (provided by caller)
QueryVector []float32
// Strategy configures which TEMPR strategies to use
Strategy *RecallStrategy
// TopK is the maximum total results to return
TopK int
}
RecallRequest is the input for the Recall operation.
type RecallResult ¶
type RecallResult struct {
*Memory
Score float64 // Combined relevance score
Strategy string // Which strategy produced this result
}
RecallResult is a scored memory returned from recall.
type RecallStrategy ¶
type RecallStrategy struct {
// Temporal filters by time range
Temporal *TemporalFilter
// Entity filters by specific entities
Entity []string
// Memory enables semantic vector search
Memory bool
// Priming enables keyword/BM25 search
Priming bool
// TopK limits results per strategy (0 = no limit)
TopK int
}
RecallStrategy configures which TEMPR strategies to use for recall. TEMPR stands for Temporal, Entity, Memory, Priming, and Recall.
func DefaultStrategy ¶
func DefaultStrategy() *RecallStrategy
DefaultStrategy returns a strategy with all TEMPR methods enabled.
type ReflectRequest ¶
type ReflectRequest struct {
// BankID is the memory bank to reflect on
BankID string
// Query is the topic or question to reflect on
Query string
// QueryVector is the embedding of the query (provided by caller)
QueryVector []float32
// Strategy configures which TEMPR strategies to use for finding relevant memories
Strategy *RecallStrategy
// TopK is the maximum memories to consider for reflection
TopK int
// ObservationTypes specifies which types of observations to generate
// If empty, generates all relevant types
ObservationTypes []ObservationType
// MinConfidence is the minimum confidence for an observation to be persisted
MinConfidence float64
}
ReflectRequest is the input for generating observations through reflection.
type ReflectResponse ¶
type ReflectResponse struct {
// Observations are the new insights generated from reflection
Observations []*Observation
// Context is the formatted context (for backward compatibility)
Context string
// SourceMemories are the memories that informed these observations
SourceMemories []*Memory
}
ReflectResponse contains the results of reflection.
type RerankerFn ¶
type RerankerFn func(ctx context.Context, query string, candidates []*RecallResult) ([]*RecallResult, error)
RerankerFn is a caller-provided hook that reranks a list of RecallResults given the original query text after TEMPR+RRF fusion.
The implementation receives the full candidate list ordered by RRF score and must return the same items (or a subset) in the desired relevance order. Returning an empty slice is treated as a failure and the original RRF order is preserved as fallback.
Example wiring (cross-encoder via a local HTTP sidecar):
sys.SetReranker(func(ctx context.Context, query string, candidates []*RecallResult) ([]*RecallResult, error) {
texts := make([]string, len(candidates))
for i, c := range candidates { texts[i] = c.Content }
scores, err := crossEncoderClient.Score(ctx, query, texts)
if err != nil { return nil, err }
sort.Slice(candidates, func(i, j int) bool { return scores[i] > scores[j] })
return candidates, nil
})
type System ¶
type System struct {
// contains filtered or unexported fields
}
System is the Hindsight memory system built on cortexdb. It provides Retain, Recall, and Reflect operations for AI agent memory.
func (*System) AddMessage ¶
AddMessage stores a chat message in the session history and — when auto-retain is enabled and the trigger threshold is reached — asynchronously extracts and persists facts into the memory bank identified by bankID.
Behaviour:
- The message is always written synchronously; this call blocks only for the database insert, not for extraction.
- Extraction is fired in a goroutine; errors are logged via slog and never surfaced to the caller (they do not affect the message write).
- The trigger counter increments for every message whose Role matches AutoRetainConfig.RoleFilter (empty = all roles).
- When the counter reaches TriggerEvery, extraction fires with the last WindowSize messages from the same session, then the counter resets.
If auto-retain is disabled or no FactExtractorFn is registered, this method is equivalent to db.Vector().AddMessage().
func (*System) AddObservation ¶
func (s *System) AddObservation(ctx context.Context, obs *Observation) error
AddObservation adds a pre-generated observation to the memory bank. This allows the caller (e.g., an LLM) to generate observations and persist them.
func (*System) Close ¶
Close waits for any in-flight background goroutines (e.g. auto-retain extraction) to finish and then closes the underlying database.
func (*System) CreateBank ¶
CreateBank creates a new memory bank and persists it to the database.
func (*System) CreateSession ¶
CreateSession is a convenience wrapper that creates a chat session in the underlying store. It is provided so callers that use hindsight as their primary entry-point don't need a separate reference to cortexdb.DB.
func (*System) DeleteBank ¶
DeleteBank removes a memory bank.
func (*System) GetBank ¶
GetBank retrieves a memory bank by ID. First checks in-memory cache, then loads from database if not found.
func (*System) GetObservations ¶
GetObservations retrieves observations for a bank.
func (*System) Graph ¶
func (s *System) Graph() *graph.GraphStore
Graph returns the underlying graph store for advanced usage.
func (*System) Observe ¶
func (s *System) Observe(ctx context.Context, req *ReflectRequest) (*ReflectResponse, error)
Observe performs deep reflection to generate observations from memories. This is the true "Reflect" operation that forms new connections and persists them. The caller can optionally provide generated observations, or the system can analyze patterns in existing memories.
func (*System) Recall ¶
func (s *System) Recall(ctx context.Context, req *RecallRequest) ([]*RecallResult, error)
Recall retrieves memories using the specified TEMPR strategies.
func (*System) Reflect ¶
func (s *System) Reflect(ctx context.Context, req *ContextRequest) (*ContextResponse, error)
Reflect generates context for Agent reasoning based on retrieved memories. This does NOT call an LLM - it formats memories for the caller to use.
func (*System) Retain ¶
Retain stores a new memory in the system. The caller must provide pre-processed data (vector, entities).
func (*System) RetainFromText ¶
func (s *System) RetainFromText(ctx context.Context, bankID string, messages []*core.Message) (*ExtractResult, error)
RetainFromText feeds raw conversation messages through the configured FactExtractorFn and persists each successfully extracted fact via Retain. This is the high-level entry-point that pairs with the FactExtractorFn hook: callers supply raw messages; knowledge extraction and storage happen here.
Partial success is supported — failed facts are recorded in ExtractResult.Errors while successfully retained facts increment ExtractResult.Retained.
Returns ErrNoFactExtractor if SetFactExtractor has not been called.
func (*System) RetainObservation ¶
func (s *System) RetainObservation(ctx context.Context, obs *Observation) error
RetainObservation stores an observation as a memory.
func (*System) SetAutoRetain ¶
func (s *System) SetAutoRetain(cfg *AutoRetainConfig)
SetAutoRetain enables automatic fact extraction on AddMessage and configures its behaviour. Requires a FactExtractorFn to be registered via SetFactExtractor before auto-retain can fire. Calling this with cfg.Enabled = false disables it.
Example:
sys.SetFactExtractor(myExtractor)
sys.SetAutoRetain(&hindsight.AutoRetainConfig{
Enabled: true,
WindowSize: 6,
TriggerEvery: 2,
})
// From now on, sys.AddMessage() triggers extraction automatically.
func (*System) SetFactExtractor ¶
func (s *System) SetFactExtractor(fn FactExtractorFn)
SetFactExtractor registers a FactExtractorFn for use by RetainFromText and auto-retain (AddMessage). Safe for concurrent use.
func (*System) SetReranker ¶
func (s *System) SetReranker(fn RerankerFn)
SetReranker registers a RerankerFn for use by Recall. Safe for concurrent use; replaces any previously registered reranker.