Add conversation memory so agents remember previous interactions.

Overview

Sessions store conversation history across multiple agent runs. Without sessions, each run starts with no context. With sessions, agents remember previous messages, creating coherent multi-turn conversations.

Two session implementations:

  • MemorySession: In-memory storage for development and testing
  • SQLiteSession: Persistent SQLite storage for production

Basic Session Usage

Create a session and pass it via RunConfig:

// Create a simple agent
Agent<UnknownContext, TextOutput> agent =
    Agent.<UnknownContext, TextOutput>builder()
        .name("Assistant")
        .instructions("You are a helpful assistant with a good memory.")
        .build();

// Create an in-memory session
Session session = new MemorySession("conversation-123");

// Create RunConfig with the session
RunConfig config = RunConfig.builder().session(session).build();

// Turn 1
RunResult<UnknownContext, ?> result1 = Runner.run(
    agent,
    "My name is Alice and I love hiking.",
    config
);

// Turn 2 - agent remembers Alice
RunResult<UnknownContext, ?> result2 = Runner.run(
    agent,
    "What's my name?",
    config
);

System.out.println(result2.getFinalOutput());
// Output: "Your name is Alice."

The session automatically stores all messages and tool calls, maintaining full conversation context.

MemorySession

In-memory session for development and short-lived conversations.

Creating a MemorySession

// Create with explicit session ID
Session session = new MemorySession("conversation-123");

// Or generate random ID
Session session = new MemorySession();

When to Use MemorySession

Use for:

  • Development and testing
  • Short-lived conversations (single request lifecycle)
  • Prototyping and experimentation
  • Applications where persistence isn't needed

Don't use for:

  • Production applications requiring persistence
  • Long-running conversations
  • Multi-instance deployments
  • Conversations that must survive restarts

Example: Multi-Turn Conversation

Agent<UnknownContext, TextOutput> agent =
    Agent.<UnknownContext, TextOutput>builder()
        .name("Assistant")
        .instructions("Remember details from the conversation naturally.")
        .build();

Session session = new MemorySession("user-session");
RunConfig config = RunConfig.builder().session(session).build();

// Turn 1: User introduces themselves
RunResult<UnknownContext, ?> result1 = Runner.run(
    agent,
    "My name is Alice and I love hiking.",
    config
);
System.out.println("Agent: " + result1.getFinalOutput());

// Turn 2: Ask unrelated question
RunResult<UnknownContext, ?> result2 = Runner.run(
    agent,
    "What's the capital of France?",
    config
);
System.out.println("Agent: " + result2.getFinalOutput());

// Turn 3: Agent remembers name
RunResult<UnknownContext, ?> result3 = Runner.run(
    agent,
    "What's my name?",
    config
);
System.out.println("Agent: " + result3.getFinalOutput());
// Output: "Your name is Alice."

// Turn 4: Agent remembers hobby
RunResult<UnknownContext, ?> result4 = Runner.run(
    agent,
    "What hobby did I mention?",
    config
);
System.out.println("Agent: " + result4.getFinalOutput());
// Output: "You mentioned that you love hiking."

// Session statistics
System.out.println("Total items in history: " + session.getItems(null).join().size());

View complete example →

Data Loss

MemorySession data is lost when the JVM exits. Use SQLiteSession for persistence.

SQLiteSession

Persistent session storage using SQLite database.

Creating a SQLiteSession

Path dbPath = Path.of("./conversations.db");
String sessionId = "alice-conversation";

// Create or open SQLite session
try (SQLiteSession session = SQLiteSession.fromFile(sessionId, dbPath)) {
    // Use session...
}

The database file is created automatically if it doesn't exist.

When to Use SQLiteSession

Use for:

  • Production applications requiring persistence
  • Long-running conversations
  • Multi-session management (multiple users/conversations)
  • Conversation history across application restarts
  • Audit trails and conversation analysis

Features:

  • WAL mode for better concurrency
  • Session isolation (multiple sessions in one database)
  • Transaction support for data integrity
  • Automatic schema management

Example: Persistent Conversations

Path dbPath = Path.of("./example-conversations.db");
String sessionId = "alice-conversation";

Agent<UnknownContext, TextOutput> agent =
    Agent.<UnknownContext, TextOutput>builder()
        .name("PersistentAssistant")
        .instructions("Remember all details from previous conversations.")
        .build();

try (SQLiteSession session = SQLiteSession.fromFile(sessionId, dbPath)) {
    // Check existing history
    List<AgentInputItem> existingHistory = session.getItems(null).join();
    if (!existingHistory.isEmpty()) {
        System.out.println("Found " + existingHistory.size() + " items in conversation history");
        System.out.println("This is a continuation of a previous conversation.");
    }

    // Create RunConfig with the session
    RunConfig config = RunConfig.builder().session(session).build();

    if (existingHistory.isEmpty()) {
        // First conversation
        System.out.println("Turn 1:");
        RunResult<UnknownContext, ?> result1 = Runner.run(
            agent,
            "Hi! My name is Alice, I'm a software engineer, and I love rock climbing.",
            config
        );
        System.out.println("Agent: " + result1.getFinalOutput());

        System.out.println("\nTurn 2:");
        RunResult<UnknownContext, ?> result2 = Runner.run(
            agent,
            "I work mostly with Java and Python.",
            config
        );
        System.out.println("Agent: " + result2.getFinalOutput());

        System.out.println("\nRun this example again to see persistence!");

    } else {
        // Continuation - test memory
        System.out.println("Turn 1 (testing memory):");
        RunResult<UnknownContext, ?> result1 = Runner.run(
            agent,
            "Hi again! Do you remember me?",
            config
        );
        System.out.println("Agent: " + result1.getFinalOutput());

        System.out.println("\nTurn 2:");
        RunResult<UnknownContext, ?> result2 = Runner.run(
            agent,
            "What programming languages do I use?",
            config
        );
        System.out.println("Agent: " + result2.getFinalOutput());
        // Output: "You mentioned that you work with Java and Python."
    }

    System.out.println("\nTotal items in history: " + session.getItems(null).join().size());
}

Run this example multiple times - the agent remembers details across program executions.

View complete example →

Session Operations

Both session types implement the Session interface:

Get Session ID

CompletableFuture<String> sessionIdFuture = session.getSessionId();
String sessionId = sessionIdFuture.join();

Get Conversation History

// Get all items
CompletableFuture<List<AgentInputItem>> itemsFuture = session.getItems(null);
List<AgentInputItem> items = itemsFuture.join();

System.out.println("Total items: " + items.size());

Add Items Manually

// Create a message item
AgentInputItem message = /* ... */;

// Add to session
session.addItems(List.of(message)).join();

Remove Last Item

// Remove the most recent item
session.popItem().join();

Clear Session

// Clear all conversation history
session.clearSession().join();

Sessions with Tools

Sessions store tool calls and results, maintaining full execution context:

Agent<UnknownContext, TextOutput> agent =
    Agent.<UnknownContext, TextOutput>builder()
        .name("Assistant")
        .instructions("Use tools to help answer questions.")
        .tools(List.of(new CalculatorTool(), new WeatherTool()))
        .build();

Session session = new MemorySession("tool-conversation");
RunConfig config = RunConfig.builder().session(session).build();

// Turn 1: Agent uses calculator
RunResult<UnknownContext, ?> result1 = Runner.run(
    agent,
    "What is 25 * 17?",
    config
);
System.out.println(result1.getFinalOutput());

// Turn 2: Agent references previous calculation
RunResult<UnknownContext, ?> result2 = Runner.run(
    agent,
    "Double that number",
    config
);
System.out.println(result2.getFinalOutput());
// Agent remembers the result (425) and calculates 425 * 2 = 850

Tool calls, inputs, and outputs are all stored in the session.

Sessions with Handoffs

Sessions preserve context across agent handoffs:

Agent<UnknownContext, TextOutput> billingAgent = /* ... */;
Agent<UnknownContext, TextOutput> supportAgent = /* ... */;

Agent<UnknownContext, TextOutput> triageAgent =
    Agent.<UnknownContext, TextOutput>builder()
        .name("Triage")
        .instructions("Route to appropriate specialist.")
        .handoffs(List.of(billingAgent, supportAgent))
        .build();

Session session = new MemorySession("customer-conversation");
RunConfig config = RunConfig.builder().session(session).build();

// Turn 1: Routes to billing specialist
RunResult<UnknownContext, ?> result1 = Runner.run(
    triageAgent,
    "I was charged incorrectly for my subscription.",
    config
);

// Turn 2: Routes to support, but specialist sees full history
RunResult<UnknownContext, ?> result2 = Runner.run(
    triageAgent,
    "Also, the app is crashing when I try to view my invoices.",
    config
);

// Both specialists have access to full conversation history

Specialists can reference earlier parts of the conversation, even from before the handoff.

Managing Multiple Sessions

Single Database, Multiple Sessions

Path dbPath = Path.of("./all-conversations.db");

// User 1's conversation
try (SQLiteSession session1 = SQLiteSession.fromFile("user-alice", dbPath)) {
    RunConfig config1 = RunConfig.builder().session(session1).build();
    Runner.run(agent, "Alice's question", config1);
}

// User 2's conversation (same database, different session)
try (SQLiteSession session2 = SQLiteSession.fromFile("user-bob", dbPath)) {
    RunConfig config2 = RunConfig.builder().session(session2).build();
    Runner.run(agent, "Bob's question", config2);
}

// Sessions are isolated - Alice and Bob have separate conversation histories

Session Cleanup

Delete old sessions to manage database size:

try (SQLiteSession session = SQLiteSession.fromFile(sessionId, dbPath)) {
    // Clear conversation history
    session.clearSession().join();
}

// Or delete the entire database file
Files.deleteIfExists(dbPath);

Thread Safety

Both MemorySession and SQLiteSession are thread-safe:

Session session = new MemorySession("shared-session");
RunConfig config = RunConfig.builder().session(session).build();

// Safe to use from multiple threads
ExecutorService executor = Executors.newFixedThreadPool(5);

for (int i = 0; i < 10; i++) {
    int turnNumber = i;
    executor.submit(() -> {
        RunResult<UnknownContext, ?> result = Runner.run(
            agent,
            "Question " + turnNumber,
            config
        );
        System.out.println("Turn " + turnNumber + ": " + result.getFinalOutput());
    });
}

executor.shutdown();

SQLiteSession uses connection pooling and WAL mode for concurrent access.

Best Practices

Development vs Production

  • Development: Use MemorySession for simplicity and speed
  • Production: Use SQLiteSession for data persistence
  • Testing: Use MemorySession for isolated test cases
  • Long-Running: Use SQLiteSession for multi-day conversations

Session Management

  • Unique IDs: Use meaningful session IDs (user ID, conversation ID)
  • Lifecycle: Match session lifecycle to conversation lifecycle
  • Cleanup: Periodically clear old or abandoned sessions
  • Size Monitoring: Monitor database size for long conversations
  • Isolation: One session per conversation/user for clean separation

Performance

  • Connection Reuse: Reuse SQLiteSession instances when possible
  • Batch Operations: Add multiple items at once when applicable
  • Index Strategy: SQLiteSession automatically indexes session_id
  • WAL Mode: SQLiteSession uses WAL mode for better concurrency
  • History Limits: Consider truncating very long conversation histories

Common Mistakes

  • Sharing Sessions: Don't share sessions across unrelated conversations
  • Missing try-with-resources: Always close SQLiteSession (use try-with-resources)
  • Lost Context: Forgetting to pass session via RunConfig
  • Memory Leaks: Not cleaning up MemorySession references
  • File Permissions: Ensure write permissions for SQLiteSession database path

Comparing Session Types

Feature MemorySession SQLiteSession
Persistence No (lost on exit) Yes (survives restarts)
Performance Fast (in-memory) Good (disk I/O)
Concurrency Thread-safe Thread-safe + WAL
Storage Limit JVM memory Disk space
Setup None required Database file
Use Case Dev/testing Production
Cleanup Automatic (GC) Manual
Multi-session Separate instances Single database

Next Steps

  • Running Agents - Use RunConfig to pass sessions
  • Handoffs - Maintain context across agent handoffs
  • Run Context - Combine sessions with custom context
  • Tools - Store tool interactions in sessions

Additional Resources