ModelBox Open Source

/

AgentKit
Core Concepts/Sessions

Sessions

Manage conversation state and multi-turn interactions

|

Sessions manage conversation state between you and agents, enabling multi-turn conversations with context and memory. They handle message history, agent handoffs, and conversation continuity.

Quick Start

// Create a session for conversation state
session := session.New(agent)

// Have a multi-turn conversation
response1, _ := session.Run(ctx, []agent.ChatMessage{agent.NewUserMessage("Hi, my name is Alice")}, nil)
fmt.Println(response1.FinalOutput) // "Hello Alice! Nice to meet you."

response2, _ := session.Run(ctx, []agent.ChatMessage{agent.NewUserMessage("What's my name?")}, nil)  
fmt.Println(response2.FinalOutput) // "Your name is Alice."

Basic Usage

Single Agent Conversations

Maintain context across multiple interactions:

// Create agent and session
agent := agent.New().
    SetModel(model).
    SetSystemPrompt("You are a helpful coding assistant.")

session := session.New(agent)

// First question
response1, _ := session.Run(ctx, []agent.ChatMessage{agent.NewUserMessage("How do I create a slice in Go?")}, nil)

// Follow-up question with context
response2, _ := session.Run(ctx, []agent.ChatMessage{agent.NewUserMessage("Can you show me how to append to it?")}, nil)

// Agent remembers we were talking about Go slices
response3, _ := session.Run(ctx, []agent.ChatMessage{agent.NewUserMessage("What about removing elements?")}, nil)

Accessing Conversation History

Retrieve and analyze conversation messages:

session := session.New(agent)

// Have some conversations
session.Run(ctx, []agent.ChatMessage{agent.NewUserMessage("Tell me about Go")}, nil)
session.Run(ctx, []agent.ChatMessage{agent.NewUserMessage("What are its main features?")}, nil)

// Get the conversation history
messages := session.Messages()
fmt.Printf("Conversation has %d messages\n", len(messages))

for _, msg := range messages {
    fmt.Printf("%s: %s\n", msg.Role, msg.Content)
}

Agent Handoffs

Transfer conversations between specialized agents while maintaining context:

// Create specialized agents
codeReviewer := agent.New().
    SetModel(model).
    SetSystemPrompt("You are a code reviewer. Analyze code for bugs and improvements.")

tester := agent.New().
    SetModel(model).
    SetSystemPrompt("You generate comprehensive unit tests for code.")

documenter := agent.New().
    SetModel(model).
    SetSystemPrompt("You create clear documentation for code.")

// Start conversation with code reviewer
session := session.New(codeReviewer)
reviewResponse, _ := session.Run(ctx, []agent.ChatMessage{agent.NewUserMessage("Review this function: "+codeSnippet)}, nil)

// Hand off to test generator (keeps conversation context)
session.SetAgent(tester)
testResponse, _ := session.Run(ctx, []agent.ChatMessage{agent.NewUserMessage("Generate tests based on the review feedback")}, nil)

// Hand off to documenter (knows about code and tests)
session.SetAgent(documenter)
docResponse, _ := session.Run(ctx, []agent.ChatMessage{agent.NewUserMessage("Create documentation covering the reviewed code and tests")}, nil)

Session Configuration

Custom Session Options

Configure session behavior:

// Session with custom configuration
session := session.New(agent).
    WithMaxMessages(100).        // Limit conversation history
    WithSystemContext("Project: E-commerce API") // Add persistent context

Message Filtering

Control which messages are kept in history:

// Create session with message filtering
session := session.New(agent).
    WithMessageFilter(func(msg session.Message) bool {
        // Only keep user messages and final responses
        return msg.Role == "user" || (msg.Role == "assistant" && !msg.IsIntermediate)
    })

Streaming with Sessions

Handle real-time conversations with persistent state:

session := session.New(agent)

// Stream first response
stream1, _ := session.RunStreamed(ctx, []agent.ChatMessage{agent.NewUserMessage("Tell me a story about a robot")}, nil)
for event, err := range stream1.StreamEvents() {
    if err != nil {
        log.Fatal(err)
    }
    if event.IsTextDelta() {
        fmt.Print(event.GetContent())
    }
}
fmt.Println()

// Stream follow-up (remembers the story context)
stream2, _ := session.RunStreamed(ctx, []agent.ChatMessage{agent.NewUserMessage("What happens next in the story?")}, nil)
for event, err := range stream2.StreamEvents() {
    if err != nil {
        log.Fatal(err)
    }
    if event.IsTextDelta() {
        fmt.Print(event.GetContent())
    }
}

Advanced Patterns

Session Branching

Create conversation branches for different scenarios:

// Main conversation
mainSession := session.New(agent)
mainSession.Run(ctx, []agent.ChatMessage{agent.NewUserMessage("I'm building a web application")}, nil)

// Branch for different technical approaches
frontendBranch := mainSession.Branch()
frontendBranch.Run(ctx, []agent.ChatMessage{agent.NewUserMessage("Focus on React frontend architecture")}, nil)

backendBranch := mainSession.Branch()  
backendBranch.Run(ctx, []agent.ChatMessage{agent.NewUserMessage("Focus on Go backend architecture")}, nil)

// Both branches remember the original context about building a web app

Session Persistence

Save and restore conversation state:

// Save session state
session := session.New(agent)
session.Run(ctx, []agent.ChatMessage{agent.NewUserMessage("Help me plan a vacation to Japan")}, nil)

sessionData, err := session.Export()
if err != nil {
    log.Fatal(err)
}

// Store sessionData (JSON) to database/file
err = saveToDatabase(sessionData)

// Later: restore session
sessionData, err = loadFromDatabase()
if err != nil {
    log.Fatal(err)
}

restoredSession, err := session.Import(sessionData)
if err != nil {
    log.Fatal(err)
}

// Continue conversation where you left off
restoredSession.Run(ctx, []agent.ChatMessage{agent.NewUserMessage("What's the weather like in Tokyo in spring?")}, nil)

Multi-User Sessions

Handle conversations for different users:

type ConversationManager struct {
    sessions map[string]*session.Session
    agent    *agent.Agent
}

func NewConversationManager(agent *agent.Agent) *ConversationManager {
    return &ConversationManager{
        sessions: make(map[string]*session.Session),
        agent:    agent,
    }
}

func (cm *ConversationManager) HandleUserMessage(userID, message string) (string, error) {
    // Get or create session for user
    sess, exists := cm.sessions[userID]
    if !exists {
        sess = session.New(cm.agent)
        cm.sessions[userID] = sess
    }
    
    // Process message with user's conversation history
    response, err := sess.Run(ctx, []agent.ChatMessage{agent.NewUserMessage(message)}, nil)
    if err != nil {
        return "", err
    }
    
    return response.FinalOutput, nil
}

Session Cleanup

Manage memory usage for long-running applications:

type SessionManager struct {
    sessions map[string]*session.Session
    lastUsed map[string]time.Time
}

func (sm *SessionManager) CleanupOldSessions() {
    now := time.Now()
    for sessionID, lastUsed := range sm.lastUsed {
        // Remove sessions inactive for more than 1 hour
        if now.Sub(lastUsed) > time.Hour {
            delete(sm.sessions, sessionID)
            delete(sm.lastUsed, sessionID)
        }
    }
}

func (sm *SessionManager) GetSession(id string, agent *agent.Agent) *session.Session {
    sm.lastUsed[id] = time.Now()
    
    if sess, exists := sm.sessions[id]; exists {
        return sess
    }
    
    // Create new session
    sess := session.New(agent)
    sm.sessions[id] = sess
    return sess
}

Best Practices

Memory Management

Control conversation length to manage costs and performance:

// Limit conversation history
session := session.New(agent).
    WithMaxMessages(50)  // Keep last 50 messages

// Or use sliding window
session := session.New(agent).
    WithSlidingWindow(20) // Keep last 20 exchanges

Context Preservation

Keep important context while trimming history:

session := session.New(agent).
    WithContextPreservation(func(messages []session.Message) []session.Message {
        // Always keep system message and last 10 exchanges
        if len(messages) <= 21 { // 1 system + 20 user/assistant
            return messages
        }
        
        preserved := []session.Message{messages[0]} // Keep system message
        preserved = append(preserved, messages[len(messages)-20:]...) // Keep last 20
        return preserved
    })

Error Handling

Handle session-related errors gracefully:

response, err := session.Run(ctx, []agent.ChatMessage{agent.NewUserMessage(userMessage)}, nil)
if err != nil {
    switch {
    case errors.Is(err, session.ErrMaxMessagesExceeded):
        // Handle conversation limit
        session.Reset()
        return "Conversation history cleared. Please repeat your question."
        
    case errors.Is(err, session.ErrInvalidState):
        // Handle corrupted session
        session = session.New(agent)
        return "Session reset due to error. How can I help you?"
        
    default:
        return fmt.Sprintf("Error: %v", err)
    }
}

Performance Optimization

Optimize for high-throughput scenarios:

// Use session pools for high concurrency
type SessionPool struct {
    sessions chan *session.Session
}

func NewSessionPool(size int, agent *agent.Agent) *SessionPool {
    pool := &SessionPool{
        sessions: make(chan *session.Session, size),
    }
    
    // Pre-populate pool
    for i := 0; i < size; i++ {
        pool.sessions <- session.New(agent)
    }
    
    return pool
}

func (sp *SessionPool) Get(agent *agent.Agent) *session.Session {
    select {
    case sess := <-sp.sessions:
        return sess
    default:
        // Pool empty, create new session
        return session.New(agent)
    }
}

func (sp *SessionPool) Put(sess *session.Session) {
    select {
    case sp.sessions <- sess:
        // Session returned to pool
    default:
        // Pool full, let session be garbage collected
    }
}

Error Handling

Common session errors and how to handle them:

response, err := session.Run(ctx, []agent.ChatMessage{agent.NewUserMessage(message)}, nil)
if err != nil {
    switch {
    case errors.Is(err, session.ErrContextTooLong):
        // Trim conversation history
        session.TrimToTokenLimit(4000)
        response, err = session.Run(ctx, []agent.ChatMessage{agent.NewUserMessage(message)}, nil)
        
    case errors.Is(err, session.ErrInvalidMessage):
        // Handle malformed message
        log.Printf("Invalid message format: %v", err)
        
    case errors.Is(err, session.ErrSessionExpired):
        // Create new session
        session = session.New(agent)
        response, err = session.Run(ctx, []agent.ChatMessage{agent.NewUserMessage(message)}, nil)
    }
}

Next Steps

  • Agents - Learn how to create agents that work with sessions
  • Models - Configure models for optimal conversation performance
  • Tools - Add function calling to conversational agents
  • Streaming - Implement real-time conversational interfaces
Edit on GitHub