Documentation
¶
Overview ¶
Package statestore provides conversation state persistence and management.
Index ¶
- Constants
- Variables
- type BulkWriter
- type ConversationState
- type InMemoryIndex
- type InMemoryIndexOption
- type IndexResult
- type LLMSummarizer
- type ListAccessor
- type ListOptions
- type MemoryStore
- func (s *MemoryStore) AppendList(ctx context.Context, id, listName string, items [][]byte) error
- func (s *MemoryStore) AppendMessages(ctx context.Context, id string, messages []types.Message) error
- func (s *MemoryStore) Close()
- func (s *MemoryStore) Delete(ctx context.Context, id string) error
- func (s *MemoryStore) Fork(ctx context.Context, sourceID, newID string) error
- func (s *MemoryStore) Len() int
- func (s *MemoryStore) List(ctx context.Context, opts ListOptions) ([]string, error)
- func (s *MemoryStore) ListLen(ctx context.Context, id, listName string) (int, error)
- func (s *MemoryStore) Load(ctx context.Context, id string) (*ConversationState, error)
- func (s *MemoryStore) LoadList(ctx context.Context, id, listName string) ([][]byte, error)
- func (s *MemoryStore) LoadMetadata(ctx context.Context, id string) (map[string]interface{}, error)
- func (s *MemoryStore) LoadRecentMessages(ctx context.Context, id string, n int) ([]types.Message, error)
- func (s *MemoryStore) LoadSummaries(ctx context.Context, id string) ([]Summary, error)
- func (s *MemoryStore) LogAppend(ctx context.Context, id string, startSeq int, messages []types.Message) (int, error)
- func (s *MemoryStore) LogLen(ctx context.Context, id string) (int, error)
- func (s *MemoryStore) LogLoad(ctx context.Context, id string, recent int) ([]types.Message, error)
- func (s *MemoryStore) MergeMetadata(ctx context.Context, id string, updates map[string]interface{}) error
- func (s *MemoryStore) MessageCount(ctx context.Context, id string) (int, error)
- func (s *MemoryStore) Save(ctx context.Context, state *ConversationState) error
- func (s *MemoryStore) SaveSummary(ctx context.Context, id string, summary Summary) error
- type MemoryStoreOption
- type MessageAppender
- type MessageIndex
- type MessageLog
- type MessageReader
- type MetadataAccessor
- type RedisOption
- type RedisStore
- func (s *RedisStore) AppendList(ctx context.Context, id, listName string, items [][]byte) error
- func (s *RedisStore) AppendMessages(ctx context.Context, id string, messages []types.Message) error
- func (s *RedisStore) Delete(ctx context.Context, id string) error
- func (s *RedisStore) Fork(ctx context.Context, sourceID, newID string) error
- func (s *RedisStore) List(ctx context.Context, opts ListOptions) ([]string, error)
- func (s *RedisStore) ListLen(ctx context.Context, id, listName string) (int, error)
- func (s *RedisStore) Load(ctx context.Context, id string) (*ConversationState, error)
- func (s *RedisStore) LoadList(ctx context.Context, id, listName string) ([][]byte, error)
- func (s *RedisStore) LoadMetadata(ctx context.Context, id string) (map[string]interface{}, error)
- func (s *RedisStore) LoadRecentMessages(ctx context.Context, id string, n int) ([]types.Message, error)
- func (s *RedisStore) LoadSummaries(ctx context.Context, id string) ([]Summary, error)
- func (s *RedisStore) LogAppend(ctx context.Context, id string, startSeq int, messages []types.Message) (int, error)
- func (s *RedisStore) LogLen(ctx context.Context, id string) (int, error)
- func (s *RedisStore) LogLoad(ctx context.Context, id string, recent int) ([]types.Message, error)
- func (s *RedisStore) MergeMetadata(ctx context.Context, id string, updates map[string]interface{}) error
- func (s *RedisStore) MessageCount(ctx context.Context, id string) (int, error)
- func (s *RedisStore) Save(ctx context.Context, state *ConversationState) error
- func (s *RedisStore) SaveSummary(ctx context.Context, id string, summary Summary) error
- type Store
- type Summarizer
- type Summary
- type SummaryAccessor
Constants ¶
const ( SortByCreatedAt = "created_at" SortByUpdatedAt = "updated_at" )
Sort field constants for ListOptions.SortBy.
const DefaultMaxEntries = 10000
DefaultMaxEntries is the default maximum number of entries a MemoryStore will hold. When the limit is reached, the least-recently-accessed entry is evicted. Use WithNoMaxEntries() to explicitly disable the entry limit.
const DefaultMaxIndexedConversations = 1000
DefaultMaxIndexedConversations is the default maximum number of conversations that can be indexed in memory before LRU eviction of the least recently used.
const DefaultTTL = 1 * time.Hour
DefaultTTL is the default time-to-live for conversation states in a MemoryStore. Entries that have not been accessed within the TTL are considered expired and eligible for eviction. Use WithNoTTL() to explicitly disable TTL expiration.
Variables ¶
var ErrInvalidID = errors.New("invalid conversation ID")
ErrInvalidID is returned when an invalid conversation ID is provided.
var ErrInvalidState = errors.New("invalid conversation state")
ErrInvalidState is returned when a conversation state is invalid.
var ErrNotFound = errors.New("conversation not found")
ErrNotFound is returned when a conversation doesn't exist in the store.
Functions ¶
This section is empty.
Types ¶
type BulkWriter ¶ added in v1.4.7
type BulkWriter interface {
// Save persists the entire conversation state, overwriting any existing
// record. Auto-creates the conversation if it doesn't exist.
Save(ctx context.Context, state *ConversationState) error
}
BulkWriter allows replacing the entire conversation state in one operation. Optional and explicitly OUT-OF-BAND for hot-path stages — it exists for admin tools, test seeders, and Session.Clear-style ops.
Pipeline stages must NEVER take a BulkWriter as a config field. Use MessageAppender, MetadataAccessor.MergeMetadata, and SummaryAccessor.SaveSummary for typed, race-safe writes instead. A reflective test in runtime/pipeline/stage enforces this invariant.
type ConversationState ¶
type ConversationState struct {
ID string // Unique conversation identifier
UserID string // User who owns this conversation
Messages []types.Message // Message history (using unified types.Message)
SystemPrompt string // System prompt for this conversation
Summaries []Summary // Compressed summaries of old turns
TokenCount int // Total tokens in messages
LastAccessedAt time.Time // Last time conversation was accessed
Metadata map[string]interface{} // Arbitrary metadata (e.g., extracted context)
}
ConversationState represents stored conversation state in the state store. This is the primary data structure for persisting and loading conversation history.
type InMemoryIndex ¶ added in v1.3.1
type InMemoryIndex struct {
// contains filtered or unexported fields
}
InMemoryIndex provides an in-memory implementation of MessageIndex using brute-force cosine similarity search. Suitable for development, testing, and conversations with up to ~10K messages.
The number of indexed conversations is bounded by maxConversations with LRU eviction to prevent unbounded memory growth.
func NewInMemoryIndex ¶ added in v1.3.1
func NewInMemoryIndex(provider providers.EmbeddingProvider, opts ...InMemoryIndexOption) *InMemoryIndex
NewInMemoryIndex creates a new in-memory message index.
func (*InMemoryIndex) Delete ¶ added in v1.3.1
func (idx *InMemoryIndex) Delete(_ context.Context, conversationID string) error
Delete removes all indexed messages for a conversation.
func (*InMemoryIndex) Index ¶ added in v1.3.1
func (idx *InMemoryIndex) Index( ctx context.Context, conversationID string, turnIndex int, message types.Message, ) error
Index adds a message to the search index by computing its embedding.
func (*InMemoryIndex) Search ¶ added in v1.3.1
func (idx *InMemoryIndex) Search(ctx context.Context, conversationID, query string, k int) ([]IndexResult, error)
Search finds the top-k messages most relevant to the query string.
type InMemoryIndexOption ¶ added in v1.3.10
type InMemoryIndexOption func(*InMemoryIndex)
InMemoryIndexOption configures an InMemoryIndex.
func WithMaxIndexedConversations ¶ added in v1.3.10
func WithMaxIndexedConversations(maxConv int) InMemoryIndexOption
WithMaxIndexedConversations sets the maximum number of conversations to index. Default is DefaultMaxIndexedConversations (1000).
type IndexResult ¶ added in v1.3.1
type IndexResult struct {
// TurnIndex is the position of the message in the conversation history.
TurnIndex int
// Message is the full message content.
Message types.Message
// Score is the relevance score (higher is more relevant, typically 0.0-1.0).
Score float64
}
IndexResult represents a single search result from the message index.
type LLMSummarizer ¶ added in v1.3.1
type LLMSummarizer struct {
// contains filtered or unexported fields
}
LLMSummarizer uses an LLM provider to compress messages into summaries.
func NewLLMSummarizer ¶ added in v1.3.1
func NewLLMSummarizer(provider providers.Provider) *LLMSummarizer
NewLLMSummarizer creates a new LLM-based summarizer. A cheaper/faster model is recommended (e.g., GPT-3.5, Claude Haiku).
type ListAccessor ¶ added in v1.4.7
type ListAccessor interface {
// AppendList appends items to the named list for the conversation.
// Items are opaque bytes (typically JSON-encoded by the caller).
// Auto-creates the conversation and the list if either is missing.
AppendList(ctx context.Context, id, listName string, items [][]byte) error
// LoadList returns all items of the named list, in append order.
// Returns (nil, nil) — not an error — when the list is empty or
// has never been written. Returns ErrNotFound only when the
// conversation itself doesn't exist.
LoadList(ctx context.Context, id, listName string) ([][]byte, error)
// ListLen returns the current length of the named list. Returns 0
// for empty/missing lists; ErrNotFound only when the conversation
// doesn't exist.
ListLen(ctx context.Context, id, listName string) (int, error)
}
ListAccessor allows storing append-only collections of opaque items (JSON-encoded by the caller) per conversation. Each list is keyed by a stable name (e.g. "workflow.history").
Stores that implement this interface persist appends incrementally — MemoryStore in a Go slice, RedisStore as a Redis list (RPUSH). This keeps per-write cost O(new entries) regardless of how long the collection has grown — the load-bearing property for long-running workflows whose History/ArtifactHistory grow without bound.
Optional. Callers (typically the SDK workflow code) type-assert; the caller surfaces a clear error when the store doesn't satisfy.
type ListOptions ¶
type ListOptions struct {
// UserID filters conversations by the user who owns them.
// If empty, all conversations are returned (subject to pagination).
UserID string
// Limit is the maximum number of conversation IDs to return.
// If 0, a default limit (e.g., 100) should be applied.
Limit int
// Offset is the number of conversations to skip (for pagination).
Offset int
// SortBy specifies the field to sort by (e.g., "created_at", "updated_at").
// If empty, implementation-specific default sorting is used.
SortBy string
// SortOrder specifies sort direction: "asc" or "desc".
// If empty, defaults to "desc" (newest first).
SortOrder string
}
ListOptions provides filtering and pagination options for listing conversations.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore provides an in-memory implementation of the Store interface. It is thread-safe and suitable for development, testing, and single-instance deployments. For distributed systems, use RedisStore or a database-backed implementation.
func NewMemoryStore ¶
func NewMemoryStore(opts ...MemoryStoreOption) *MemoryStore
NewMemoryStore creates a new in-memory state store. By default, DefaultTTL and DefaultMaxEntries are applied to prevent unbounded memory growth in server scenarios. Options can be provided to override these defaults. Use WithNoTTL() and/or WithNoMaxEntries() to explicitly disable limits.
func (*MemoryStore) AppendList ¶ added in v1.4.7
AppendList appends items to the named list for the conversation. Auto-creates the conversation entry if missing. Items are deep-copied so subsequent caller mutations don't affect stored state.
func (*MemoryStore) AppendMessages ¶ added in v1.3.1
func (s *MemoryStore) AppendMessages(ctx context.Context, id string, messages []types.Message) error
AppendMessages appends messages to the conversation's message history. If the entry is expired, it is treated as non-existent and a new state is created.
func (*MemoryStore) Close ¶ added in v1.3.2
func (s *MemoryStore) Close()
Close stops the background eviction goroutine, if running. It is safe to call Close multiple times.
func (*MemoryStore) Delete ¶
func (s *MemoryStore) Delete(ctx context.Context, id string) error
Delete removes a conversation state by ID.
func (*MemoryStore) Fork ¶ added in v1.1.6
func (s *MemoryStore) Fork(ctx context.Context, sourceID, newID string) error
Fork creates a copy of an existing conversation state with a new ID.
func (*MemoryStore) Len ¶ added in v1.3.2
func (s *MemoryStore) Len() int
Len returns the number of entries currently in the store, including expired entries that have not yet been evicted. This is primarily useful for testing.
func (*MemoryStore) List ¶
func (s *MemoryStore) List(ctx context.Context, opts ListOptions) ([]string, error)
List returns conversation IDs matching the given criteria. Expired entries are excluded from results.
func (*MemoryStore) ListLen ¶ added in v1.4.7
ListLen returns the length of the named list. ErrNotFound only when the conversation itself doesn't exist.
func (*MemoryStore) Load ¶
func (s *MemoryStore) Load(ctx context.Context, id string) (*ConversationState, error)
Load retrieves a conversation state by ID. Returns a deep copy to prevent external mutations. Expired entries are lazily evicted on access and return ErrNotFound.
func (*MemoryStore) LoadList ¶ added in v1.4.7
LoadList returns all items of the named list, deep-copied. Returns (nil, nil) when the list is empty or never written. Returns ErrNotFound when the conversation itself doesn't exist.
func (*MemoryStore) LoadMetadata ¶ added in v1.3.10
LoadMetadata returns just the metadata map for the given conversation. This avoids the cost of deep-copying the entire message history, making it significantly cheaper than Load() for callers that only need metadata. Expired entries are lazily evicted and return ErrNotFound.
func (*MemoryStore) LoadRecentMessages ¶ added in v1.3.1
func (s *MemoryStore) LoadRecentMessages(ctx context.Context, id string, n int) ([]types.Message, error)
LoadRecentMessages returns the last n messages for the given conversation. Expired entries return ErrNotFound.
func (*MemoryStore) LoadSummaries ¶ added in v1.3.1
LoadSummaries returns all summaries for the given conversation. Uses a read lock since summaries contain only value types and cloning is a simple slice copy that doesn't benefit from write-lock protection. Expired entries return nil but are not eagerly evicted under RLock.
func (*MemoryStore) LogAppend ¶ added in v1.3.24
func (s *MemoryStore) LogAppend(ctx context.Context, id string, startSeq int, messages []types.Message) (int, error)
LogAppend appends messages with sequence-based idempotent deduplication. Messages before startSeq are already persisted and are skipped.
func (*MemoryStore) LogLen ¶ added in v1.3.24
LogLen returns the total message count for the conversation. Returns 0 if the conversation doesn't exist.
func (*MemoryStore) LogLoad ¶ added in v1.3.24
LogLoad returns messages for the conversation. If recent > 0, returns only the last N messages. Returns an empty slice if the conversation doesn't exist.
func (*MemoryStore) MergeMetadata ¶ added in v1.4.7
func (s *MemoryStore) MergeMetadata(ctx context.Context, id string, updates map[string]interface{}) error
MergeMetadata atomically merges the supplied keys into the conversation's Metadata map. Auto-creates the conversation if it doesn't exist.
func (*MemoryStore) MessageCount ¶ added in v1.3.1
MessageCount returns the total number of messages in the conversation. Uses a read lock since it only needs the message count, not a mutable reference. Expired entries return ErrNotFound but are not eagerly evicted under RLock; they will be cleaned up on the next write operation or background eviction.
func (*MemoryStore) Save ¶
func (s *MemoryStore) Save(ctx context.Context, state *ConversationState) error
Save persists a conversation state. If it already exists, it will be updated. When a max entries limit is set and the store is full, the least-recently-accessed entry is evicted to make room.
func (*MemoryStore) SaveSummary ¶ added in v1.3.1
SaveSummary appends a summary to the conversation's summary list. Auto-creates the conversation if it doesn't exist.
type MemoryStoreOption ¶ added in v1.3.2
type MemoryStoreOption func(*MemoryStore)
MemoryStoreOption configures optional behavior for MemoryStore.
func WithMemoryEvictionInterval ¶ added in v1.3.2
func WithMemoryEvictionInterval(d time.Duration) MemoryStoreOption
WithMemoryEvictionInterval sets the interval for the background cleanup goroutine that removes expired entries. If zero, no background cleanup runs and eviction happens only lazily on access. Requires a non-zero TTL to have any effect.
func WithMemoryMaxEntries ¶ added in v1.3.2
func WithMemoryMaxEntries(n int) MemoryStoreOption
WithMemoryMaxEntries sets the maximum number of entries the store will hold. When the limit is reached, the least-recently-accessed entry is evicted. A zero or negative value disables the limit. By default, DefaultMaxEntries is applied. Use WithNoMaxEntries() as an explicit, self-documenting way to disable the entry limit.
func WithMemoryTTL ¶ added in v1.3.2
func WithMemoryTTL(ttl time.Duration) MemoryStoreOption
WithMemoryTTL sets the time-to-live for conversation states. Entries that have not been accessed within the TTL are considered expired and eligible for eviction. A zero or negative TTL disables expiration. By default, DefaultTTL is applied. Use WithNoTTL() as an explicit, self-documenting way to disable TTL expiration.
func WithNoMaxEntries ¶ added in v1.3.10
func WithNoMaxEntries() MemoryStoreOption
WithNoMaxEntries explicitly disables the max entries limit so the store can grow unbounded. This overrides the DefaultMaxEntries that is otherwise applied automatically.
func WithNoTTL ¶ added in v1.3.10
func WithNoTTL() MemoryStoreOption
WithNoTTL explicitly disables TTL expiration so entries never expire. This overrides the DefaultTTL that is otherwise applied automatically.
type MessageAppender ¶ added in v1.3.1
type MessageAppender interface {
// AppendMessages appends messages to the conversation's message history.
// Auto-creates the conversation if it doesn't exist.
AppendMessages(ctx context.Context, id string, messages []types.Message) error
}
MessageAppender allows appending messages without a full load+replace+save cycle. This is an optional interface — stores that implement it enable incremental saves. Pipeline stages type-assert for this interface and fall back to BulkWriter when unavailable.
type MessageIndex ¶ added in v1.3.1
type MessageIndex interface {
// Index adds a message to the search index for the given conversation.
// turnIndex is the position of the message in the conversation history.
Index(ctx context.Context, conversationID string, turnIndex int, message types.Message) error
// Search finds the top-k messages most relevant to the query string.
// Results are ordered by descending relevance score.
Search(ctx context.Context, conversationID string, query string, k int) ([]IndexResult, error)
// Delete removes all indexed messages for a conversation.
Delete(ctx context.Context, conversationID string) error
}
MessageIndex provides semantic search over conversation messages. Implementations can use embedding-based vector search or other similarity methods to find messages relevant to a given query.
type MessageLog ¶ added in v1.3.24
type MessageLog interface {
// LogAppend appends messages starting at the given sequence number.
// If startSeq < current message count, the first (current - startSeq) input
// messages are skipped (idempotent deduplication). Returns the new total count.
LogAppend(ctx context.Context, id string, startSeq int, messages []types.Message) (int, error)
// LogLoad returns messages for the conversation.
// If recent > 0, returns only the last N messages.
// If recent == 0, returns all messages.
// Returns an empty slice (not an error) if the conversation doesn't exist.
LogLoad(ctx context.Context, id string, recent int) ([]types.Message, error)
// LogLen returns the total message count for the conversation.
// Returns 0 (not an error) if the conversation doesn't exist.
LogLen(ctx context.Context, id string) (int, error)
}
MessageLog provides sequence-based message persistence with idempotent append. Stores that implement this interface enable per-round write-through during tool loops, so messages survive process crashes without waiting for the end-of-pipeline save stage.
The sequence number is the message index in the conversation (0-based). AppendMessages with startSeq < current length skips already-persisted messages, making retries safe.
Implementations are not required to be safe against concurrent LogAppend calls targeting the same conversation id. Callers must serialize writes per conversation — the ProviderStage tool loop already does this via per-conversation single-threading.
type MessageReader ¶ added in v1.3.1
type MessageReader interface {
// LoadRecentMessages returns the last n messages for the given conversation.
// Returns ErrNotFound if the conversation doesn't exist.
LoadRecentMessages(ctx context.Context, id string, n int) ([]types.Message, error)
// MessageCount returns the total number of messages in the conversation.
// Returns ErrNotFound if the conversation doesn't exist.
MessageCount(ctx context.Context, id string) (int, error)
}
MessageReader allows loading a subset of messages without full state deserialization. This is an optional interface — stores that implement it enable efficient partial reads. Pipeline stages type-assert for this interface and fall back to Store.Load when unavailable.
type MetadataAccessor ¶ added in v1.3.10
type MetadataAccessor interface {
// LoadMetadata returns just the metadata map for the given conversation.
// Returns ErrNotFound if the conversation doesn't exist.
// The returned map is a deep copy safe for mutation by the caller.
LoadMetadata(ctx context.Context, id string) (map[string]interface{}, error)
// MergeMetadata atomically merges the supplied keys into the
// conversation's Metadata map. Existing keys are overwritten, other
// fields (Messages, Summaries, etc.) are untouched. Auto-creates the
// conversation if it doesn't exist.
MergeMetadata(ctx context.Context, id string, updates map[string]interface{}) error
}
MetadataAccessor allows reading and writing metadata without loading the full state. This is an optional interface — stores that implement it enable efficient metadata operations without the cost of deep-copying the message history. Pipeline stages type-assert for this interface; reads fall back to Store.Load when LoadMetadata is unavailable, and writes fall back to BulkWriter when MergeMetadata is unavailable.
type RedisOption ¶
type RedisOption func(*RedisStore)
RedisOption configures a RedisStore.
func WithPrefix ¶
func WithPrefix(prefix string) RedisOption
WithPrefix sets the key prefix for Redis keys. Default is "promptkit".
func WithTTL ¶
func WithTTL(ttl time.Duration) RedisOption
WithTTL sets the time-to-live for conversation states. After this duration, conversations will be automatically deleted. Default is 24 hours. Set to 0 for no expiration.
type RedisStore ¶
type RedisStore struct {
// contains filtered or unexported fields
}
RedisStore provides a Redis-backed implementation of the Store interface. It uses JSON serialization for state storage and supports automatic TTL-based cleanup. This implementation is suitable for distributed systems and production deployments.
func NewRedisStore ¶
func NewRedisStore(client *redis.Client, opts ...RedisOption) *RedisStore
NewRedisStore creates a new Redis-backed state store.
Example:
store := NewRedisStore(
redis.NewClient(&redis.Options{Addr: "localhost:6379"}),
WithTTL(24 * time.Hour),
WithPrefix("myapp"),
)
func (*RedisStore) AppendList ¶ added in v1.4.7
AppendList appends opaque items to the named per-conversation list using RPUSH. The list name is also recorded in a per-conversation index set (SADD) so Delete can enumerate list keys without a SCAN. All commands run in a single pipeline; TTLs are refreshed on the list, the index set, and the meta key so that long-running workflows that only append list entries don't expire prematurely.
Auto-creates the conversation if it doesn't exist — meta is touched on the next interaction (or by an explicit Save). No Save is required.
func (*RedisStore) AppendMessages ¶ added in v1.3.1
AppendMessages appends messages to the conversation's message list using RPUSH. Uses Redis pipelining to batch the RPUSH, EXPIRE, and meta update in a single round-trip.
func (*RedisStore) Delete ¶
func (s *RedisStore) Delete(ctx context.Context, id string) error
Delete removes a conversation state from Redis. Removes both decomposed keys (meta, messages, summaries) and legacy monolithic key. Uses a pipeline to batch all DEL commands and optional user index cleanup.
func (*RedisStore) Fork ¶ added in v1.1.6
func (s *RedisStore) Fork(ctx context.Context, sourceID, newID string) error
Fork creates a copy of an existing conversation state with a new ID.
func (*RedisStore) List ¶
func (s *RedisStore) List(ctx context.Context, opts ListOptions) ([]string, error)
List returns conversation IDs matching the given criteria.
func (*RedisStore) ListLen ¶ added in v1.4.7
ListLen returns the current length of the named list. Returns 0 for empty/missing lists; ErrNotFound only when the conversation itself doesn't exist.
func (*RedisStore) Load ¶
func (s *RedisStore) Load(ctx context.Context, id string) (*ConversationState, error)
Load retrieves a conversation state by ID from Redis. Tries the decomposed format (meta key + messages list + summaries list) first, then falls back to the legacy monolithic JSON string for backward compatibility.
func (*RedisStore) LoadList ¶ added in v1.4.7
LoadList returns all items of the named list in append order. Returns (nil, nil) when the list is empty or has never been written and the conversation exists. Returns ErrNotFound only when the conversation itself doesn't exist (distinguished by an EXISTS check on the meta key).
func (*RedisStore) LoadMetadata ¶ added in v1.3.10
LoadMetadata returns the user metadata map for the given conversation by reading the dedicated metadata hash. Returns ErrNotFound when the conversation doesn't exist (matches MemoryStore.LoadMetadata). Returns an empty (or nil) map when the conversation exists but has no metadata.
func (*RedisStore) LoadRecentMessages ¶ added in v1.3.1
func (s *RedisStore) LoadRecentMessages(ctx context.Context, id string, n int) ([]types.Message, error)
LoadRecentMessages returns the last n messages using LRANGE on the messages list. Falls back to loading from the monolithic key if the list doesn't exist.
func (*RedisStore) LoadSummaries ¶ added in v1.3.1
LoadSummaries returns all summaries for the conversation.
func (*RedisStore) LogAppend ¶ added in v1.4.12
func (s *RedisStore) LogAppend( ctx context.Context, id string, startSeq int, messages []types.Message, ) (int, error)
LogAppend appends messages with sequence-based idempotent deduplication. The on-disk layout matches what Save writes (a Redis list at messagesKey(id)) so messages persisted via the tool-loop write-through path are loaded back transparently by Load. A minimal meta stub is written via SETNX so a subsequent Load(id) does not return ErrNotFound after a crash recovery before the end-of-turn Save has run; the next Save overwrites it.
func (*RedisStore) LogLen ¶ added in v1.4.12
LogLen returns the message count for the conversation, or 0 if absent.
func (*RedisStore) LogLoad ¶ added in v1.4.12
LogLoad returns messages for the conversation. Empty (not an error) when the conversation doesn't exist. With recent > 0, returns only the last N.
func (*RedisStore) MergeMetadata ¶ added in v1.4.7
func (s *RedisStore) MergeMetadata(ctx context.Context, id string, updates map[string]interface{}) error
MergeMetadata writes the supplied keys into the conversation's metadata hash via a single HMSET. Each Redis hash field write is server-atomic, so concurrent MergeMetadata calls on the same conversation just work — no WATCH/MULTI/EXEC, no retry loop. Updates the meta-key TTL too so metadata-only writes count as activity for retention purposes.
func (*RedisStore) MessageCount ¶ added in v1.3.1
MessageCount returns the total number of messages. Falls back to loading from the monolithic key if the list doesn't exist.
func (*RedisStore) Save ¶
func (s *RedisStore) Save(ctx context.Context, state *ConversationState) error
Save persists a conversation state to Redis using decomposed keys. Metadata is stored in a meta key, messages in a Redis list, and summaries in a separate list. Messages use append-only delta writes: only new messages (beyond what is already stored) are RPUSHed, avoiding the cost of DEL+RPUSH for the entire list on every save. If the message count in Redis exceeds the local count (e.g., external truncation), a full rewrite is performed.
func (*RedisStore) SaveSummary ¶ added in v1.3.1
SaveSummary appends a summary to the conversation's summary list. Uses a pipeline to batch RPUSH and EXPIRE into a single round-trip.
type Store ¶
type Store interface {
// Load retrieves conversation state by ID
Load(ctx context.Context, id string) (*ConversationState, error)
// Fork creates a copy of an existing conversation state with a new ID
// The original conversation is left unchanged. Returns ErrNotFound if sourceID doesn't exist.
Fork(ctx context.Context, sourceID, newID string) error
}
Store defines the interface for persistent conversation state storage.
Store is read-shaped: it deliberately does not include bulk-write methods. Callers needing to persist state should use the typed write interfaces (MessageAppender, MetadataAccessor.MergeMetadata, SummaryAccessor.SaveSummary). Admin/seed paths that need to replace whole state should type-assert for BulkWriter; hot-path pipeline stages must not.
type Summarizer ¶ added in v1.3.1
type Summarizer interface {
// Summarize compresses the given messages into a concise summary.
Summarize(ctx context.Context, messages []types.Message) (string, error)
}
Summarizer compresses a batch of messages into a summary string. Implementations may use LLM providers, extractive methods, or other compression strategies.
type Summary ¶
type Summary struct {
StartTurn int // First turn included in this summary
EndTurn int // Last turn included in this summary
Content string // Summarized content
TokenCount int // Token count of the summary
CreatedAt time.Time // When this summary was created
}
Summary represents a compressed version of conversation turns. Used to maintain context while reducing token count for older conversations.
type SummaryAccessor ¶ added in v1.3.1
type SummaryAccessor interface {
// LoadSummaries returns all summaries for the given conversation.
// Returns nil (not an error) if no summaries exist.
LoadSummaries(ctx context.Context, id string) ([]Summary, error)
// SaveSummary appends a summary to the conversation's summary list.
// Auto-creates the conversation if it doesn't exist.
SaveSummary(ctx context.Context, id string, summary Summary) error
}
SummaryAccessor allows reading and writing summaries independently of the full state. This is an optional interface for stores that support efficient summary operations.