longterm

package
v1.10.3 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Index

Constants

View Source
const DefaultRecallChars = 6_000

DefaultRecallChars is the size of the block, about 1,500 tokens.

Variables

View Source
var ErrChangedDuringConsolidation = errors.New("memory changed while it was being consolidated")

ErrChangedDuringConsolidation is returned when memory was written while a consolidation was being computed. The consolidation is dropped, not retried: the next one starts from the new state.

View Source
var ErrNoSnapshotSupport = errors.New("the memory store cannot take snapshots, so memory is not consolidated")

ErrNoSnapshotSupport is returned by a consolidation whose store cannot copy memory first: it does not run without a way back.

View Source
var ErrRefusedConsolidation = errors.New("consolidation result refused")

ErrRefusedConsolidation is returned when the model's result was refused as unsafe to apply.

View Source
var ErrSnapshotNotFound = errors.New("memory snapshot not found")

ErrSnapshotNotFound is returned when a snapshot does not exist for the user.

Functions

func BuildTranscript

func BuildTranscript(messages []types.Message, maxChars int) string

BuildTranscript formats messages as a readable transcript, capped at maxChars by keeping the most recent content.

Types

type Consolidatable added in v1.10.1

type Consolidatable interface {
	Store

	// ListEntities returns every entity of the user, ordered by name.
	ListEntities(ctx context.Context, userID string) ([]Entity, error)

	// ReplaceEntities rewrites the entities of snapshot (what ListEntities returned) as entities, atomically: a
	// snapshot entity not in entities is removed, one in entities is created or rewritten. It returns
	// ErrChangedDuringConsolidation, changing nothing, if any snapshot entity was modified or removed since the
	// snapshot; entities created since are left alone. renames maps the name of an entity merged into another to the
	// name it now has, so that relations follow it; a relation left without both endpoints, or pointing to itself, is
	// removed.
	ReplaceEntities(ctx context.Context, userID string, snapshot []Entity, entities []EntityInput, renames map[string]string) error
}

Consolidatable is a Store that can be rewritten as a whole, which consolidation needs: the plain Store only adds. SQLiteStore implements it; a host with its own Store opts in by implementing it too.

type ConsolidationReport added in v1.10.1

type ConsolidationReport struct {
	EntitiesBefore, EntitiesAfter         int
	ObservationsBefore, ObservationsAfter int
	// SnapshotID names the copy of the memory taken before the rewrite; UndoLast, or Snapshotter.RestoreSnapshot with
	// this id, puts it back.
	SnapshotID string
}

ConsolidationReport says what a consolidation changed.

type Consolidator added in v1.10.1

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

Consolidator is the second phase of memory: extraction only ever adds, so memory fills with near-duplicates and facts that were later contradicted. The consolidator rereads a user's whole memory and rewrites it smaller: merging duplicates, keeping the newest of two statements that conflict, and dropping what no longer holds.

func NewConsolidator added in v1.10.1

func NewConsolidator(store Consolidatable, caller LLMCaller, cfg ConsolidatorConfig) *Consolidator

NewConsolidator creates a Consolidator.

func (*Consolidator) Consolidate added in v1.10.1

func (c *Consolidator) Consolidate(ctx context.Context, userID string) (*ConsolidationReport, error)

Consolidate rewrites the user's memory now, if it holds at least MinObservations. It returns a nil report when it is too small.

func (*Consolidator) MaybeConsolidate added in v1.10.1

func (c *Consolidator) MaybeConsolidate(ctx context.Context, userID string) (*ConsolidationReport, error)

MaybeConsolidate consolidates the user's memory if it is big enough and was not consolidated recently, and does nothing otherwise (a nil report). An attempt that fails still counts as recent, so a failing model is not called again at every session.

func (*Consolidator) UndoLast added in v1.10.2

func (c *Consolidator) UndoLast(ctx context.Context, userID string) error

UndoLast puts the memory back as it was before the latest consolidation. It returns ErrSnapshotNotFound when the user has no such copy (none was taken, or it was pruned).

type ConsolidatorConfig added in v1.10.1

type ConsolidatorConfig struct {
	// MinObservations is the number of observations a user must hold before consolidation is worth a model call.
	// Default: 60.
	MinObservations int

	// MinInterval is the shortest time between two consolidations of the same user, kept per process. Default: 24h.
	MinInterval time.Duration

	// MaxInputChars caps the memory sent to the model. Entities past it are left as they are. Default: 60000.
	MaxInputChars int

	// MinKeepRatio is the share of the observations a result must keep to be applied: a result that drops more is
	// taken for a model error and refused. Default: 0.3.
	MinKeepRatio float64

	// Model is the model that consolidates. Zero uses the caller's default.
	Model types.ModelIdentifier

	// Timeout is the deadline of the model call. Default: 120s.
	Timeout time.Duration
}

ConsolidatorConfig controls when and how memory is consolidated.

type Entity

type Entity struct {
	ID           string    `json:"id"`
	UserID       string    `json:"user_id"`
	Name         string    `json:"name"`
	EntityType   string    `json:"entity_type"`
	Observations []string  `json:"observations"`
	CreatedAt    time.Time `json:"created_at"`
	UpdatedAt    time.Time `json:"updated_at"`
}

Entity is a named thing with a type and a list of observations. Modelled after the MCP memory server reference (servers/src/memory).

type EntityInput

type EntityInput struct {
	Name         string   `json:"name"`
	EntityType   string   `json:"entity_type"`
	Observations []string `json:"observations"`
}

EntityInput is used for upsert operations.

func ParseLLMResponse

func ParseLLMResponse(raw string) ([]EntityInput, error)

ParseLLMResponse extracts EntityInputs from a JSON string that may be wrapped in markdown fences or surrounded by explanatory prose.

func ScreenEntities added in v1.10.1

func ScreenEntities(in []EntityInput) []EntityInput

ScreenEntities applies the limits to what a model proposed to remember: it drops an observation that holds a credential, shortens an overlong one, and keeps at most maxEntitiesPerExtraction entities with maxObservationsPerEntity observations each. An entity left with nothing to say is dropped.

type Extractor

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

Extractor runs LLM-powered extraction of entities and observations from session transcripts and persists them in the long-term memory graph.

Extraction is designed to be asynchronous and best-effort: any error is logged at DEBUG level and not surfaced to the user.

func NewExtractor

func NewExtractor(store Store, caller LLMCaller, cfg ExtractorConfig) *Extractor

NewExtractor creates an Extractor. Zero-value config fields are replaced with defaults.

func (*Extractor) Extract

func (e *Extractor) Extract(ctx context.Context, userID string, messages []types.Message) error

Extract analyses the conversation and upserts any extracted entities into the knowledge graph for userID.

It is safe to call from a goroutine: all errors are logged and the function always returns nil to the caller (extraction failures are non-fatal).

func (*Extractor) SetConsolidator added in v1.10.1

func (e *Extractor) SetConsolidator(c *Consolidator)

SetConsolidator makes the extractor consolidate the user's memory after each extraction, when the consolidator finds it due. Nil (the default) never consolidates.

type ExtractorConfig

type ExtractorConfig struct {
	// MinTurns is the minimum number of user turns required before extraction
	// runs. Short sessions rarely contain facts worth persisting.
	// Default: 3.
	MinTurns int

	// MaxTranscriptChars caps the conversation snippet fed to the LLM.
	// Keeps extraction cheap; recent turns are preferred when truncating.
	// Default: 8000.
	MaxTranscriptChars int

	// Model is the LLM model used for extraction. Haiku-class recommended.
	// When Model.Model is empty the request is sent without an explicit model
	// field, relying on the caller's default.
	Model types.ModelIdentifier

	// ExtractionTimeout is the per-call deadline passed to CreateMessage.
	// Default: 45 s.
	ExtractionTimeout time.Duration
}

ExtractorConfig controls extraction behaviour.

func DefaultExtractorConfig

func DefaultExtractorConfig() ExtractorConfig

DefaultExtractorConfig returns sensible defaults.

type Graph

type Graph struct {
	Entities  []Entity   `json:"entities"`
	Relations []Relation `json:"relations"`
}

Graph is a result set containing entities and the relations that touch them.

type LLMCaller

type LLMCaller interface {
	CreateMessage(ctx context.Context, req types.APIRequest) (*types.APIResponse, error)
}

LLMCaller is the minimal interface for calling the provider API. *providers.Client satisfies this — defined here as an interface so the extractor can be tested without a live network call.

type Lister added in v1.10.2

type Lister interface {
	ListEntities(ctx context.Context, userID string) ([]Entity, error)
}

Lister is a Store that can list a user's entities, which recall needs: the plain Store only searches by substring.

type ObservationInput

type ObservationInput struct {
	EntityName string   `json:"entity_name"`
	Contents   []string `json:"contents"`
}

ObservationInput appends observations to an existing entity.

type ObservationResult

type ObservationResult struct {
	EntityName        string   `json:"entity_name"`
	AddedObservations []string `json:"added_observations"`
}

ObservationResult reports what was actually added after deduplication.

type Recaller added in v1.10.2

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

Recaller turns a user's memory into the block of text placed in the system prompt of a new session, so the agent starts knowing what earlier conversations taught instead of having to think of searching for it.

func NewRecaller added in v1.10.2

func NewRecaller(store Lister, maxChars int) *Recaller

NewRecaller creates a Recaller. maxChars <= 0 uses DefaultRecallChars.

func (*Recaller) Recall added in v1.10.2

func (r *Recaller) Recall(ctx context.Context, userID, query string) string

Recall returns the memory block for a conversation that starts with query, or an empty string when memory holds nothing relevant. Entities of the always-recalled types come first, then those that share words with the query, best match first, until the block is full.

type Relation

type Relation struct {
	ID           string    `json:"id"`
	UserID       string    `json:"user_id"`
	From         string    `json:"from"` // entity name
	To           string    `json:"to"`   // entity name
	RelationType string    `json:"relation_type"`
	CreatedAt    time.Time `json:"created_at"`
}

Relation is a directed edge between two entities (active-voice convention).

type RelationInput

type RelationInput struct {
	From         string `json:"from"`
	To           string `json:"to"`
	RelationType string `json:"relation_type"`
}

RelationInput is used for create operations.

type SQLiteStore

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

SQLiteStore implements Store using a SQLite database. Schema is initialized via migration 20260615_012_longterm_memory.

func NewSQLiteStore

func NewSQLiteStore(db *sql.DB) *SQLiteStore

NewSQLiteStore wraps an open *sql.DB as a longterm memory Store. The caller is responsible for closing the underlying DB.

func (*SQLiteStore) AddObservations

func (s *SQLiteStore) AddObservations(ctx context.Context, userID string, inputs []ObservationInput) ([]ObservationResult, error)

AddObservations appends new observations to existing entities. Duplicate observation strings (per entity) are silently skipped.

func (*SQLiteStore) ListEntities added in v1.10.1

func (s *SQLiteStore) ListEntities(ctx context.Context, userID string) ([]Entity, error)

ListEntities returns every entity of the user, ordered by name.

func (*SQLiteStore) ListSnapshots added in v1.10.2

func (s *SQLiteStore) ListSnapshots(ctx context.Context, userID string) ([]SnapshotInfo, error)

ListSnapshots implements Snapshotter.

func (*SQLiteStore) OpenNodes

func (s *SQLiteStore) OpenNodes(ctx context.Context, userID string, names []string) (*Graph, error)

OpenNodes retrieves specific entities by exact name and their relations.

func (*SQLiteStore) ReplaceEntities added in v1.10.1

func (s *SQLiteStore) ReplaceEntities(ctx context.Context, userID string, snapshot []Entity, entities []EntityInput, renames map[string]string) error

ReplaceEntities implements Consolidatable.

func (*SQLiteStore) RestoreSnapshot added in v1.10.2

func (s *SQLiteStore) RestoreSnapshot(ctx context.Context, userID, snapshotID string) error

RestoreSnapshot implements Snapshotter.

func (*SQLiteStore) RetrieveForContext

func (s *SQLiteStore) RetrieveForContext(ctx context.Context, userID, query string, maxTokens int) (string, error)

RetrieveForContext searches memory and formats results as a Markdown block.

func (*SQLiteStore) SaveSnapshot added in v1.10.2

func (s *SQLiteStore) SaveSnapshot(ctx context.Context, userID, reason string) (SnapshotInfo, error)

SaveSnapshot implements Snapshotter.

func (*SQLiteStore) SearchNodes

func (s *SQLiteStore) SearchNodes(ctx context.Context, userID, query string) (*Graph, error)

SearchNodes performs case-insensitive substring search across entity names, types, and observation content.

func (*SQLiteStore) UpsertEntities

func (s *SQLiteStore) UpsertEntities(ctx context.Context, userID string, inputs []EntityInput) ([]Entity, error)

UpsertEntities creates entities that do not yet exist (deduplicates by name per user). Returns only the entities that were actually inserted.

type SnapshotInfo added in v1.10.2

type SnapshotInfo struct {
	ID           string    `json:"id"`
	Reason       string    `json:"reason"`
	CreatedAt    time.Time `json:"created_at"`
	Entities     int       `json:"entities"`
	Observations int       `json:"observations"`
}

SnapshotInfo describes a copy of a user's memory.

type Snapshotter added in v1.10.2

type Snapshotter interface {
	// SaveSnapshot copies the user's entities and relations and returns the copy's description.
	SaveSnapshot(ctx context.Context, userID, reason string) (SnapshotInfo, error)

	// ListSnapshots returns the user's copies, newest first.
	ListSnapshots(ctx context.Context, userID string) ([]SnapshotInfo, error)

	// RestoreSnapshot replaces the user's memory with the copy. It first copies the memory as it is, so that a restore
	// can be undone too.
	RestoreSnapshot(ctx context.Context, userID, snapshotID string) error
}

Snapshotter is a store that can copy a user's memory and put the copy back. SQLiteStore implements it; a host with its own Consolidatable store implements it too to allow consolidation, which refuses to run without a way back.

type Store

type Store interface {
	// UpsertEntities creates entities that do not exist yet (deduplicates by name).
	// Returns only the entities that were actually inserted.
	UpsertEntities(ctx context.Context, userID string, inputs []EntityInput) ([]Entity, error)

	// AddObservations appends new observations to existing entities.
	// Duplicate observation strings are silently skipped.
	AddObservations(ctx context.Context, userID string, inputs []ObservationInput) ([]ObservationResult, error)

	// SearchNodes performs case-insensitive substring search across entity names,
	// entity types, and observation content. Returns matching entities plus all
	// relations where at least one endpoint is in the result set.
	SearchNodes(ctx context.Context, userID, query string) (*Graph, error)

	// OpenNodes returns specific entities by exact name and the relations that
	// touch them (at least one endpoint in the requested set).
	OpenNodes(ctx context.Context, userID string, names []string) (*Graph, error)

	// RetrieveForContext retrieves memory relevant to the current query and
	// formats it as a Markdown block capped to approximately maxTokens.
	// Returns an empty string when there is nothing relevant.
	RetrieveForContext(ctx context.Context, userID, query string, maxTokens int) (string, error)
}

Store is the persistence interface for the long-term memory knowledge graph. Concrete persistence implementations are injected by the embedding runtime. All operations are scoped to a single user via userID.

Jump to

Keyboard shortcuts

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