Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/EllisYuan/ChatAgents/llms.txt

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

ChatAgents persists every conversation to disk so that your history survives server restarts and container redeploys. Each conversation is assigned a unique UUID as its thread_id. That ID is used both by LangGraph’s in-memory checkpointer (for multi-turn context) and by SessionManager (for durable JSON storage). All session files live under data/sessions/ and are indexed by a single lightweight index.json file, making it fast to list sessions without reading every individual file.

Session data structure

Each conversation is stored as a JSON file at data/sessions/<thread_id>.json. The structure mirrors what SessionManager.create_session() and SessionManager.save_session() write to disk:
{
  "session_id": "<uuid>",
  "title": "What is LangGraph?",
  "created_at": "2024-01-01T12:00:00",
  "updated_at": "2024-01-01T12:05:00",
  "messages": [
    {
      "role": "user",
      "content": "What is LangGraph?",
      "timestamp": "2024-01-01T12:00:00"
    },
    {
      "role": "assistant",
      "content": "LangGraph is a library for building stateful, multi-actor applications with LLMs...",
      "timestamp": "2024-01-01T12:05:00",
      "tool_calls": [
        {
          "tool_name": "TavilySearch",
          "tool_type": "search",
          "content": { "query": "LangGraph overview" }
        }
      ]
    }
  ]
}
tool_calls is only present on assistant messages where the agent invoked a Tavily tool during that turn.

Title auto-generation

When a new conversation is started and no explicit title is provided, SessionManager.auto_generate_title() derives the title from the very first user message:
def auto_generate_title(self, first_message: str, max_length: int = 30) -> str:
    title = first_message.strip()
    title = title.replace("\n", " ").replace("\r", " ")
    if len(title) > max_length:
        title = title[:max_length] + "..."
    return title or "新对话"
  • Whitespace is stripped and newlines are collapsed to spaces.
  • If the message is longer than 30 characters, it is truncated and ... is appended.
  • If the message is empty, the fallback title "新对话" (New Conversation) is used.
The title is set once at session creation time inside app.py’s stream_agent endpoint, immediately after the first streaming response completes.

Index file

data/sessions/index.json is a lightweight catalogue of all sessions. It is updated on every create_session, save_session, delete_session, and rename_session call so that get_sessions_list() never needs to open individual session files:
{
  "sessions": [
    {
      "session_id": "3f7a1c2e-...",
      "title": "What is LangGraph?",
      "created_at": "2024-01-01T12:00:00",
      "updated_at": "2024-01-01T12:05:00",
      "message_count": 4
    }
  ]
}
get_sessions_list() sorts the array by updated_at in descending order before returning it, so the most recently active conversation always appears first in the sidebar.

SessionManager API

The SessionManager class (located in backend/session_manager.py) exposes a clean CRUD interface. A global singleton is accessed via get_session_manager().

get_sessions_list()List[Dict]

Returns the metadata list from index.json, sorted by updated_at descending. Does not load individual session files.
session_manager = get_session_manager()
sessions = session_manager.get_sessions_list()
# [{"session_id": "...", "title": "...", "message_count": 4, ...}, ...]

get_session(session_id)Optional[Dict]

Reads and returns the full session file including all messages. Returns None if the file does not exist; logs a warning in that case.
session = session_manager.get_session("3f7a1c2e-...")
if session is None:
    print("Session not found")

create_session(session_id, title=None)Dict

Creates a new empty session file and adds it to index.json. If title is omitted, the session_id string is used as the title placeholder — in practice app.py always passes the auto-generated title.
session = session_manager.create_session(
    session_id="3f7a1c2e-...",
    title="What is LangGraph?"
)

save_session(session_data)

Upserts the session file on disk and refreshes the corresponding entry in index.json (updating updated_at and message_count). If the session does not yet exist in the index, a new entry is appended. Raises ValueError if session_data does not contain a session_id key.
session_data["messages"].append({"role": "user", "content": "...", "timestamp": "..."})
session_manager.save_session(session_data)

delete_session(session_id)bool

Removes the session JSON file from disk and deletes its entry from index.json. Always returns True (even if the file was already absent) after cleaning up the index.
deleted = session_manager.delete_session("3f7a1c2e-...")

rename_session(session_id, new_title)bool

Updates the title and updated_at fields in both the session file and the index. Returns False (and logs a warning) if the session file does not exist.
success = session_manager.rename_session("3f7a1c2e-...", "Deep dive into LangGraph")

Concurrency safety

SessionManager uses filelock.FileLock to serialize all reads and writes:
self.lock = FileLock(str(LOCK_FILE), timeout=10)
  • Lock file: data/sessions/.lock
  • Timeout: 10 seconds — raises filelock.Timeout if another process holds the lock longer
  • Backup-on-write: before overwriting any JSON file, _write_json() copies the original to a .json.bak backup. If the write fails, the backup is restored. The backup is deleted after a successful write.
This pattern prevents corrupted files if the process is killed mid-write or if two requests race to update the same session.

Data persistence with Docker

The docker-compose.yml mounts the data/ directory from the host into both containers:
backend:
  volumes:
    - ./data:/app/data   # persists session JSON files

frontend:
  volumes:
    - ./data:/app/data   # allows frontend to read session index if needed
On first run, SessionManager creates data/sessions/ automatically (via DATA_DIR.mkdir(parents=True, exist_ok=True)). Subsequent container restarts find the existing files on the host and session history is preserved.
Without the ./data:/app/data volume mount in docker-compose.yml, all session history is stored inside the container’s ephemeral layer and is permanently lost when the container stops or is recreated. Always verify the volume is present before deploying to production.

In-memory LangGraph state vs. durable JSON

ChatAgents uses two complementary persistence layers:
LayerImplementationPurpose
In-memory checkpointerMemorySaver (LangGraph)Holds the LangGraph message graph for multi-turn LLM context within a running process
Durable JSON storageSessionManagerPersists conversation history to disk so it survives restarts
MemorySaver is initialized once in app.py’s lifespan handler and is keyed by thread_id. It enables the agent to recall earlier turns in the same conversation when constructing the next LLM prompt. SessionManager stores the finalized messages after each streaming response completes, providing the data shown in the sidebar and loaded when a user reopens a past session. When the backend process restarts, MemorySaver is reset (in-process memory is gone), but SessionManager can reload messages from disk, so displayed history is never lost.

Managing sessions in the UI

The Streamlit sidebar exposes all session management actions:
  • Session list: displays all sessions from GET /api/sessions, sorted most-recent-first. Clicking a session loads its full message history via GET /api/sessions/{session_id}.
  • New Session button: generates a fresh UUID thread_id, creates a blank session, and clears the chat window.
  • Rename: double-click a session title in the sidebar to edit it inline. The change is sent to PUT /api/sessions/{session_id}.
  • Delete: click the delete icon next to a session to call DELETE /api/sessions/{session_id} and remove it from the list.

Build docs developers (and LLMs) love