Documentation
¶
Overview ¶
Package memory provides conversation memory storage and session archiving.
The package has two main subsystems:
Active memory (SQLiteStore) manages the working conversation context — messages that are actively used for LLM context windows. Messages can be compacted (summarized) when the context grows too large.
Session archive (ArchiveStore) provides immutable, long-term storage of all conversation transcripts. Messages are archived before any destructive operation (compaction, reset, shutdown), ensuring primary source data is never lost. The archive supports full-text search with gap-aware context expansion — search results include surrounding conversation bounded by natural silence gaps rather than rigid message counts.
Index ¶
- func FitPrefix(n, byteCap int, render func(k int) []byte) []byte
- func FitSuffix(n, byteCap int, render func(drop int) []byte) []byte
- func FormatRecentMessages(messages []Message, now time.Time, truncated bool) []byte
- func FormatSearchResults(results []SearchResult, now time.Time, truncated bool) []byte
- func FormatSessionsList(sessions []*Session, now time.Time, truncated bool) []byte
- func ShortID(id string) string
- type ArchiveAdapter
- func (a *ArchiveAdapter) ActiveConversationIDs() []string
- func (a *ArchiveAdapter) ActiveSessionID(conversationID string) string
- func (a *ArchiveAdapter) ActiveSessionStartedAt(conversationID string) time.Time
- func (a *ArchiveAdapter) ArchiveConversation(conversationID string, messages []Message, reason string) error
- func (a *ArchiveAdapter) ArchiveIterations(iterations []ArchivedIteration) error
- func (a *ArchiveAdapter) EndSession(sessionID string, reason string) error
- func (a *ArchiveAdapter) EnsureSession(conversationID string) string
- func (a *ArchiveAdapter) LinkPendingIterationToolCalls(sessionID string) error
- func (a *ArchiveAdapter) OnMessage(_ string)
- func (a *ArchiveAdapter) StartSession(conversationID string) (string, error)
- func (a *ArchiveAdapter) Store() *ArchiveStore
- type ArchiveConfig
- type ArchiveContextProvider
- type ArchiveReader
- type ArchiveReason
- type ArchiveSearcher
- type ArchiveStore
- func (s *ArchiveStore) ActiveSession(conversationID string) (*Session, error)
- func (s *ArchiveStore) ActiveSessionCount() (int, error)
- func (s *ArchiveStore) ActiveSessionsWithLastActivity() ([]IdleSessionInfo, error)
- func (s *ArchiveStore) ArchiveIterations(iterations []ArchivedIteration) error
- func (s *ArchiveStore) ArchiveMessages(messages []Message) error
- func (s *ArchiveStore) ArchiveToolCalls(calls []ArchivedToolCall) error
- func (s *ArchiveStore) ClaimActiveMessages(conversationID, sessionID string) (int64, error)
- func (s *ArchiveStore) Close() error
- func (s *ArchiveStore) CloseOrphanedSessions(before time.Time) (int64, error)
- func (s *ArchiveStore) DB() *sql.DB
- func (s *ArchiveStore) EndSession(sessionID string, reason string) error
- func (s *ArchiveStore) EndSessionAt(sessionID string, reason string, endedAt time.Time) error
- func (s *ArchiveStore) ExportSessionMarkdown(sessionID string) (string, error)
- func (s *ArchiveStore) FTSEnabled() bool
- func (s *ArchiveStore) GetMessagesByTimeRange(from, to time.Time, conversationID string, limit int) ([]Message, error)
- func (s *ArchiveStore) GetMessagesInRange(opts RangeOptions) ([]Message, bool, error)
- func (s *ArchiveStore) GetSession(sessionID string) (*Session, error)
- func (s *ArchiveStore) GetSessionIterations(sessionID string) ([]ArchivedIteration, error)
- func (s *ArchiveStore) GetSessionToolCalls(sessionID string) ([]ArchivedToolCall, error)
- func (s *ArchiveStore) GetSessionTranscript(sessionID string) ([]Message, error)
- func (s *ArchiveStore) ImportMessages(messages []Message) error
- func (s *ArchiveStore) ImportToolCalls(calls []ArchivedToolCall) error
- func (s *ArchiveStore) IsImported(sourceID, sourceType string) (bool, error)
- func (s *ArchiveStore) LinkPendingIterationToolCalls(sessionID string) error
- func (s *ArchiveStore) LinkToolCallsToIteration(sessionID string, iterationIndex int, toolCallIDs []string) error
- func (s *ArchiveStore) ListChildSessions(parentSessionID string) ([]*Session, error)
- func (s *ArchiveStore) ListSessions(conversationID string, limit int) ([]*Session, error)
- func (s *ArchiveStore) PurgeImported(sourceType string) (int, error)
- func (s *ArchiveStore) RecordImport(sourceID, sourceType, archiveSessionID string) error
- func (s *ArchiveStore) Search(opts SearchOptions) ([]SearchResult, error)
- func (s *ArchiveStore) SetSessionMetadata(sessionID string, meta *SessionMetadata, title string, tags []string) error
- func (s *ArchiveStore) SetSessionSummary(sessionID string, summary string) error
- func (s *ArchiveStore) StartSession(conversationID string) (*Session, error)
- func (s *ArchiveStore) StartSessionAt(conversationID string, startedAt time.Time) (*Session, error)
- func (s *ArchiveStore) StartSessionWithOptions(conversationID string, opts ...SessionOption) (*Session, error)
- func (s *ArchiveStore) Stats() (map[string]any, error)
- func (s *ArchiveStore) UnsummarizedSessions(limit int) ([]*Session, error)
- type ArchivedIteration
- type ArchivedToolCall
- type ChannelBinding
- type CompactableStore
- type CompactionConfig
- type Compactor
- func (c *Compactor) Compact(ctx context.Context, conversationID string) error
- func (c *Compactor) CompactionStats(conversationID string) map[string]any
- func (c *Compactor) CompactionThreshold() int
- func (c *Compactor) NeedsCompaction(conversationID string) bool
- func (c *Compactor) SetWorkingMemoryStore(wm WorkingMemoryReader)
- type Conversation
- type ConversationMetadata
- type DelegationMetadata
- type EpisodicConfig
- type EpisodicProvider
- type ExtractFunc
- type ExtractedFact
- type ExtractionResult
- type Extractor
- func (e *Extractor) Extract(ctx context.Context, userMsg, assistantResp string, recentHistory []Message) error
- func (e *Extractor) SetExtractFunc(fn ExtractFunc)
- func (e *Extractor) SetTimeout(d time.Duration)
- func (e *Extractor) ShouldExtract(userMsg, assistantResp string, messageCount int, skipContext bool) bool
- func (e *Extractor) Timeout() time.Duration
- type FactSetter
- type IdleSessionInfo
- type InteractionCallback
- type LLMSummarizer
- type MemoryStore
- type Message
- type MessageArchiver
- type MessageChannelProvider
- type MessageChannelProviderConfig
- type MessageView
- type RangeOptions
- type SQLiteStore
- func (s *SQLiteStore) AddCompactionSummary(conversationID, summary string) error
- func (s *SQLiteStore) AddMessage(conversationID, role, content string) error
- func (s *SQLiteStore) ArchiveMessages(conversationID, sessionID, reason string) (int64, error)
- func (s *SQLiteStore) ArchiveToolCalls(conversationID, sessionID string) (int64, error)
- func (s *SQLiteStore) BindConversationChannel(conversationID string, binding *ChannelBinding) error
- func (s *SQLiteStore) Clear(conversationID string) error
- func (s *SQLiteStore) ClearToolCalls(conversationID string) error
- func (s *SQLiteStore) Close() error
- func (s *SQLiteStore) CompleteToolCall(toolCallID, result, errMsg string) error
- func (s *SQLiteStore) DB() *sql.DB
- func (s *SQLiteStore) GetAllConversations() []*Conversation
- func (s *SQLiteStore) GetAllMessages(conversationID string) []Message
- func (s *SQLiteStore) GetConversation(id string) *Conversation
- func (s *SQLiteStore) GetMessages(conversationID string) []Message
- func (s *SQLiteStore) GetMessagesForCompaction(conversationID string, keep int) []Message
- func (s *SQLiteStore) GetOrCreateConversation(id string) (*Conversation, error)
- func (s *SQLiteStore) GetTokenCount(conversationID string) int
- func (s *SQLiteStore) GetToolCalls(conversationID string, limit int) []ToolCall
- func (s *SQLiteStore) GetToolCallsByName(toolName string, limit int) []ToolCall
- func (s *SQLiteStore) MarkCompacted(conversationID string, before time.Time) error
- func (s *SQLiteStore) NeedsCompaction(conversationID string, maxTokens int) bool
- func (s *SQLiteStore) PutConversationMetadata(conversationID string, metadata *ConversationMetadata) error
- func (s *SQLiteStore) RecordToolCall(conversationID, messageID, toolCallID, toolName, arguments string) error
- func (s *SQLiteStore) Stats() map[string]any
- func (s *SQLiteStore) ToolCallStats() map[string]any
- type SearchOptions
- type SearchResult
- type SearchResultView
- type Session
- type SessionMetadata
- type SessionOption
- type SessionView
- type SimpleSummarizer
- type Store
- func (s *Store) AddMessage(conversationID string, role, content string) error
- func (s *Store) BindConversationChannel(conversationID string, binding *ChannelBinding) error
- func (s *Store) Clear(conversationID string) error
- func (s *Store) GetAllConversations() []*Conversation
- func (s *Store) GetConversation(id string) *Conversation
- func (s *Store) GetMessages(conversationID string) []Message
- func (s *Store) GetOrCreateConversation(id string) *Conversation
- func (s *Store) GetTokenCount(conversationID string) int
- func (s *Store) PutConversationMetadata(conversationID string, metadata *ConversationMetadata) error
- func (s *Store) RestoreConversations(convs []*Conversation)
- func (s *Store) Stats() map[string]any
- type Summarizer
- type SummarizerConfig
- type SummarizerWorker
- type ToolCall
- type ToolCallArchiver
- type WorkingMemoryProvider
- type WorkingMemoryReader
- type WorkingMemoryStore
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FitPrefix ¶
FitPrefix returns the largest count k in [0, n] such that render(k) is within byteCap. render must produce monotonically non-decreasing output as k grows. Used by prefix-fit clipping (e.g., search results, where the tail entries are lower-relevance and are the right ones to drop). Output is always rendered with truncated=true when k < n.
func FitSuffix ¶
FitSuffix returns the smallest count k in [0, n] such that render(k) is within byteCap. render must produce monotonically non-increasing output as k grows (k is the number of items dropped from the front). Used by suffix-fit clipping where older entries are dropped first to preserve the most-recent tail.
func FormatRecentMessages ¶
FormatRecentMessages renders messages as JSON for tool output or system-prompt context blocks. Each entry includes a delta timestamp and the originating session ID, so the model can chain into archive_session_transcript when it wants more context around a turn.
func FormatSearchResults ¶
func FormatSearchResults(results []SearchResult, now time.Time, truncated bool) []byte
FormatSearchResults renders archive search hits as JSON. Each result carries the matched message plus the surrounding context window in chronological order. SessionID is emitted on every message; context messages may belong to a different session than the match because context expansion is bounded by silence gaps, not session edges. Context lists are trimmed to the [maxSearchContextPerSide] messages closest to the match on each side — for context_before that's the last N, for context_after the first N.
func FormatSessionsList ¶
FormatSessionsList renders sessions as JSON suitable for tool output or system-prompt context blocks. Always emits a non-nil "sessions" array and a "truncated" boolean so the model can detect cap-driven truncation without parsing prose.
Types ¶
type ArchiveAdapter ¶
type ArchiveAdapter struct {
// contains filtered or unexported fields
}
ArchiveAdapter bridges the ArchiveStore to the agent.SessionArchiver interface. It manages session lifecycle and coordinates message archival in the unified messages table.
func NewArchiveAdapter ¶
func NewArchiveAdapter(store *ArchiveStore, msgStore MessageArchiver, tcStore ToolCallArchiver, logger *slog.Logger) *ArchiveAdapter
NewArchiveAdapter creates an adapter that implements agent.SessionArchiver. The msgStore and tcStore are used to set lifecycle status when archiving messages and tool calls in the unified table.
func (*ArchiveAdapter) ActiveConversationIDs ¶
func (a *ArchiveAdapter) ActiveConversationIDs() []string
ActiveConversationIDs returns the conversation IDs with currently open sessions. It prefers the in-memory cache for hot-path accuracy and merges in any active sessions found in the store so startup recovery and direct store writes are also reflected.
func (*ArchiveAdapter) ActiveSessionID ¶
func (a *ArchiveAdapter) ActiveSessionID(conversationID string) string
ActiveSessionID returns the current session ID for a conversation, or empty.
func (*ArchiveAdapter) ActiveSessionStartedAt ¶
func (a *ArchiveAdapter) ActiveSessionStartedAt(conversationID string) time.Time
ActiveSessionStartedAt returns when the active session for a conversation began, or the zero time if there is no active session. Uses the in-memory cache populated by StartSession and ActiveSessionID to avoid per-turn database lookups.
func (*ArchiveAdapter) ArchiveConversation ¶
func (a *ArchiveAdapter) ArchiveConversation(conversationID string, messages []Message, reason string) error
ArchiveConversation archives all messages and tool calls from a conversation by setting their status to 'archived' in the unified table.
func (*ArchiveAdapter) ArchiveIterations ¶
func (a *ArchiveAdapter) ArchiveIterations(iterations []ArchivedIteration) error
ArchiveIterations persists a batch of iteration records to the archive store.
func (*ArchiveAdapter) EndSession ¶
func (a *ArchiveAdapter) EndSession(sessionID string, reason string) error
EndSession ends a session. Session metadata is generated by the background summarizer worker, not here — this avoids a race with process shutdown that previously caused summaries to be lost.
func (*ArchiveAdapter) EnsureSession ¶
func (a *ArchiveAdapter) EnsureSession(conversationID string) string
EnsureSession starts a session if none is active for the conversation.
func (*ArchiveAdapter) LinkPendingIterationToolCalls ¶
func (a *ArchiveAdapter) LinkPendingIterationToolCalls(sessionID string) error
LinkPendingIterationToolCalls links archived tool calls to their parent iterations using the tool_call_ids stored on the iteration records.
func (*ArchiveAdapter) OnMessage ¶
func (a *ArchiveAdapter) OnMessage(_ string)
OnMessage is a no-op retained for interface compatibility. Session message counts are now computed from the unified messages table.
func (*ArchiveAdapter) StartSession ¶
func (a *ArchiveAdapter) StartSession(conversationID string) (string, error)
StartSession begins a new session and returns its ID.
func (*ArchiveAdapter) Store ¶
func (a *ArchiveAdapter) Store() *ArchiveStore
Store returns the underlying ArchiveStore for direct access (API endpoints, etc.)
type ArchiveConfig ¶
type ArchiveConfig struct {
// SilenceThreshold is the gap duration that signals a conversation boundary.
// Default: 10 minutes.
SilenceThreshold time.Duration
// MaxContextMessages is the hard cap on context messages per direction.
// Default: 50.
MaxContextMessages int
// MaxContextDuration is the time-based hard cap on context expansion.
// Default: 1 hour.
MaxContextDuration time.Duration
}
ArchiveConfig configures the archive store.
func DefaultArchiveConfig ¶
func DefaultArchiveConfig() ArchiveConfig
DefaultArchiveConfig returns sensible defaults.
type ArchiveContextProvider ¶
type ArchiveContextProvider struct {
// contains filtered or unexported fields
}
ArchiveContextProvider implements [agent.TagContextProvider] for injecting relevant past conversation excerpts into the system prompt. This is Layer 2 of the pre-warming system: Layer 1 provides knowledge (facts + KB docs), Layer 2 provides experiential judgment (prior reasoning about similar situations). Registered via [agent.Loop.RegisterAlwaysContextProvider].
func NewArchiveContextProvider ¶
func NewArchiveContextProvider(store ArchiveSearcher, maxResults, maxBytes int, logger *slog.Logger) *ArchiveContextProvider
NewArchiveContextProvider creates a context provider that searches the conversation archive for relevant past exchanges. maxResults caps the number of search hits; maxBytes caps the formatted output size to prevent context flooding.
func (*ArchiveContextProvider) TagContext ¶
func (p *ArchiveContextProvider) TagContext(ctx context.Context, req agentctx.ContextRequest) (string, error)
TagContext searches the conversation archive for excerpts relevant to the current wake context. Subjects are extracted from ctx (set by the wake bridge); if no subjects are available but req.UserMessage is short, it falls back to searching by message content. Implements [agent.TagContextProvider]; registered via RegisterAlwaysContextProvider.
Returns empty string when there is nothing to search for or no results are found. Errors from the archive store are logged and swallowed — archive injection should never block a wake.
type ArchiveReader ¶
type ArchiveReader interface {
// ListSessions returns sessions ordered newest-first. Pass empty
// conversationID to list sessions across all conversations.
ListSessions(conversationID string, limit int) ([]*Session, error)
}
ArchiveReader is the subset of ArchiveStore needed by the episodic memory provider. Defined as an interface for testability.
type ArchiveReason ¶
type ArchiveReason string
ArchiveReason describes why messages were archived.
const ( ArchiveReasonCompaction ArchiveReason = "compaction" ArchiveReasonReset ArchiveReason = "reset" ArchiveReasonShutdown ArchiveReason = "shutdown" ArchiveReasonManual ArchiveReason = "manual" )
type ArchiveSearcher ¶
type ArchiveSearcher interface {
Search(opts SearchOptions) ([]SearchResult, error)
}
ArchiveSearcher abstracts archive search for testing. ArchiveStore satisfies this interface — no adapter needed.
type ArchiveStore ¶
type ArchiveStore struct {
// contains filtered or unexported fields
}
ArchiveStore handles immutable session transcript archiving.
func NewArchiveStore ¶
func NewArchiveStore(dbPath string, messagesDB *sql.DB, cfg *ArchiveConfig, logger *slog.Logger) (*ArchiveStore, error)
NewArchiveStore creates a new archive store at the given database path. Pass nil for cfg to use DefaultArchiveConfig(). Pass nil for logger to suppress startup logging.
When messagesDB is non-nil (unified mode), message queries use that connection against the "messages" table. When nil (legacy mode), message queries use the archive database's "archive_messages" table.
func NewArchiveStoreFromDB ¶
func NewArchiveStoreFromDB(db *sql.DB, cfg *ArchiveConfig, logger *slog.Logger) (*ArchiveStore, error)
NewArchiveStoreFromDB creates an ArchiveStore backed by an existing database connection (typically the main thane.db). The store does NOT own the connection and Close will not close it. All session, iteration, message, and tool-call queries go to the shared connection. This is the "consolidated" mode where archive.db no longer exists.
func (*ArchiveStore) ActiveSession ¶
func (s *ArchiveStore) ActiveSession(conversationID string) (*Session, error)
ActiveSession returns the most recent unclosed session for a conversation, if any.
func (*ArchiveStore) ActiveSessionCount ¶
func (s *ArchiveStore) ActiveSessionCount() (int, error)
ActiveSessionCount returns the number of unclosed (active) sessions. This is a lightweight query for telemetry dashboards — use ArchiveStore.ActiveSessionsWithLastActivity when per-session details are needed.
func (*ArchiveStore) ActiveSessionsWithLastActivity ¶
func (s *ArchiveStore) ActiveSessionsWithLastActivity() ([]IdleSessionInfo, error)
ActiveSessionsWithLastActivity returns all unclosed sessions and the timestamp of the most recent message in each. Sessions with no messages use the session's started_at as the last activity time. This powers the summarizer's idle session detection — it survives crashes because it reads from the database rather than relying on in-memory state.
func (*ArchiveStore) ArchiveIterations ¶
func (s *ArchiveStore) ArchiveIterations(iterations []ArchivedIteration) error
ArchiveIterations copies iteration records to the immutable archive. Iteration indices are automatically offset so that sessions spanning multiple Run() calls never collide on the (session_id, iteration_index) primary key.
func (*ArchiveStore) ArchiveMessages ¶
func (s *ArchiveStore) ArchiveMessages(messages []Message) error
ArchiveMessages copies messages to the immutable archive. This is the core "never throw data away" operation.
In unified mode (messagesDB set), this is a no-op — messages already live in the unified table and are archived via status UPDATE by SQLiteStore.
func (*ArchiveStore) ArchiveToolCalls ¶
func (s *ArchiveStore) ArchiveToolCalls(calls []ArchivedToolCall) error
ArchiveToolCalls copies tool call records to the immutable archive.
In unified mode (messagesDB set), this is a no-op — tool call archival is handled via status UPDATE by SQLiteStore.ArchiveToolCalls.
func (*ArchiveStore) ClaimActiveMessages ¶
func (s *ArchiveStore) ClaimActiveMessages(conversationID, sessionID string) (int64, error)
ClaimActiveMessages stamps session_id on active messages for a conversation so they become retrievable by GetSessionTranscript. This is needed when the summarizer's idle backstop closes a session — active messages in the unified table have session_id=NULL until archival, so without this step the transcript query returns nothing and the session is marked empty.
In legacy mode (archive_messages), session_id is always set at insert time, so this is a no-op. In unified mode, the status column distinguishes active messages from compacted/archived ones.
func (*ArchiveStore) Close ¶
func (s *ArchiveStore) Close() error
Close closes the underlying database connection. If the store was created via NewArchiveStoreFromDB with a shared connection, Close is a no-op — the caller owns the connection lifetime.
func (*ArchiveStore) CloseOrphanedSessions ¶
func (s *ArchiveStore) CloseOrphanedSessions(before time.Time) (int64, error)
CloseOrphanedSessions ends any sessions that are still open (ended_at IS NULL) but were started before the given cutoff time. This recovers sessions orphaned by crashes (SIGKILL, OOM, panics) where EndSession was never called. Returns the number of sessions closed.
func (*ArchiveStore) DB ¶
func (s *ArchiveStore) DB() *sql.DB
DB returns the underlying database connection. This allows other stores (e.g. WorkingMemoryStore) to share the archive database without opening a separate connection.
func (*ArchiveStore) EndSession ¶
func (s *ArchiveStore) EndSession(sessionID string, reason string) error
EndSession marks a session as ended at the current time.
func (*ArchiveStore) EndSessionAt ¶
EndSessionAt marks a session as ended at a specific time.
func (*ArchiveStore) ExportSessionMarkdown ¶
func (s *ArchiveStore) ExportSessionMarkdown(sessionID string) (string, error)
ExportSessionMarkdown exports a session transcript as human-readable markdown. Includes tool call records interleaved chronologically with messages.
func (*ArchiveStore) FTSEnabled ¶
func (s *ArchiveStore) FTSEnabled() bool
FTSEnabled returns whether FTS5 full-text search is available.
func (*ArchiveStore) GetMessagesByTimeRange ¶
func (s *ArchiveStore) GetMessagesByTimeRange(from, to time.Time, conversationID string, limit int) ([]Message, error)
GetMessagesByTimeRange returns archived messages within a time range.
func (*ArchiveStore) GetMessagesInRange ¶
func (s *ArchiveStore) GetMessagesInRange(opts RangeOptions) ([]Message, bool, error)
GetMessagesInRange returns archived messages bounded by time, with an optional MinMessages floor that guarantees a useful tail even on quiet conversations. Results are ordered chronologically (oldest first). The boolean return is true when MaxMessages clipped the result.
func (*ArchiveStore) GetSession ¶
func (s *ArchiveStore) GetSession(sessionID string) (*Session, error)
GetSession retrieves a session by ID.
func (*ArchiveStore) GetSessionIterations ¶
func (s *ArchiveStore) GetSessionIterations(sessionID string) ([]ArchivedIteration, error)
GetSessionIterations returns archived iterations for a session ordered by iteration index.
func (*ArchiveStore) GetSessionToolCalls ¶
func (s *ArchiveStore) GetSessionToolCalls(sessionID string) ([]ArchivedToolCall, error)
GetSessionToolCalls returns archived tool calls for a session in chronological order.
func (*ArchiveStore) GetSessionTranscript ¶
func (s *ArchiveStore) GetSessionTranscript(sessionID string) ([]Message, error)
GetSessionTranscript returns all archived messages for a session in chronological order.
func (*ArchiveStore) ImportMessages ¶
func (s *ArchiveStore) ImportMessages(messages []Message) error
ImportMessages inserts externally-sourced messages (e.g. from openclaw-import) into the archive. Unlike ArchiveMessages, which is a no-op in unified mode (since archival is a status UPDATE on existing rows), ImportMessages performs real INSERTs in both modes — the data doesn't already exist in any table.
In legacy mode, this delegates to ArchiveMessages. In unified mode, rows are inserted directly into the messages table with status='archived'. FTS triggers keep the full-text index in sync automatically.
func (*ArchiveStore) ImportToolCalls ¶
func (s *ArchiveStore) ImportToolCalls(calls []ArchivedToolCall) error
ImportToolCalls inserts externally-sourced tool calls (e.g. from openclaw-import) into the archive. Like ImportMessages, this performs real INSERTs in both modes rather than being a no-op in unified mode.
func (*ArchiveStore) IsImported ¶
func (s *ArchiveStore) IsImported(sourceID, sourceType string) (bool, error)
IsImported checks whether an external source ID has already been imported.
func (*ArchiveStore) LinkPendingIterationToolCalls ¶
func (s *ArchiveStore) LinkPendingIterationToolCalls(sessionID string) error
LinkPendingIterationToolCalls reads iterations for a session, and for each iteration with stored tool_call_ids, updates the corresponding archive_tool_calls rows. Call this after tool calls have been archived so the UPDATE finds matching rows.
func (*ArchiveStore) LinkToolCallsToIteration ¶
func (s *ArchiveStore) LinkToolCallsToIteration(sessionID string, iterationIndex int, toolCallIDs []string) error
LinkToolCallsToIteration sets the iteration_index on tool calls that belong to a specific iteration within a session. In unified mode, this updates the working DB's tool_calls table.
func (*ArchiveStore) ListChildSessions ¶
func (s *ArchiveStore) ListChildSessions(parentSessionID string) ([]*Session, error)
ListChildSessions returns sessions whose parent_session_id matches the given ID, ordered chronologically. Used by the session inspector to show delegate sub-sessions.
func (*ArchiveStore) ListSessions ¶
func (s *ArchiveStore) ListSessions(conversationID string, limit int) ([]*Session, error)
ListSessions returns sessions, newest first.
func (*ArchiveStore) PurgeImported ¶
func (s *ArchiveStore) PurgeImported(sourceType string) (int, error)
PurgeImported removes all archive data that was imported from a given source type. This deletes sessions, messages, tool calls, and import metadata — a clean slate so the import can be re-run with improved logic.
In unified mode, messages live in a different database than sessions and metadata. The method handles this by deleting messages from the messages DB first, then cleaning up sessions and metadata from the archive DB in a transaction.
func (*ArchiveStore) RecordImport ¶
func (s *ArchiveStore) RecordImport(sourceID, sourceType, archiveSessionID string) error
RecordImport creates a mapping from an external source ID to an archive session ID. Used by importers to track which external sessions have already been imported, enabling idempotent re-runs.
func (*ArchiveStore) Search ¶
func (s *ArchiveStore) Search(opts SearchOptions) ([]SearchResult, error)
Search performs a full-text search with gap-aware context expansion.
func (*ArchiveStore) SetSessionMetadata ¶
func (s *ArchiveStore) SetSessionMetadata(sessionID string, meta *SessionMetadata, title string, tags []string) error
SetSessionMetadata updates the full rich metadata for a session, including title, tags, summary, and structured metadata JSON.
func (*ArchiveStore) SetSessionSummary ¶
func (s *ArchiveStore) SetSessionSummary(sessionID string, summary string) error
SetSessionSummary updates only the summary text for a session. For richer metadata, use SetSessionMetadata.
func (*ArchiveStore) StartSession ¶
func (s *ArchiveStore) StartSession(conversationID string) (*Session, error)
StartSession creates a new session record with the current time.
func (*ArchiveStore) StartSessionAt ¶
StartSessionAt creates a new session record with a specific start time. Use for imports where the original timestamp must be preserved.
func (*ArchiveStore) StartSessionWithOptions ¶
func (s *ArchiveStore) StartSessionWithOptions(conversationID string, opts ...SessionOption) (*Session, error)
StartSessionWithOptions creates a new session record with optional parent linkage and metadata snapshots. Use WithParentSession, WithParentToolCall, and WithChannelBinding to stamp archive-only session context at creation time.
func (*ArchiveStore) Stats ¶
func (s *ArchiveStore) Stats() (map[string]any, error)
Stats returns archive statistics.
func (*ArchiveStore) UnsummarizedSessions ¶
func (s *ArchiveStore) UnsummarizedSessions(limit int) ([]*Session, error)
UnsummarizedSessions returns ended sessions that have no metadata yet, ordered oldest-first for catch-up processing. Only sessions with at least one message are returned — the message count is checked via a post-query filter because messages may live in a different database than sessions in unified mode.
type ArchivedIteration ¶
type ArchivedIteration struct {
SessionID string `json:"session_id"`
IterationIndex int `json:"iteration_index"`
Model string `json:"model"`
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
ToolCallCount int `json:"tool_call_count"`
ToolCallIDs []string `json:"tool_call_ids,omitempty"`
ToolsOffered []string `json:"tools_offered,omitempty"`
StartedAt time.Time `json:"started_at"`
DurationMs int64 `json:"duration_ms"`
HasToolCalls bool `json:"has_tool_calls"`
BreakReason string `json:"break_reason,omitempty"`
}
ArchivedIteration represents one pass through an agent or delegate loop preserved in the archive. Each iteration corresponds to one LLM call plus any tool calls that follow.
type ArchivedToolCall ¶
type ArchivedToolCall struct {
ID string `json:"id"`
ConversationID string `json:"conversation_id"`
SessionID string `json:"session_id"`
ToolName string `json:"tool_name"`
Arguments string `json:"arguments"`
Result string `json:"result,omitempty"`
Error string `json:"error,omitempty"`
StartedAt time.Time `json:"started_at"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
DurationMs int64 `json:"duration_ms,omitempty"`
ArchivedAt time.Time `json:"archived_at"`
IterationIndex *int `json:"iteration_index,omitempty"`
}
ArchivedToolCall represents a tool call preserved in the archive.
type ChannelBinding ¶
type ChannelBinding struct {
Channel string `json:"channel,omitempty"`
Address string `json:"address,omitempty"`
ContactID string `json:"contact_id,omitempty"`
ContactName string `json:"contact_name,omitempty"`
TrustZone string `json:"trust_zone,omitempty"`
LinkSource string `json:"link_source,omitempty"`
IsOwner bool `json:"is_owner,omitempty"`
}
ChannelBinding captures the runtime identity of a channel-backed conversation. It links a live channel/address pair to any known contact record so downstream code can gate on a typed binding instead of reconstructing identity from hints.
func (*ChannelBinding) Clone ¶
func (b *ChannelBinding) Clone() *ChannelBinding
Clone returns a deep copy of the binding.
func (*ChannelBinding) Normalize ¶
func (b *ChannelBinding) Normalize() *ChannelBinding
Normalize returns a trimmed copy of the binding, or nil when the binding carries no meaningful channel identity.
type CompactableStore ¶
type CompactableStore interface {
GetTokenCount(conversationID string) int
GetMessagesForCompaction(conversationID string, keep int) []Message
MarkCompacted(conversationID string, before time.Time) error
AddCompactionSummary(conversationID, summary string) error
}
CompactableStore is the interface for stores that support compaction.
type CompactionConfig ¶
type CompactionConfig struct {
MaxTokens int // Context window size
TriggerRatio float64 // Trigger compaction at this ratio (e.g., 0.7 = 70%)
KeepRecent int // Number of recent messages to always keep
MinMessagesToCompact int // Minimum messages before considering compaction
}
CompactionConfig controls compaction behavior.
func DefaultCompactionConfig ¶
func DefaultCompactionConfig() CompactionConfig
DefaultCompactionConfig returns sensible defaults.
type Compactor ¶
type Compactor struct {
// contains filtered or unexported fields
}
Compactor handles conversation compaction.
func NewCompactor ¶
func NewCompactor(store CompactableStore, config CompactionConfig, summarizer Summarizer, logger *slog.Logger) *Compactor
NewCompactor creates a new compactor.
func (*Compactor) CompactionStats ¶
CompactionStats returns stats about compaction for a conversation.
func (*Compactor) CompactionThreshold ¶
CompactionThreshold returns the token count at which compaction triggers.
func (*Compactor) NeedsCompaction ¶
NeedsCompaction checks if a conversation needs compaction.
func (*Compactor) SetWorkingMemoryStore ¶
func (c *Compactor) SetWorkingMemoryStore(wm WorkingMemoryReader)
SetWorkingMemoryStore configures a working memory store so that the compactor can include experiential context in the compaction prompt.
type Conversation ¶
type Conversation struct {
ID string `json:"id"`
Messages []Message `json:"messages"`
Metadata *ConversationMetadata `json:"metadata,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
Conversation holds the state of a single conversation.
type ConversationMetadata ¶
type ConversationMetadata struct {
ChannelBinding *ChannelBinding `json:"channel_binding,omitempty"`
}
ConversationMetadata holds typed metadata associated with a live conversation. It is stored as JSON so new fields can be added without schema churn.
func (*ConversationMetadata) Clone ¶
func (m *ConversationMetadata) Clone() *ConversationMetadata
Clone returns a deep copy of the metadata.
func (*ConversationMetadata) Normalize ¶
func (m *ConversationMetadata) Normalize() *ConversationMetadata
Normalize returns a cleaned copy of the metadata, or nil when it contains no meaningful fields.
type DelegationMetadata ¶
type DelegationMetadata struct {
Task string `json:"task"`
Guidance string `json:"guidance,omitempty"`
Profile string `json:"profile"`
Model string `json:"model"`
Iterations int `json:"iterations"`
MaxIterations int `json:"max_iterations"`
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
Exhausted bool `json:"exhausted"`
ExhaustReason string `json:"exhaust_reason,omitempty"`
ResultContent string `json:"result_content,omitempty"`
DurationMs int64 `json:"duration_ms"`
Error string `json:"error,omitempty"`
// Messages is the raw JSON-serialized conversation history from the
// legacy delegations table. Preserved to avoid data loss — these
// messages predate the per-message storage in the messages table.
Messages string `json:"messages,omitempty"`
}
DelegationMetadata holds delegation-specific fields preserved from the legacy delegations table during migration (#446).
type EpisodicConfig ¶
type EpisodicConfig struct {
// Timezone is the IANA timezone string (e.g. "America/Chicago").
Timezone string
// DailyDir is the directory containing YYYY-MM-DD.md daily memory
// files. Supports ~ expansion. Empty disables daily file injection.
DailyDir string
// LookbackDays is how many days of daily memory files to include.
LookbackDays int
// HistoryTokens is the approximate token budget for recent
// conversation history. Converted to a byte cap (×4) when fitting
// the JSON catalog block.
HistoryTokens int
}
EpisodicConfig holds configuration for the episodic memory provider.
type EpisodicProvider ¶
type EpisodicProvider struct {
// contains filtered or unexported fields
}
EpisodicProvider implements [agent.TagContextProvider] for episodic memory. It injects two unrelated context blocks into the system prompt:
"Daily Notes" — markdown content from per-day notes files (a human-authored journal) for the configured lookback window.
"Recent Sessions" — a JSON catalog of the most recent closed sessions across all conversations, keyed for archive_search and archive_session_transcript follow-ups. Rendered via FormatSessionsList for schema parity with the archive_* tools and the message_channel context provider.
Per-channel verbatim history (the model's "what did we just say?" view for message-channel conversations) lives in a separate provider gated on the message_channel capability tag — see MessageChannelProvider. EpisodicProvider intentionally stays channel-agnostic and emits the same JSON shape on every code path.
func NewEpisodicProvider ¶
func NewEpisodicProvider(archive ArchiveReader, logger *slog.Logger, cfg EpisodicConfig) *EpisodicProvider
NewEpisodicProvider creates an episodic memory context provider.
func (*EpisodicProvider) TagContext ¶
func (p *EpisodicProvider) TagContext(_ context.Context, _ agentctx.ContextRequest) (string, error)
TagContext returns episodic memory context for injection into the system prompt. It assembles daily memory notes and the recent- sessions JSON catalog from the archive. Implements [agent.TagContextProvider]; registered via RegisterAlwaysContextProvider.
type ExtractFunc ¶
type ExtractFunc func(ctx context.Context, userMessage, assistantResponse string, recentHistory []Message) (*ExtractionResult, error)
ExtractFunc calls an LLM to extract facts from a single interaction. It receives the current user message, assistant response, and recent conversation history for context.
type ExtractedFact ¶
type ExtractedFact struct {
Category string `json:"category"`
Key string `json:"key"`
Value string `json:"value"`
Confidence float64 `json:"confidence"`
}
ExtractedFact is a single fact parsed from the LLM extraction response. Category must be one of the valid fact categories (user, home, device, routine, preference, architecture). Confidence is 0–1.
type ExtractionResult ¶
type ExtractionResult struct {
Facts []ExtractedFact `json:"facts"`
WorthPersisting bool `json:"worth_persisting"`
}
ExtractionResult is the structured JSON response from an LLM fact extraction call. WorthPersisting acts as a top-level gate: when false, the Facts slice is ignored even if populated.
type Extractor ¶
type Extractor struct {
// contains filtered or unexported fields
}
Extractor runs automatic fact extraction after each interaction. It is fully async and best-effort — failures are logged but never propagate to the caller or affect the user-facing response.
func NewExtractor ¶
func NewExtractor(facts FactSetter, logger *slog.Logger, minMessages int) *Extractor
NewExtractor creates an Extractor that persists facts via the given FactSetter. The minMessages threshold controls the minimum conversation length before extraction is attempted.
func (*Extractor) Extract ¶
func (e *Extractor) Extract(ctx context.Context, userMsg, assistantResp string, recentHistory []Message) error
Extract calls the configured ExtractFunc and persists any discovered facts via the FactSetter. Incomplete facts (missing category, key, or value) are silently skipped. Errors from individual SetFact calls are logged but do not stop processing of remaining knowledge.
func (*Extractor) SetExtractFunc ¶
func (e *Extractor) SetExtractFunc(fn ExtractFunc)
SetExtractFunc configures the LLM extraction function.
func (*Extractor) SetTimeout ¶
SetTimeout configures the LLM call timeout for extraction.
func (*Extractor) ShouldExtract ¶
func (e *Extractor) ShouldExtract(userMsg, assistantResp string, messageCount int, skipContext bool) bool
ShouldExtract reports whether the given interaction is worth analyzing for knowledge. It filters out simple device commands, short responses, and auxiliary requests to keep LLM extraction calls to roughly 30–50% of interactions.
type FactSetter ¶
type FactSetter interface {
SetFact(category, key, value, source string, confidence float64) error
}
FactSetter persists extracted facts to long-term storage. Implementations may apply additional logic such as confidence reinforcement on upsert.
type IdleSessionInfo ¶
IdleSessionInfo holds an active session's identity and last activity time for idle timeout evaluation by the summarizer worker.
type InteractionCallback ¶
type InteractionCallback func(conversationID string, sessionID string, endedAt time.Time, topics []string)
InteractionCallback is called after successful session summarization to update the contact's last interaction metadata. Parameters: conversationID, sessionID, endedAt, topics (tags).
type LLMSummarizer ¶
type LLMSummarizer struct {
// contains filtered or unexported fields
}
LLMSummarizer uses an LLM to generate summaries.
func NewLLMSummarizer ¶
func NewLLMSummarizer(llmFunc func(ctx context.Context, prompt string) (string, error)) *LLMSummarizer
NewLLMSummarizer creates a summarizer that uses an LLM.
func (*LLMSummarizer) Summarize ¶
func (s *LLMSummarizer) Summarize(ctx context.Context, messages []Message, workingMemory string) (string, error)
Summarize generates a summary of the messages using an LLM. When workingMemory is non-empty, it is included in the prompt so the summarizer preserves experiential context through compaction.
type MemoryStore ¶
type MemoryStore interface {
GetMessages(conversationID string) []Message
AddMessage(conversationID, role, content string) error
GetConversation(id string) *Conversation
Clear(conversationID string) error
Stats() map[string]any
}
MemoryStore is the interface for memory storage backends (in-memory and SQLite). Both Store and SQLiteStore satisfy it.
type Message ¶
type Message struct {
ID string `json:"id"` // Stable UUIDv7 assigned at creation time
ConversationID string `json:"conversation_id,omitempty"` // Set for archived messages
SessionID string `json:"session_id,omitempty"` // Set for archived messages
Role string `json:"role"` // system, user, assistant, tool
Content string `json:"content"`
Timestamp time.Time `json:"timestamp"`
TokenCount int `json:"token_count,omitempty"` // Estimated token count
ToolCalls string `json:"tool_calls,omitempty"` // JSON array of tool calls (assistant messages)
ToolCallID string `json:"tool_call_id,omitempty"` // Tool call ID (tool response messages)
ArchivedAt time.Time `json:"archived_at,omitzero"` // When the message was archived
ArchiveReason string `json:"archive_reason,omitempty"` // Why: compaction, reset, shutdown, import
}
Message represents a conversation message. This is the unified type for both active working-memory messages and archived session transcripts. Archive-specific fields (ConversationID, SessionID, TokenCount, ArchivedAt, ArchiveReason) are zero-valued for active messages.
type MessageArchiver ¶
type MessageArchiver interface {
ArchiveMessages(conversationID, sessionID, reason string) (int64, error)
}
MessageArchiver sets lifecycle status on messages in the unified table.
type MessageChannelProvider ¶
type MessageChannelProvider struct {
// contains filtered or unexported fields
}
MessageChannelProvider injects two context blocks into the system prompt for message-channel conversations:
"Recent Conversation" — verbatim tail of recent archived messages (last [TailWindow] OR floor of [TailMinMessages] of the most recent, whichever yields more, capped at [TailMaxMessages] and [TailByteCap]). Crosses session boundaries. Excludes the active session's currently in-memory rows so the model does not see them twice (once in its working message list, again here).
"Older Sessions" — JSON metadata block for sessions ending before the verbatim window. Acts as enticement to call archive_session_transcript or archive_search for fuller content.
Implements [agent.TagContextProvider] via structural typing; gated on the message_channel capability tag asserted by Signal (and future Matrix/iMessage) inbound bridges.
Output sits in the system prompt's DYNAMIC CONTEXT section per docs/anthropic-caching.md: the delta timestamps tick every turn so the block is intrinsically uncached, but the cached prefix above it stays warm.
func NewMessageChannelProvider ¶
func NewMessageChannelProvider(archive *ArchiveStore, conversationIDFromCtx func(context.Context) string, cfg MessageChannelProviderConfig, logger *slog.Logger) *MessageChannelProvider
NewMessageChannelProvider creates the provider. The conversationIDFromCtx function extracts the active conversation ID from a request context — pass [tools.ConversationIDFromContext]. Zero-valued config fields fall back to defaults documented on MessageChannelProviderConfig.
func (*MessageChannelProvider) TagContext ¶
func (p *MessageChannelProvider) TagContext(ctx context.Context, _ agentctx.ContextRequest) (string, error)
TagContext returns the verbatim tail + older-sessions blocks for the active conversation. Returns the empty string when there is nothing to emit (no conversation context, no archived content).
type MessageChannelProviderConfig ¶
type MessageChannelProviderConfig struct {
// TailWindow is the time bound on the verbatim tail. Default: 30m.
TailWindow time.Duration
// TailMinMessages is the floor: at least this many of the most
// recent archived messages on the conversation are returned even
// if the time window is empty. Default: 50.
TailMinMessages int
// TailMaxMessages caps the verbatim tail before byte-cap fitting.
// Default: 200.
TailMaxMessages int
// TailByteCap is the JSON output ceiling for the verbatim tail.
// Default: 32000.
TailByteCap int
// OlderSessionsLimit caps the number of older-session entries
// listed in the catalog block. Default: 20.
OlderSessionsLimit int
// OlderSessionsByteCap is the JSON output ceiling for the older-
// sessions catalog. Default: 16000.
OlderSessionsByteCap int
}
MessageChannelProviderConfig configures the verbatim-tail + older- sessions context provider for message-channel conversations (Signal, Matrix, iMessage). Zero values fall back to defaults.
type MessageView ¶
type MessageView struct {
T string `json:"t"`
Role string `json:"role"`
Content string `json:"content"`
ContentTruncated bool `json:"content_truncated"`
SessionID string `json:"session_id"`
}
MessageView is the JSON-facing projection of an archived message. T is a signed-second delta via promptfmt.FormatDeltaOnly. SessionID is always emitted (empty string when unknown) so the model sees a stable schema across calls. ContentTruncated signals when Content was clipped to [maxMessageContentBytes].
type RangeOptions ¶
type RangeOptions struct {
// ConversationID restricts the result to a single conversation when
// non-empty. Empty matches all conversations.
ConversationID string
// ExcludeSessionID drops messages from the named session when
// non-empty. Useful for system-prompt context providers that want
// archived/older messages but not the active session's currently
// in-memory rows (which the model already sees in its working
// message list).
ExcludeSessionID string
// From is the earliest timestamp to include (inclusive). Zero means
// unbounded — combined with MinMessages, this is how the "give me at
// least N most-recent messages regardless of age" query is expressed.
From time.Time
// To is the latest timestamp to include (inclusive). Zero is treated
// as time.Now() at query time.
To time.Time
// MinMessages is a floor: ensure at least this many of the most
// recent (≤ To) messages are returned, even when fewer fall inside
// [From, To]. Zero disables the floor. Useful for "last X minutes
// OR Y messages, whichever is more" without two callsite queries.
MinMessages int
// MaxMessages caps the result. Non-positive uses the default of 200.
// When the cap clips the result, the second return value of
// GetMessagesInRange is true.
MaxMessages int
}
RangeOptions configures ArchiveStore.GetMessagesInRange. All fields are optional — the zero value of RangeOptions returns the most recent 200 messages across all conversations, ordered chronologically.
type SQLiteStore ¶
type SQLiteStore struct {
// contains filtered or unexported fields
}
SQLiteStore is a SQLite-backed memory store.
func NewSQLiteStore ¶
func NewSQLiteStore(dbPath string, maxMessages int) (*SQLiteStore, error)
NewSQLiteStore creates a new SQLite-backed store.
func NewSQLiteStoreWithLogger ¶
func NewSQLiteStoreWithLogger(dbPath string, maxMessages int, logger *slog.Logger) (*SQLiteStore, error)
NewSQLiteStoreWithLogger creates a new SQLite-backed store and uses logger for non-fatal data-integrity warnings encountered while reading existing rows. Nil falls back to slog.Default.
func (*SQLiteStore) AddCompactionSummary ¶
func (s *SQLiteStore) AddCompactionSummary(conversationID, summary string) error
AddCompactionSummary adds a compaction summary message.
func (*SQLiteStore) AddMessage ¶
func (s *SQLiteStore) AddMessage(conversationID, role, content string) error
AddMessage adds a message to a conversation.
func (*SQLiteStore) ArchiveMessages ¶
func (s *SQLiteStore) ArchiveMessages(conversationID, sessionID, reason string) (int64, error)
ArchiveMessages updates messages in the unified table to archived status. This replaces the cross-DB copy that the legacy archive flow used.
func (*SQLiteStore) ArchiveToolCalls ¶
func (s *SQLiteStore) ArchiveToolCalls(conversationID, sessionID string) (int64, error)
ArchiveToolCalls updates tool calls in the unified table to archived status. This replaces the cross-DB copy that the legacy archive flow used.
func (*SQLiteStore) BindConversationChannel ¶
func (s *SQLiteStore) BindConversationChannel(conversationID string, binding *ChannelBinding) error
BindConversationChannel updates only the channel-binding portion of a conversation's typed metadata.
func (*SQLiteStore) Clear ¶
func (s *SQLiteStore) Clear(conversationID string) error
Clear removes a conversation and its messages.
func (*SQLiteStore) ClearToolCalls ¶
func (s *SQLiteStore) ClearToolCalls(conversationID string) error
ClearToolCalls deletes tool call records for a conversation from the working store. Called after archiving to prevent re-archival on the next session split.
func (*SQLiteStore) Close ¶
func (s *SQLiteStore) Close() error
Close closes the database connection.
func (*SQLiteStore) CompleteToolCall ¶
func (s *SQLiteStore) CompleteToolCall(toolCallID, result, errMsg string) error
CompleteToolCall records the result of a tool call.
func (*SQLiteStore) DB ¶
func (s *SQLiteStore) DB() *sql.DB
DB returns the underlying database connection for use by the unification migration and by ArchiveStore when reading from the unified messages table.
func (*SQLiteStore) GetAllConversations ¶
func (s *SQLiteStore) GetAllConversations() []*Conversation
GetAllConversations returns all conversations for checkpointing.
func (*SQLiteStore) GetAllMessages ¶
func (s *SQLiteStore) GetAllMessages(conversationID string) []Message
GetAllMessages retrieves ALL messages for a conversation, including compacted ones. Includes tool call data for full-fidelity archiving — never lose primary sources.
func (*SQLiteStore) GetConversation ¶
func (s *SQLiteStore) GetConversation(id string) *Conversation
GetConversation retrieves a conversation by ID.
func (*SQLiteStore) GetMessages ¶
func (s *SQLiteStore) GetMessages(conversationID string) []Message
GetMessages retrieves messages for a conversation.
func (*SQLiteStore) GetMessagesForCompaction ¶
func (s *SQLiteStore) GetMessagesForCompaction(conversationID string, keep int) []Message
GetMessagesForCompaction retrieves messages that should be compacted. Keeps the most recent 'keep' messages.
func (*SQLiteStore) GetOrCreateConversation ¶
func (s *SQLiteStore) GetOrCreateConversation(id string) (*Conversation, error)
GetOrCreateConversation ensures a conversation exists and returns it.
func (*SQLiteStore) GetTokenCount ¶
func (s *SQLiteStore) GetTokenCount(conversationID string) int
GetTokenCount returns the total token count for a conversation.
func (*SQLiteStore) GetToolCalls ¶
func (s *SQLiteStore) GetToolCalls(conversationID string, limit int) []ToolCall
GetToolCalls retrieves tool calls, optionally filtered by conversation. If conversationID is empty, returns all recent tool calls.
func (*SQLiteStore) GetToolCallsByName ¶
func (s *SQLiteStore) GetToolCallsByName(toolName string, limit int) []ToolCall
GetToolCallsByName retrieves tool calls filtered by tool name.
func (*SQLiteStore) MarkCompacted ¶
func (s *SQLiteStore) MarkCompacted(conversationID string, before time.Time) error
MarkCompacted marks messages as compacted.
func (*SQLiteStore) NeedsCompaction ¶
func (s *SQLiteStore) NeedsCompaction(conversationID string, maxTokens int) bool
NeedsCompaction checks if a conversation needs compaction.
func (*SQLiteStore) PutConversationMetadata ¶
func (s *SQLiteStore) PutConversationMetadata(conversationID string, metadata *ConversationMetadata) error
PutConversationMetadata replaces the typed metadata for a conversation, creating the conversation row if needed.
func (*SQLiteStore) RecordToolCall ¶
func (s *SQLiteStore) RecordToolCall(conversationID, messageID, toolCallID, toolName, arguments string) error
RecordToolCall records a tool call execution. messageID can be empty - it will be stored as NULL.
func (*SQLiteStore) Stats ¶
func (s *SQLiteStore) Stats() map[string]any
Stats returns memory statistics.
func (*SQLiteStore) ToolCallStats ¶
func (s *SQLiteStore) ToolCallStats() map[string]any
ToolCallStats returns statistics about tool usage.
type SearchOptions ¶
type SearchOptions struct {
Query string
ConversationID string // optional filter
SilenceThreshold time.Duration // gap that stops context expansion
MaxMessages int // hard cap per direction
MaxDuration time.Duration // time-based cap per direction
Limit int // max results
NoContext bool // if true, return matches only (no surrounding context)
}
SearchOptions configures a search query.
type SearchResult ¶
type SearchResult struct {
Match Message `json:"match"`
SessionID string `json:"session_id"`
ContextBefore []Message `json:"context_before"`
ContextAfter []Message `json:"context_after"`
Highlight string `json:"highlight,omitempty"`
}
SearchResult represents a search hit with surrounding context.
type SearchResultView ¶
type SearchResultView struct {
Match MessageView `json:"match"`
ContextBefore []MessageView `json:"context_before"`
ContextAfter []MessageView `json:"context_after"`
Highlight string `json:"highlight"`
}
SearchResultView is the JSON-facing projection of an archive search hit. Match is the message that matched; ContextBefore/ContextAfter are the surrounding messages in chronological order, bounded by the configured silence threshold.
type Session ¶
type Session struct {
ID string `json:"id"`
ConversationID string `json:"conversation_id"`
StartedAt time.Time `json:"started_at"`
EndedAt *time.Time `json:"ended_at,omitempty"`
EndReason string `json:"end_reason,omitempty"`
MessageCount int `json:"message_count"`
Summary string `json:"summary,omitempty"`
Title string `json:"title,omitempty"`
Tags []string `json:"tags,omitempty"`
Metadata *SessionMetadata `json:"metadata,omitempty"`
ParentSessionID string `json:"parent_session_id,omitempty"`
ParentToolCallID string `json:"parent_tool_call_id,omitempty"`
}
Session represents a conversation session with boundaries.
type SessionMetadata ¶
type SessionMetadata struct {
// ChannelBinding is the channel/contact binding snapshot active when
// the session was created. This preserves the security-policy
// identity view Thane had at the time for later forensics.
ChannelBinding *ChannelBinding `json:"channel_binding,omitempty"`
// Summaries at different lengths for different display contexts.
OneLiner string `json:"one_liner,omitempty"` // ~10 words
Paragraph string `json:"paragraph,omitempty"` // 2-4 sentences
Detailed string `json:"detailed,omitempty"` // full summary
// Key decisions or outcomes from the session.
KeyDecisions []string `json:"key_decisions,omitempty"`
// People involved or mentioned.
Participants []string `json:"participants,omitempty"`
// Characterization of the session's nature.
SessionType string `json:"session_type,omitempty"` // e.g. "debugging", "architecture", "philosophy", "casual"
// Tools used during the session (tool name → call count).
ToolsUsed map[string]int `json:"tools_used,omitempty"`
// Files touched or discussed during the session.
FilesTouched []string `json:"files_touched,omitempty"`
// Model(s) used, if known.
Models []string `json:"models,omitempty"`
// Legacy delegation execution details, preserved from the delegations
// table migration (#446). Only populated for imported delegation records.
Delegation *DelegationMetadata `json:"delegation,omitempty"`
}
SessionMetadata holds rich, LLM-generated metadata for human-oriented search and browsing. Stored as JSON in the database for flexibility — new fields can be added without schema migrations.
type SessionOption ¶
type SessionOption func(*Session)
SessionOption configures optional fields when starting a session.
func WithChannelBinding ¶
func WithChannelBinding(binding *ChannelBinding) SessionOption
WithChannelBinding snapshots the effective conversation/channel binding onto the archived session at creation time.
func WithParentSession ¶
func WithParentSession(id string) SessionOption
WithParentSession sets the parent session ID for a child session (e.g. a delegate spawned from a parent session).
func WithParentToolCall ¶
func WithParentToolCall(id string) SessionOption
WithParentToolCall sets the tool call ID that triggered this child session (e.g. the thane_now or thane_assign call in the parent).
type SessionView ¶
type SessionView struct {
ID string `json:"id"`
ConversationID string `json:"conversation_id"`
Started string `json:"started"`
Ended string `json:"ended"`
DurationSeconds int `json:"duration_seconds"`
Messages int `json:"messages"`
Title string `json:"title"`
Tags []string `json:"tags"`
Summary string `json:"summary"`
}
SessionView is the JSON-facing projection of an archived session. Field shape is stable across calls — empty strings and zero values are emitted explicitly rather than omitted, so the model can rely on schema invariants when comparing entries across turns.
Started/Ended are signed-second deltas via promptfmt.FormatDeltaOnly (e.g., "-7200s"). Ended is the empty string when the session is still active; DurationSeconds is 0 in the same case.
type SimpleSummarizer ¶
type SimpleSummarizer struct{}
SimpleSummarizer creates a basic summary without LLM (fallback).
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is an in-memory conversation memory backend. For persistent storage see SQLiteStore.
func (*Store) AddMessage ¶
AddMessage adds a message to a conversation.
func (*Store) BindConversationChannel ¶
func (s *Store) BindConversationChannel(conversationID string, binding *ChannelBinding) error
BindConversationChannel updates only the channel-binding portion of a conversation's typed metadata.
func (*Store) GetAllConversations ¶
func (s *Store) GetAllConversations() []*Conversation
GetAllConversations returns all conversations for checkpointing.
func (*Store) GetConversation ¶
func (s *Store) GetConversation(id string) *Conversation
GetConversation retrieves a conversation by ID. Returns nil if not found.
func (*Store) GetMessages ¶
GetMessages retrieves messages for a conversation. Returns empty slice if conversation doesn't exist.
func (*Store) GetOrCreateConversation ¶
func (s *Store) GetOrCreateConversation(id string) *Conversation
GetOrCreateConversation retrieves or creates a conversation.
func (*Store) GetTokenCount ¶
GetTokenCount returns estimated token count for a conversation.
func (*Store) PutConversationMetadata ¶
func (s *Store) PutConversationMetadata(conversationID string, metadata *ConversationMetadata) error
PutConversationMetadata replaces the typed metadata for a conversation, creating the conversation record if needed.
func (*Store) RestoreConversations ¶
func (s *Store) RestoreConversations(convs []*Conversation)
RestoreConversations replaces all conversations from a checkpoint.
type Summarizer ¶
type Summarizer interface {
Summarize(ctx context.Context, messages []Message, workingMemory string) (string, error)
}
Summarizer generates summaries from messages. When workingMemory is non-empty, it is included in the prompt so the summarizer preserves experiential context through compaction.
type SummarizerConfig ¶
type SummarizerConfig struct {
// Interval between periodic scans for unsummarized sessions.
// Default: 5 minutes.
Interval time.Duration
// Timeout per individual session summarization LLM call.
// Default: 60 seconds.
Timeout time.Duration
// PauseBetween is the delay between processing consecutive sessions
// to avoid overwhelming the LLM or starving interactive requests.
// Default: 5 seconds.
PauseBetween time.Duration
// BatchSize is the max number of unsummarized sessions to fetch per scan.
// Default: 10.
BatchSize int
// ModelPreference is a soft hint for which model to use.
// Passed as HintModelPreference to the router. If empty, the router
// picks freely based on other hints.
ModelPreference string
// IdleTimeout is the duration of inactivity after which an open
// session is silently closed by the summarizer. Zero disables
// idle session closing. The summarizer worker is the sole owner
// of session idle close — message-channel continuity across the
// rotation boundary is delivered via the message_channel context
// provider's verbatim tail, not an LLM-driven carry-forward.
IdleTimeout time.Duration
}
SummarizerConfig controls the summarizer worker behavior.
func DefaultSummarizerConfig ¶
func DefaultSummarizerConfig() SummarizerConfig
DefaultSummarizerConfig returns sensible defaults for the summarizer worker.
type SummarizerWorker ¶
type SummarizerWorker struct {
// contains filtered or unexported fields
}
SummarizerWorker periodically scans for unsummarized sessions and generates metadata (title, tags, summaries) using an LLM via the model router.
func NewSummarizerWorker ¶
func NewSummarizerWorker(store *ArchiveStore, llmClient llm.Client, rtr *router.Router, logger *slog.Logger, cfg SummarizerConfig) *SummarizerWorker
NewSummarizerWorker creates a summarizer worker. Call Start to begin processing.
func (*SummarizerWorker) SetInteractionCallback ¶
func (w *SummarizerWorker) SetInteractionCallback(cb InteractionCallback)
SetInteractionCallback registers a callback invoked after each successful session summarization. The callback receives the conversation ID, session ID, session end time, and LLM-generated topic tags, allowing callers to update contact interaction history without coupling the memory package to the contacts package.
func (*SummarizerWorker) Start ¶
func (w *SummarizerWorker) Start(ctx context.Context)
Start begins the background summarization worker. It performs an immediate scan on startup (to catch up on missed sessions), then scans periodically at the configured interval.
func (*SummarizerWorker) Stop ¶
func (w *SummarizerWorker) Stop()
Stop cancels the worker and waits for its goroutine to exit.
type ToolCall ¶
type ToolCall struct {
ID string `json:"id"`
MessageID string `json:"message_id"`
ConversationID string `json:"conversation_id"`
ToolName string `json:"tool_name"`
Arguments string `json:"arguments"`
Result string `json:"result,omitempty"`
Error string `json:"error,omitempty"`
StartedAt time.Time `json:"started_at"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
DurationMs int64 `json:"duration_ms,omitempty"`
}
ToolCall represents a recorded tool invocation.
type ToolCallArchiver ¶
type ToolCallArchiver interface {
ArchiveToolCalls(conversationID, sessionID string) (int64, error)
}
ToolCallArchiver sets lifecycle status on tool calls in the unified table.
type WorkingMemoryProvider ¶
type WorkingMemoryProvider struct {
// contains filtered or unexported fields
}
WorkingMemoryProvider implements [agent.TagContextProvider] for auto-injecting working memory into the system prompt. When the current conversation has working memory content, it is included under a "### Working Memory" heading so the agent has experiential continuity without needing to explicitly read it. Registered via [agent.Loop.RegisterAlwaysContextProvider].
func NewWorkingMemoryProvider ¶
func NewWorkingMemoryProvider(store *WorkingMemoryStore, convFunc func(context.Context) string) *WorkingMemoryProvider
NewWorkingMemoryProvider creates a context provider that auto-injects working memory for the current conversation. The convFunc parameter extracts the conversation ID from the request context — typically [tools.ConversationIDFromContext].
func (*WorkingMemoryProvider) TagContext ¶
func (p *WorkingMemoryProvider) TagContext(ctx context.Context, _ agentctx.ContextRequest) (string, error)
TagContext returns the working memory content for the current conversation, formatted for system prompt injection. Returns empty string if no working memory exists. Implements [agent.TagContextProvider]; registered via RegisterAlwaysContextProvider.
type WorkingMemoryReader ¶
WorkingMemoryReader is the subset of WorkingMemoryStore needed by the compactor. Defined as an interface for testability and to avoid coupling the compactor to the concrete store type.
type WorkingMemoryStore ¶
type WorkingMemoryStore struct {
// contains filtered or unexported fields
}
WorkingMemoryStore persists free-form working memory per conversation. Working memory captures experiential context that mechanical summarisation destroys: emotional tone, conversational arc, relationship temperature, and unresolved threads. The table lives in archive.db alongside session transcripts.
func NewWorkingMemoryStore ¶
func NewWorkingMemoryStore(db *sql.DB) (*WorkingMemoryStore, error)
NewWorkingMemoryStore creates a working memory store using the given database connection (typically from ArchiveStore.DB). It creates the working_memory table if it does not already exist.
func (*WorkingMemoryStore) Delete ¶
func (s *WorkingMemoryStore) Delete(conversationID string) error
Delete removes the working memory for a conversation.
func (*WorkingMemoryStore) Get ¶
Get returns the working memory content and last-updated timestamp for a conversation. If no working memory exists, it returns an empty string and zero time with no error.
func (*WorkingMemoryStore) Set ¶
func (s *WorkingMemoryStore) Set(conversationID, content string) error
Set writes or replaces the working memory content for a conversation.