hindsight

package
v2.7.0 Latest Latest
Warning

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

Go to latest
Published: Mar 5, 2026 License: MIT Imports: 11 Imported by: 1

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

Constants

This section is empty.

Variables

View Source
var ErrNoFactExtractor = errors.New("hindsight: no FactExtractorFn configured; call SetFactExtractor first")

ErrNoFactExtractor is returned by RetainFromText when no FactExtractorFn is configured on the System.

View Source
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 NewBank

func NewBank(id, name string) *Bank

NewBank creates a new memory bank with default disposition.

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

func DefaultConfig(dbPath string) *Config

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 New

func New(cfg *Config) (*System, error)

New creates a new Hindsight memory system.

func (*System) AddMessage

func (s *System) AddMessage(ctx context.Context, bankID string, msg *core.Message) error

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

func (s *System) Close() error

Close waits for any in-flight background goroutines (e.g. auto-retain extraction) to finish and then closes the underlying database.

func (*System) CreateBank

func (s *System) CreateBank(ctx context.Context, bank *Bank) error

CreateBank creates a new memory bank and persists it to the database.

func (*System) CreateSession

func (s *System) CreateSession(ctx context.Context, session *core.Session) error

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) DB

func (s *System) DB() *cortexdb.DB

DB returns the underlying cortexdb database for advanced usage.

func (*System) DeleteBank

func (s *System) DeleteBank(bankID string) error

DeleteBank removes a memory bank.

func (*System) GetBank

func (s *System) GetBank(bankID string) (*Bank, bool)

GetBank retrieves a memory bank by ID. First checks in-memory cache, then loads from database if not found.

func (*System) GetObservations

func (s *System) GetObservations(ctx context.Context, bankID string) ([]*Observation, error)

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) ListBanks

func (s *System) ListBanks() []*Bank

ListBanks returns all registered banks.

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

func (s *System) Retain(ctx context.Context, mem *Memory) error

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.

func (*System) Store

func (s *System) Store() core.Store

Store returns the underlying core store for advanced usage.

type TemporalFilter

type TemporalFilter struct {
	Start *time.Time // Inclusive start time
	End   *time.Time // Inclusive end time
}

TemporalFilter specifies a time range for temporal recall.

Jump to

Keyboard shortcuts

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