Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/GuaiZai233/FrostAgent/llms.txt

Use this file to discover all available pages before exploring further.

FrostAgent gives every user and every group chat its own isolated conversation history. A session holds the ordered list of ChatMessage objects that the Engine sends to the LLM on each turn, so the model has the context it needs to give coherent, context-aware replies. Sessions live in memory, are automatically created on first use, and are evicted after 24 hours of inactivity.

SessionManager

SessionManager (defined in internal/llm/session.go) is the registry for all active sessions. The Engine holds a single *SessionManager instance for its entire lifetime.

Creating a manager

sm := llm.NewSessionManager()
NewSessionManager returns a manager with sensible defaults and immediately starts a background cleanup goroutine:
func NewSessionManager() *SessionManager {
    sm := &SessionManager{
        sessions:   make(map[string]*SessionContext),
        MaxHistory: 20,
        TTL:        24 * time.Hour,
    }
    go sm.startCleanupRoutine()
    return sm
}
FieldDefaultPurpose
MaxHistory20Maximum non-system messages kept per session after each RunWithSession call.
TTL24hSessions not updated within this window are removed by the cleanup routine.

Methods

GetOrCreate(sessionID string) *SessionContext Returns the existing session for sessionID, or creates and stores a new one if it does not exist yet. Uses a double-checked locking pattern — a read lock is tried first; only on a miss is a write lock acquired.
session := sm.GetOrCreate("group:123456789")
Count() int Returns the current number of live sessions.
fmt.Printf("Active sessions: %d\n", sm.Count())
ListSessions(offset, limit int) []*SessionContext Returns a paginated slice of session contexts. Ordering depends on map iteration order (non-deterministic). Pass limit <= 0 to retrieve all sessions from offset to the end.
page := sm.ListSessions(0, 10) // first 10 sessions
Cleanup() Iterates every session and deletes those whose UpdatedAt is older than TTL. Called automatically by the background goroutine every hour; can also be called manually for testing or forced eviction. Delete(sessionID string) Removes a specific session immediately, regardless of its age.
sm.Delete("private:42")

SessionContext

SessionContext is the per-session object. It holds the conversation history and exposes thread-safe methods for reading and writing it.
type SessionContext struct {
    ConversationID string
    History        []ChatMessage
    CreatedAt      time.Time
    UpdatedAt      time.Time
    mu             sync.Mutex
}

Locking

Lock() and Unlock() give external callers explicit control over the session mutex. The Engine’s RunWithSession uses them to guard the full read-modify-write cycle:
session.Lock()
defer session.Unlock()
// safe to read/modify session.History here
Never call Snapshot(), ReplaceMessages(), AddMessage(), Messages(), or Clear() while already holding the session lock from an external Lock() call — those methods all acquire the same sync.Mutex and will deadlock.

Snapshot() []ChatMessage

Returns a deep copy of History. ToolCalls slices inside each message are independently copied so mutations to the snapshot cannot corrupt the stored history.
snapshot := session.Snapshot() // safe to pass to RunMessages

ReplaceMessages(messages []ChatMessage)

Atomically replaces the entire history with a deep copy of the supplied slice and updates UpdatedAt. Used to write trimmed history back after a RunWithSession call.

AddMessage(msg core.ChatMessage)

Appends a single core.ChatMessage (converting it to the internal llm.ChatMessage representation) under the session lock and bumps UpdatedAt. Used by the OneBot adapter to record user and assistant messages independently of the Engine loop.
session.AddMessage(core.ChatMessage{
    Role:    core.RoleUser,
    Content: "Hello!",
})

Messages() []core.ChatMessage

Returns the full history as a []core.ChatMessage slice, converting from the internal representation. Acquires the session lock internally.

Clear()

Resets History to nil and updates UpdatedAt. Useful for implementing user-facing “forget this conversation” commands.
session.Clear()

Session IDs

Session identifiers are string keys assigned by the OneBot adapter in historyKey:
func historyKey(event model.OneBotEvent) string {
    if event.MessageType == "group" {
        return fmt.Sprintf("group:%d", event.GroupID)
    }
    return fmt.Sprintf("private:%d", event.UserID)
}
FormatUsed for
group:<group_id>Group chat — all members share one context window
private:<user_id>Direct message — one context window per user
Because all group members share the same session, the LLM can reference earlier messages from other users in the same group. If you need per-member isolation within a group, derive a session ID such as group:<group_id>:user:<user_id> and pass it to GetOrCreate directly.

Context Trimming

Two utility functions in internal/llm/context.go help manage message length independently of the Engine’s session-write trimmer.

TrimMessages(messages []ChatMessage, limit int) []ChatMessage

Keeps all system-role messages and trims non-system messages to the most recent limit entries. Unlike the Engine’s trimMessagesForSession, this function does not inspect tool-call chains, so use it only when you are certain the trim boundary will not split a tool call / tool result pair.
const DefaultMaxMessages = 20

trimmed := llm.TrimMessages(history, llm.DefaultMaxMessages)

ApproxTokenCount(messages []ChatMessage) int

Provides a lightweight token estimate for a message slice. It adds 4 tokens of overhead per message (for role and structural tokens) and estimates content length at roughly 1 token per 4 Unicode runes. Tool call fields (ID, Type, function name, arguments) and ToolCallID are included in the estimate.
tokens := llm.ApproxTokenCount(history)
if tokens > 4000 {
    history = llm.TrimMessages(history, 10)
}
ApproxTokenCount is a heuristic, not an exact tokeniser. For English text the estimate is typically within 10–15 % of the actual BPE token count. For Chinese text the rune-based estimate can be less accurate; validate against your model’s context limit if you hit truncation errors.

TTL Cleanup

startCleanupRoutine runs in a background goroutine started by NewSessionManager. It ticks every hour and calls Cleanup:
func (sm *SessionManager) startCleanupRoutine() {
    ticker := time.NewTicker(1 * time.Hour)
    for range ticker.C {
        sm.Cleanup()
    }
}
Cleanup acquires the manager write lock, then briefly locks each session to read its UpdatedAt field. Sessions older than TTL (24 h by default) are deleted from the map. The cleanup routine does not stop when sessions are empty — it runs for the lifetime of the process.

Example: Stateful Session with RunWithSession

engine := &llm.Engine{
    MaxIterations:  5,
    Provider:       openai.NewClient(endpoint, apiKey),
    ModelName:      "gpt-4o",
    ToolRegistry:   executorMap,
    SessionManager: llm.NewSessionManager(),
}

// First turn — session is created automatically
reply1 := engine.RunWithSession("private:42", "My name is Alex.")
fmt.Println(reply1) // "Nice to meet you, Alex! How can I help?"

// Second turn — history is loaded from the session
reply2 := engine.RunWithSession("private:42", "What's my name?")
fmt.Println(reply2) // "Your name is Alex."
Each call to RunWithSession appends the user message to the stored history, runs the loop, and writes back a trimmed snapshot — so the next call automatically picks up where the last one left off.

Build docs developers (and LLMs) love