Session

Session enables persistent storage and recovery of Agent state, allowing conversations to maintain continuity across application runs.


Core Features

  • Persistent Storage: Save Agent, Memory, and other component states

  • Simple API: Call saveTo() / loadFrom() directly on Agent

  • Multiple Backends: Supports JSON files, in-memory, and custom storage

  • Flexible Identification: Use simple string session IDs or custom SessionKey


Quick Start

import io.agentscope.core.session.JsonSession;
import io.agentscope.core.session.Session;
import java.nio.file.Path;

// 1. Create components
InMemoryMemory memory = new InMemoryMemory();
ReActAgent agent = ReActAgent.builder()
    .name("Assistant")
    .model(model)
    .memory(memory)
    .build();

// 2. Create Session and load existing session
Path sessionPath = Path.of(System.getProperty("user.home"), ".agentscope", "sessions");
Session session = new JsonSession(sessionPath);
agent.loadIfExists(session, "userId");

// 3. Use Agent
Msg response = agent.call(userMsg).block();

// 4. Save session
agent.saveTo(session, "userId");

Session Implementations

AgentScope provides two Session implementations:

Implementation

Persistence

Use Case

JsonSession

File system

Production, cross-restart persistence

InMemorySession

Memory

Testing, single-process temporary storage

InMemorySession

Stores state in memory, suitable for testing and single-process temporary scenarios.

import io.agentscope.core.session.InMemorySession;

// Create in-memory session (typically used as singleton)
InMemorySession session = new InMemorySession();

// Save
agent.saveTo(session, "user123");

// Load
agent.loadIfExists(session, "user123");

// Management features
session.listSessionKeys();  // Get all session keys

Notes:

  • State is lost when application restarts

  • Not suitable for distributed environments

  • Memory usage grows with session count


Agent State Management API

Save Operations

// Save session state
agent.saveTo(session, "sessionId");

Load Operations

// Load session (silently skip if doesn't exist, returns false)
boolean loaded = agent.loadIfExists(session, "sessionId");

// Load session (throw exception if doesn't exist)
agent.loadFrom(session, "sessionId");

Session Management Operations

import io.agentscope.core.state.SimpleSessionKey;

// Check if session exists
boolean exists = session.exists(SimpleSessionKey.of("sessionId"));

// Delete session
session.delete(SimpleSessionKey.of("sessionId"));

// List all sessions
Set<SessionKey> keys = session.listSessionKeys();

Complete Example

import io.agentscope.core.ReActAgent;
import io.agentscope.core.memory.InMemoryMemory;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
import io.agentscope.core.message.TextBlock;
import io.agentscope.core.session.JsonSession;
import io.agentscope.core.session.Session;
import java.nio.file.Path;

public class SessionExample {
    public static void main(String[] args) {
        // Create components
        InMemoryMemory memory = new InMemoryMemory();
        ReActAgent agent = ReActAgent.builder()
            .name("Assistant")
            .model(model)
            .memory(memory)
            .build();

        // Set up session
        String sessionId = "user_session_001";
        Path sessionPath = Path.of(System.getProperty("user.home"), ".agentscope", "sessions");
        Session session = new JsonSession(sessionPath);

        // Load existing session (if exists)
        if (agent.loadIfExists(session, sessionId)) {
            System.out.println("Loaded session: " + sessionId);
        } else {
            System.out.println("New session: " + sessionId);
        }

        // Use Agent
        Msg userMsg = Msg.builder()
            .role(MsgRole.USER)
            .content(TextBlock.builder().text("Hello!").build())
            .build();
        Msg response = agent.call(userMsg).block();

        // Save session
        agent.saveTo(session, sessionId);
        System.out.println("Session saved");
    }
}

Custom Session

Implement the Session interface to create custom storage backends:

import io.agentscope.core.session.Session;
import io.agentscope.core.state.SessionKey;
import io.agentscope.core.state.State;
import java.util.List;
import java.util.Optional;
import java.util.Set;

public class DatabaseSession implements Session {
    @Override
    public void save(SessionKey sessionKey, String key, State value) {
        // Save single state to database
    }

    @Override
    public void save(SessionKey sessionKey, String key, List<? extends State> values) {
        // Save state list to database
    }

    @Override
    public <T extends State> Optional<T> get(SessionKey sessionKey, String key, Class<T> type) {
        // Get single state from database
        return Optional.empty();
    }

    @Override
    public <T extends State> List<T> getList(SessionKey sessionKey, String key, Class<T> itemType) {
        // Get state list from database
        return List.of();
    }

    @Override
    public boolean exists(SessionKey sessionKey) {
        // Check if session exists
        return false;
    }

    @Override
    public void delete(SessionKey sessionKey) {
        // Delete session
    }

    @Override
    public Set<SessionKey> listSessionKeys() {
        // List all sessions
        return Set.of();
    }

    @Override
    public void close() {
        // Close connection
    }
}

// Usage
Session session = new DatabaseSession(dbConnection);
agent.saveTo(session, "user123");

Data Format and Migration

Old vs New Data Format Comparison

The new Session API uses a completely different storage format that is incompatible with the old SessionManager approach:

Feature

Old Format (SessionManager)

New Format (saveTo/loadFrom)

Storage Structure

Single JSON file

Directory + multiple files

File Path

sessions/{sessionId}.json

sessions/{sessionId}/ directory

State Storage

Nested Map structure

Independent typed files

List Data

JSON array

JSONL format (incremental append)

Old Format Example

sessions/
└── user123.json          # Single JSON file containing all state

File content:

{
  "agent": {
    "name": "Assistant",
    "iteration": 5
  },
  "memory": {
    "messages": [
      {"role": "user", "content": "Hello"},
      {"role": "assistant", "content": "Hi!"}
    ]
  }
}

New Format Example

sessions/
└── user123/              # One directory per session
    ├── memory_messages.jsonl    # Message list (JSONL format)
    └── plan_notebook.json       # Plan state (JSON format)

memory_messages.jsonl content (one message per line):

{"role":"USER","name":"user","content":[{"type":"text","text":"Hello"}]}
{"role":"ASSISTANT","name":"Assistant","content":[{"type":"text","text":"Hi!"}]}

Data Migration

Due to format incompatibility, old data cannot be directly read by the new API. If migration is needed, follow these steps:

Option 2: Manual Migration

If you need to preserve historical conversation data, write a migration script:

import com.fasterxml.jackson.databind.ObjectMapper;
import io.agentscope.core.memory.InMemoryMemory;
import io.agentscope.core.message.Msg;
import io.agentscope.core.session.JsonSession;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;

public class SessionMigration {
    public static void migrate(Path oldSessionFile, Path newSessionDir) throws Exception {
        ObjectMapper mapper = new ObjectMapper();

        // 1. Read old format data
        String json = Files.readString(oldSessionFile);
        Map<String, Object> oldData = mapper.readValue(json, Map.class);

        // 2. Extract message history
        Map<String, Object> memoryData = (Map<String, Object>) oldData.get("memory");
        List<Map<String, Object>> messages = (List<Map<String, Object>>) memoryData.get("messages");

        // 3. Create new Memory and populate data
        InMemoryMemory memory = new InMemoryMemory();
        for (Map<String, Object> msgData : messages) {
            // Build Msg object based on old data structure
            Msg msg = convertToMsg(msgData);
            memory.addMessage(msg);
        }

        // 4. Save using new API
        JsonSession session = new JsonSession(newSessionDir);
        String sessionId = oldSessionFile.getFileName().toString().replace(".json", "");
        memory.saveTo(session, sessionId);

        System.out.println("Migration complete: " + sessionId);
    }

    private static Msg convertToMsg(Map<String, Object> msgData) {
        // Implement conversion logic based on actual old data structure
        // ...
    }
}

Option 3: Dual Version Parallel

During transition, keep old code for reading historical data while using new API for new sessions:

// Check if old format data exists
Path oldFile = sessionPath.resolve(sessionId + ".json");
Path newDir = sessionPath.resolve(sessionId);

if (Files.exists(newDir)) {
    // Load using new API
    agent.loadIfExists(session, sessionId);
} else if (Files.exists(oldFile)) {
    // Old format exists, perform migration
    migrateOldSession(oldFile, sessionPath);
    agent.loadIfExists(session, sessionId);
}

Database Backend Migration

MySQL Table Structure Change

The new API uses a different table structure. If you previously used MysqlSession, you need to migrate the table structure:

Old Table Structure:

CREATE TABLE agentscope_sessions (
    session_id VARCHAR(255) PRIMARY KEY,
    state_data JSON NOT NULL,
    created_at TIMESTAMP,
    updated_at TIMESTAMP
);

New Table Structure:

CREATE TABLE agentscope_sessions (
    session_id VARCHAR(255) NOT NULL,
    state_key VARCHAR(255) NOT NULL,
    item_index INT NOT NULL DEFAULT 0,
    state_data LONGTEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (session_id, state_key, item_index)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

The item_index column enables true incremental list storage:

  • Single states: stored with item_index = 0

  • List states: each item stored in a separate row with item_index = 0, 1, 2, ...

Migration Steps:

  1. Backup old data:

CREATE TABLE agentscope_sessions_backup AS SELECT * FROM agentscope_sessions;
  1. Drop old table:

DROP TABLE agentscope_sessions;
  1. Recreate (use createIfNotExist=true for automatic creation):

MysqlSession session = new MysqlSession(dataSource, true);

Or manually execute the new table structure SQL above.

Redis Storage Structure Change

The new API uses a different Redis key structure:

Old Structure:

agentscope:session:{sessionId}  -> JSON string (all states)

New Structure:

agentscope:session:{sessionId}:{stateKey}       -> JSON string (single state)
agentscope:session:{sessionId}:{stateKey}:list  -> Redis List (list state)
agentscope:session:{sessionId}:_keys            -> Redis Set (state index)

Migration Steps:

  1. Clear old data (if not needed):

redis-cli KEYS "agentscope:session:*" | xargs redis-cli DEL
  1. New data will automatically use the new structure.


More Resources