Documentation
¶
Overview ¶
Package store provides data access interfaces and implementations.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrConversationNotFound is returned when a conversation does not exist. ErrConversationNotFound = errors.New("conversation not found") // ErrMessageNotFound is returned when a message does not exist. ErrMessageNotFound = errors.New("message not found") // ErrUpdateNotFound is returned when an update does not exist. ErrUpdateNotFound = errors.New("update not found") // ErrConversationMemberNotFound is returned when a conversation member does not exist. ErrConversationMemberNotFound = errors.New("conversation member not found") // ErrInvalidDialect is returned when an unsupported database dialect is specified. ErrInvalidDialect = errors.New("invalid database dialect") // ErrToolExecutionNotFound is returned when a tool execution result does not exist. ErrToolExecutionNotFound = errors.New("tool execution not found") // ErrDuplicateIdempotencyKey is returned when a message with the same idempotency key already exists. ErrDuplicateIdempotencyKey = errors.New("duplicate idempotency key") )
Sentinel errors for store operations.
These errors are used with fmt.Errorf("%w", ErrXxx) to preserve the error chain. Callers use errors.Is(err, ErrConversationNotFound) to check error types.
Functions ¶
This section is empty.
Types ¶
type Conversation ¶
type Conversation struct {
ID string `gorm:"primaryKey;type:varchar(36)" json:"id"`
Owner string `gorm:"index;type:varchar(255)" json:"owner"`
Title string `gorm:"type:varchar(255);default:''" json:"title"`
LastProcessedMessageID uint64 `gorm:"column:last_processed_message_id;default:0" json:"last_processed_message_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
LastMessageAt time.Time `gorm:"column:last_message_at;type:TIMESTAMP;not null;default:CURRENT_TIMESTAMP" json:"last_message_at"`
}
Conversation represents a logical conversation thread with message history and checkpoint data for interruption recovery.
func (Conversation) TableName ¶
func (Conversation) TableName() string
TableName returns the GORM table name for Conversation.
type ConversationCheckpoint ¶
type ConversationCheckpoint struct {
ConversationID string `gorm:"column:conversation_id;primaryKey;type:varchar(36)" json:"conversation_id"`
Data []byte `gorm:"column:data" json:"-"`
Version uint64 `gorm:"column:version;default:1" json:"version"`
SkippedMessageIDs string `gorm:"column:skipped_message_ids;type:text" json:"-"`
UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
}
ConversationCheckpoint stores Agent checkpoint data for interruption recovery. Unlike Conversation, checkpoints are not soft-deleted and are permanently retained.
func (ConversationCheckpoint) TableName ¶
func (ConversationCheckpoint) TableName() string
TableName returns the GORM table name for ConversationCheckpoint.
type ConversationMember ¶
type ConversationMember struct {
ID string `gorm:"primaryKey;type:varchar(36)" json:"id"`
ConversationID string `gorm:"column:conversation_id;index:idx_member_conv_user,unique;type:varchar(36)" json:"conversation_id"`
UserID string `gorm:"column:user_id;index:idx_member_conv_user,unique;type:varchar(255)" json:"user_id"`
ReadMsgID uint64 `gorm:"column:read_msg_id;default:0" json:"read_msg_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
ConversationMember tracks the read watermark (last read message ID) for a user within a conversation.
func (ConversationMember) TableName ¶
func (ConversationMember) TableName() string
TableName returns the GORM table name for ConversationMember.
type ConversationWithMeta ¶
type ConversationWithMeta struct {
ID string
Title string
CreatedAt time.Time
LastMessageAt time.Time
ReadMsgID uint64
LastMessageID uint64
}
ConversationWithMeta is a read-only projection for list_conversations, combining conversation fields with member read watermark.
type IntentStat ¶
type IntentStat struct {
ID string `gorm:"primaryKey;type:varchar(36)" json:"id"`
ConversationID string `gorm:"column:conversation_id;type:varchar(36)" json:"conversation_id"`
Status string `gorm:"type:varchar(20)" json:"status"`
Duration int `json:"duration"`
IterationCount int `json:"iteration_count"`
ToolCount int `json:"tool_count"`
ErrorMessage string `gorm:"type:text" json:"error_message"`
CreatedAt time.Time `json:"created_at"`
}
IntentStat records execution statistics for an intent processing run.
func (IntentStat) TableName ¶
func (IntentStat) TableName() string
TableName returns the GORM table name for IntentStat.
type LLMStat ¶
type LLMStat struct {
ID string `gorm:"primaryKey;type:varchar(36)" json:"id"`
ConversationID string `gorm:"column:conversation_id;type:varchar(36)" json:"conversation_id"`
MessageID string `gorm:"column:message_id;type:varchar(36)" json:"message_id"`
Model string `gorm:"type:varchar(100)" json:"model"`
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
Duration int `json:"duration"`
Status string `gorm:"type:varchar(20)" json:"status"`
ErrorMessage string `gorm:"type:text" json:"error_message"`
CreatedAt time.Time `json:"created_at"`
}
LLMStat records latency and error statistics for an LLM call.
type Message ¶
type Message struct {
ID string `gorm:"primaryKey;type:varchar(36)" json:"id"`
MessageID uint64 `gorm:"column:message_id;index:idx_msg_conv,unique" json:"message_id"`
ConversationID string `gorm:"column:conversation_id;index:idx_msg_conv,unique;type:varchar(36)" json:"conversation_id"`
Role string `gorm:"type:varchar(20)" json:"role"`
Content string `gorm:"type:text" json:"content"`
Compressed bool `gorm:"default:false" json:"compressed"`
IdempotencyKey string `gorm:"column:idempotency_key;type:varchar(36);default:''" json:"idempotency_key"`
CreatedAt time.Time `json:"created_at"`
}
Message represents a single message within a conversation.
type ScopePair ¶
ScopePair represents a distinct (scope, scope_id) combination from the updates table.
type StatisticsStore ¶
type StatisticsStore interface {
// RecordTokenUsage persists token consumption for an LLM call.
RecordTokenUsage(ctx context.Context, stat *TokenStat) error
// RecordIntentStat persists execution statistics for an intent processing run.
RecordIntentStat(ctx context.Context, stat *IntentStat) error
// RecordToolStat persists execution statistics for a tool call.
RecordToolStat(ctx context.Context, stat *ToolStat) error
// RecordLLMStat persists latency and error statistics for an LLM call.
RecordLLMStat(ctx context.Context, stat *LLMStat) error
}
StatisticsStore defines the persistence layer for agent usage statistics including token counts, intent counts, tool call counts, and LLM error tracking.
Write failures in statistics must not affect the main business flow. Implementations should log errors and return nil, or return errors that callers can safely ignore.
type Store ¶
type Store interface {
// CreateConversation persists a new conversation.
CreateConversation(ctx context.Context, conv *Conversation) error
// GetConversation retrieves a conversation by its ID.
GetConversation(ctx context.Context, id string) (*Conversation, error)
// ListConversations returns a paginated list of conversations for the given owner.
ListConversations(ctx context.Context, owner string, page, size int) ([]*Conversation, error)
// ListConversationsByUser returns cursor-paginated conversations for a user,
// joined with their conversation_members read watermark.
ListConversationsByUser(ctx context.Context, userID string, cursor string, limit int) ([]*ConversationWithMeta, error)
// UpdateConversation modifies an existing conversation.
UpdateConversation(ctx context.Context, conv *Conversation) error
// UpdateLastProcessedMessageID atomically updates last_processed_message_id
// only when the new value is strictly greater than the current value.
// This provides a compare-and-swap (CAS) pattern that prevents race conditions
// when multiple intent handlers process messages concurrently.
// Returns nil if the update was applied (new value > current value),
// or nil with rowsAffected=0 if already processed (skip).
UpdateLastProcessedMessageID(ctx context.Context, conversationID string, messageID uint64) error
// DeleteConversation removes a conversation and its associated data.
DeleteConversation(ctx context.Context, id string) error
// CreateMessage persists a new message within a conversation.
CreateMessage(ctx context.Context, msg *Message) error
// GetMessage retrieves a single message by conversation ID and message sequence number.
GetMessage(ctx context.Context, conversationID string, messageID uint64) (*Message, error)
// ListMessages returns messages within the given sequence range [from, to).
ListMessages(ctx context.Context, conversationID string, from, to uint64) ([]*Message, error)
// UpdateMessage modifies the content of an existing message.
UpdateMessage(ctx context.Context, msg *Message) error
// ListActiveMessages returns uncompressed messages for a conversation,
// sorted by message_id ASC. Used for loading agent context after compression.
ListActiveMessages(ctx context.Context, conversationID string, limit int) ([]*Message, error)
// MarkMessagesCompressed bulk-updates compressed=true for messages <= maxMessageID.
MarkMessagesCompressed(ctx context.Context, conversationID string, maxMessageID uint64) error
// GetMessageByIdempotencyKey retrieves a message by its idempotency key.
// Returns ErrMessageNotFound if no message matches.
GetMessageByIdempotencyKey(ctx context.Context, key string) (*Message, error)
// CreateUpdate persists a new update record.
CreateUpdate(ctx context.Context, update *Update) error
// ListUpdates returns updates within the given sequence range [fromSeq, toSeq] inclusive.
ListUpdates(ctx context.Context, scope, scopeID string, fromSeq, toSeq uint64) ([]*Update, error)
// MaxSeq returns the maximum sequence number for a given scope and scopeID.
// Returns 0 if no records exist.
MaxSeq(ctx context.Context, scope, scopeID string) (uint64, error)
// DistinctScopePairs returns all distinct (scope, scopeID) pairs from the updates table.
// Used during startup to sync Redis seq counters with DB.
DistinctScopePairs(ctx context.Context) ([]ScopePair, error)
// DeleteMessagesAfter removes all messages with messageID > the given ID.
// Used for editMessage rollback scenarios.
DeleteMessagesAfter(ctx context.Context, conversationID string, messageID uint64) error
// CreateConversationMember adds a user to a conversation.
CreateConversationMember(ctx context.Context, member *ConversationMember) error
// GetConversationMember retrieves a single member by conversation and user IDs.
GetConversationMember(ctx context.Context, conversationID, userID string) (*ConversationMember, error)
// UpdateReadMsgID updates the read watermark (last read message ID) for a member.
UpdateReadMsgID(ctx context.Context, conversationID, userID string, readMsgID uint64) error
// ListConversationMembers returns all member user IDs for a conversation.
// Used by the push fanout: agent replies are published to each member's
// user channel so every device receives the update.
ListConversationMembers(ctx context.Context, conversationID string) ([]string, error)
// ListReadBy returns user IDs who have read at least the given message ID.
ListReadBy(ctx context.Context, conversationID string, msgID uint64) ([]string, error)
// Transaction executes fn within a database transaction. All Store methods
// called on the provided tx Store share the same transaction scope.
// If fn returns an error, the transaction is rolled back.
Transaction(ctx context.Context, fn func(tx Store) error) error
// SetCheckpoint stores checkpoint data for a conversation using optimistic locking.
// Version is atomically incremented on each update.
SetCheckpoint(ctx context.Context, conversationID string, data []byte) error
// GetCheckpoint retrieves checkpoint data for a conversation.
// Returns (nil, nil) if no checkpoint exists.
GetCheckpoint(ctx context.Context, conversationID string) ([]byte, error)
// DeleteCheckpoint removes checkpoint data for a conversation.
DeleteCheckpoint(ctx context.Context, conversationID string) error
// AppendSkippedMessageID adds a message ID to the skipped list for a conversation.
AppendSkippedMessageID(ctx context.Context, conversationID string, messageID int64) error
// ClearSkippedMessageIDs resets the skipped message ID list for a conversation.
ClearSkippedMessageIDs(ctx context.Context, conversationID string) error
// GetSkippedMessageIDs retrieves the skipped message ID list for a conversation.
GetSkippedMessageIDs(ctx context.Context, conversationID string) ([]int64, error)
// CreateToolExecutionResult persists a tool execution record.
CreateToolExecutionResult(ctx context.Context, result *ToolExecutionResult) error
// GetToolExecutionResult retrieves a tool execution record by ID.
GetToolExecutionResult(ctx context.Context, id string) (*ToolExecutionResult, error)
// Ping verifies the database connection is alive.
Ping(ctx context.Context) error
}
Store defines the persistence layer for conversations, messages, members, checkpoints, and statistics. Implementations must be safe for concurrent use.
The Transaction method provides atomic operations — all Store methods provides atomic operations — all Store methods called within the transaction function share the same underlying database transaction.
type TokenStat ¶
type TokenStat struct {
ID string `gorm:"primaryKey;type:varchar(36)" json:"id"`
ConversationID string `gorm:"column:conversation_id;type:varchar(36)" json:"conversation_id"`
MessageID string `gorm:"column:message_id;type:varchar(36)" json:"message_id"`
Model string `gorm:"type:varchar(100)" json:"model"`
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
CreatedAt time.Time `json:"created_at"`
}
TokenStat records token consumption for an LLM call.
type ToolExecutionResult ¶
type ToolExecutionResult struct {
ID string `gorm:"primaryKey;type:varchar(36)" json:"id"`
ConversationID string `gorm:"column:conversation_id;type:varchar(36)" json:"conversation_id"`
MessageID int64 `gorm:"column:message_id;not null" json:"message_id"`
ToolName string `gorm:"column:tool_name;type:varchar(255);not null" json:"tool_name"`
Input string `gorm:"type:text" json:"input"`
Output string `gorm:"type:text" json:"output"`
Status string `gorm:"type:varchar(20);not null" json:"status"` // success / error / timeout
DurationMs int64 `gorm:"column:duration_ms;not null" json:"duration_ms"`
CreatedAt time.Time `json:"created_at"`
}
ToolExecutionResult records a single tool invocation with full input/output. Referenced by tool_result Update via tool_execution_id field.
func (ToolExecutionResult) TableName ¶
func (ToolExecutionResult) TableName() string
TableName returns the GORM table name for ToolExecutionResult.
type ToolStat ¶
type ToolStat struct {
ID string `gorm:"primaryKey;type:varchar(36)" json:"id"`
ConversationID string `gorm:"column:conversation_id;type:varchar(36)" json:"conversation_id"`
ToolName string `gorm:"type:varchar(100)" json:"tool_name"`
Status string `gorm:"type:varchar(20)" json:"status"`
Duration int `json:"duration"`
ErrorMessage string `gorm:"type:text" json:"error_message"`
CreatedAt time.Time `json:"created_at"`
}
ToolStat records execution statistics for a tool call.
type Update ¶
type Update struct {
ID string `gorm:"primaryKey;type:varchar(36)" json:"id"`
Scope string `gorm:"type:varchar(20);index:idx_update_scope,unique" json:"scope"`
ScopeID string `gorm:"column:scope_id;type:varchar(255);index:idx_update_scope,unique" json:"scope_id"`
Seq uint64 `gorm:"index:idx_update_scope,unique" json:"seq"`
Type string `gorm:"type:varchar(50)" json:"type"`
Payload []byte `json:"payload"`
CreatedAt time.Time `json:"created_at"`
}
Update represents an instruction or result delivered via the Update mechanism. Updates are independent of conversations and belong to a Session or User scope.
type WebhookDeadLetter ¶
type WebhookDeadLetter struct {
ID string `gorm:"primaryKey;type:varchar(36)" json:"id"`
TaskID string `gorm:"column:task_id;type:varchar(255)" json:"task_id"`
Event string `gorm:"type:varchar(100)" json:"event"`
UserID string `gorm:"column:user_id;type:varchar(255)" json:"user_id"`
Payload []byte `gorm:"type:text" json:"payload"`
RetryCount int `gorm:"column:retry_count" json:"retry_count"`
LastError string `gorm:"column:last_error;type:text" json:"last_error"`
AttemptHistory []byte `gorm:"column:attempt_history;type:text" json:"-"`
CreatedAt time.Time `json:"created_at"`
FailedAt time.Time `gorm:"column:failed_at" json:"failed_at"`
}
WebhookDeadLetter stores failed webhook delivery attempts for later inspection or replay.
func (WebhookDeadLetter) TableName ¶
func (WebhookDeadLetter) TableName() string
TableName returns the GORM table name for WebhookDeadLetter.