# Session State Persistence and Workflow Recovery in ChatDev: A Complete Technical Guide

> Explore ChatDev's session state persistence and workflow recovery using its UUID-based in-memory registry. Seamlessly continue AI workflows after WebSocket reconnections. Read the complete technical guide.

- Repository: [OpenBMB/ChatDev](https://github.com/OpenBMB/ChatDev)
- Tags: deep-dive
- Published: 2026-04-01

---

**ChatDev implements session state persistence and workflow recovery through an in-memory registry system that maps UUID-based session IDs to WorkflowSession objects, enabling seamless continuation of AI-driven workflows across WebSocket reconnections.**

ChatDev is an open-source framework for orchestrating multi-agent AI workflows developed by OpenBMB. The platform's architecture centers on maintaining runtime state through a sophisticated session management system that ensures no progress is lost when network connections fluctuate. Understanding how **session state persistence and workflow recovery in ChatDev** functions is essential for developers building robust production deployments or extending the framework's capabilities.

## Understanding the Core Architecture

The session management architecture relies on three primary components working in concert to maintain state across the WebSocket lifecycle.

### RuntimeContext and Session Propagation

The `RuntimeContext` class defined in [`runtime/runtime_context.py`](https://github.com/OpenBMB/ChatDev/blob/main/runtime/runtime_context.py) serves as the backbone for dependency injection throughout the executor pipeline. This context carries the critical `session_id` field that links every runtime operation—including tool management, token tracking, and attachment storage—to a specific workflow instance. By propagating this identifier through the dependency graph, ChatDev ensures that all components reference the same session boundary during execution.

### WorkflowSessionStore In-Memory Registry

The `WorkflowSessionStore` class in [`server/services/session_store.py`](https://github.com/OpenBMB/ChatDev/blob/main/server/services/session_store.py) implements a thread-safe in-memory map that associates each `session_id` with a `WorkflowSession` dataclass instance. This registry acts as the source of truth for active workflows, storing mutable records containing the executor reference, current node position, graph configuration, cancellation flags, and attachment metadata. When a client reconnects, the server queries this store to locate the existing session and resume processing from the last known state.

### WebSocketManager Connection Handling

Incoming connections are managed by `WebSocketManager` in [`server/services/websocket_manager.py`](https://github.com/OpenBMB/ChatDev/blob/main/server/services/websocket_manager.py). Upon connection, this service either accepts a client-provided `session_id` to support recovery scenarios or generates a new UUID via `uuid.uuid4()`. The manager maintains an `active_connections` dictionary mapping session IDs to WebSocket instances, enabling bidirectional message routing while keeping the session identifier as the persistent anchor across transport layers.

## The Session Lifecycle Explained

ChatDev's session lifecycle follows a deterministic sequence from connection establishment through final cleanup, with each phase designed to support recovery and persistence.

### Step 1: Connection and Session ID Establishment

When a client connects to the WebSocket endpoint at `/ws`, the server initiates the handshake process. If the client passes an existing `session_id` in the connection payload, the system attempts to recover that session; otherwise, it generates a fresh UUID. The server immediately returns this identifier in a connection acknowledgment message, ensuring the client possesses the session key required for subsequent operations.

```python

# From server/services/websocket_manager.py

if not session_id:
    session_id = str(uuid.uuid4())
self.active_connections[session_id] = websocket
await self.send_message(session_id, {"type": "connection", "data": {"session_id": session_id}})

```

### Step 2: Workflow Initialization

Upon receiving a `start_workflow` message, the `WorkflowRunService` in [`server/services/workflow_run_service.py`](https://github.com/OpenBMB/ChatDev/blob/main/server/services/workflow_run_service.py) creates a formal session entry in the registry. This process captures the YAML configuration filename, task prompt, and any uploaded attachment references, binding them to the session ID for the duration of the workflow.

```python

# From WorkflowRunService.start_workflow()

self.session_store.create_session(
    yaml_file=normalized_yaml_name,
    task_prompt=task_prompt,
    session_id=session_id,
    attachments=attachments,
)

```

### Step 3: Execution Context and Graph Binding

During workflow execution, the service constructs a `GraphConfig` with a uniquely named identifier `session_{session_id}`, ensuring the execution graph is isolated and traceable. The `WebSocketGraphExecutor` receives a reference to the `WorkflowSessionStore`, allowing it to persist intermediate results, token usage statistics, and current node position back to the session object as the graph processes each node.

```python

# Graph naming convention from workflow_run_service.py

graph_config = GraphConfig.from_definition(
    design.graph,
    name=f"session_{session_id}",
    ...
)

```

### Step 4: Cancellation and Recovery Mechanisms

ChatDev implements graceful cancellation through a per-session `cancel_event` stored within the `WorkflowSession` object. When a client disconnects unexpectedly or explicitly requests cancellation, `WorkflowRunService.request_cancel()` sets this event. The executor polls this flag periodically, raising a `WorkflowCancelledError` when detected, which triggers immediate but clean termination of the current agent execution without corrupting the session state.

```python

# Cancellation check in agent_executor.py

if cancel_event and cancel_event.is_set():
    raise WorkflowCancelledError(reason, workflow_id=graph_context.name)

```

For recovery scenarios, reconnection follows the same path as initial connection. If the provided `session_id` exists in the `WorkflowSessionStore`, the server reattaches the client to the existing executor instance, preserving the graph state, attachment workspace, and conversation history.

### Step 5: Cleanup and Session Termination

Upon workflow completion or cancellation, the `finally` block in `_execute_workflow_async()` handles resource deallocation. This routine clears the executor and graph references from the session object, invokes `session_controller.cleanup_session()` to remove temporary files, and conditionally purges the session from the store only if no active WebSocket connections remain.

```python

# Cleanup logic from server/services/workflow_run_service.py

finally:
    session_ref = self.session_store.get_session(session_id)
    if session_ref:
        session_ref.executor = None
        session_ref.graph = None
    self.session_controller.cleanup_session(session_id)
    if session_id not in websocket_manager.active_connections:
        self.session_store.pop_session(session_id)

```

## Per-Session Attachment Persistence

File uploads in ChatDev maintain session affinity through the `AttachmentService` in [`server/services/attachment_service.py`](https://github.com/OpenBMB/ChatDev/blob/main/server/services/attachment_service.py). This service creates isolated directory structures under `<WARE_HOUSE>/session_<id>/code_workspace/attachments`, ensuring that uploaded files remain available across reconnections. When a session resumes, `AttachmentService.get_attachment_store(session_id)` reconstructs the attachment context, allowing agents to access previously uploaded documents without requiring re-upload after network interruptions.

## Practical Implementation Examples

### Starting a New Workflow Session

Client applications initiate workflows by requesting a new session and preserving the returned identifier for recovery purposes:

```python
import websockets
import json
import uuid

async def start_workflow():
    ws = await websockets.connect("ws://localhost:8000/ws")
    
    # Request workflow start without providing session_id (server generates new)

    await ws.send(json.dumps({
        "type": "start_workflow",
        "data": {
            "yaml_file": "demo_simple_memory.yaml",
            "task_prompt": "Generate a summary of the given text.",
            "session_id": None,
            "attachments": []
        }
    }))
    
    # Receive connection confirmation with generated session_id

    msg = json.loads(await ws.recv())
    session_id = msg["data"]["session_id"]
    print(f"Session established: {session_id}")
    return session_id, ws

```

### Reconnecting to Recover a Session

To resume after a disconnection, clients provide their saved session ID during the handshake:

```python
async def resume_session(saved_session_id):
    ws = await websockets.connect("ws://localhost:8000/ws")
    
    # Pass existing session_id to reconnect to running workflow

    await ws.send(json.dumps({
        "type": "connect",
        "data": {"session_id": saved_session_id}
    }))
    
    # Server resumes pushing updates from the last executed node

    async for message in ws:
        print(json.loads(message))

```

### Canceling a Running Workflow

Clients can terminate workflows gracefully using the session identifier:

```python
await ws.send(json.dumps({
    "type": "cancel_workflow",
    "data": {
        "session_id": saved_session_id,
        "reason": "User requested termination"
    }
}))

```

### Querying Session State Server-Side

Administrative tools or monitoring services can inspect session health directly through the store:

```python
from server.services.session_store import WorkflowSessionStore

store = WorkflowSessionStore()
session = store.get_session("a1b2c3d4-5678-90ab-cdef-1234567890ab")

if session:
    print(f"Current node: {session.current_node_id}")
    print(f"Token usage: {session.executor.token_tracker.get_token_usage()}")
    print(f"Attachments: {len(session.task_attachments)} files")

```

## Key Source Files Reference

| File Path | Purpose |
|-----------|---------|
| [`runtime/runtime_context.py`](https://github.com/OpenBMB/ChatDev/blob/main/runtime/runtime_context.py) | Defines `RuntimeContext` with `session_id` field for dependency injection |
| [`server/services/session_store.py`](https://github.com/OpenBMB/ChatDev/blob/main/server/services/session_store.py) | Implements `WorkflowSessionStore` in-memory registry and `WorkflowSession` dataclass |
| [`server/services/websocket_manager.py`](https://github.com/OpenBMB/ChatDev/blob/main/server/services/websocket_manager.py) | Manages WebSocket lifecycles and session ID assignment |
| [`server/services/workflow_run_service.py`](https://github.com/OpenBMB/ChatDev/blob/main/server/services/workflow_run_service.py) | Orchestrates workflow execution, graph naming, and cleanup routines |
| [`server/services/attachment_service.py`](https://github.com/OpenBMB/ChatDev/blob/main/server/services/attachment_service.py) | Handles per-session attachment directories and file persistence |
| [`server/services/message_handler.py`](https://github.com/OpenBMB/ChatDev/blob/main/server/services/message_handler.py) | Routes incoming WebSocket messages to appropriate services |
| [`runtime/node/executor/agent_executor.py`](https://github.com/OpenBMB/ChatDev/blob/main/runtime/node/executor/agent_executor.py) | Persists attachments and checks cancellation events during execution |
| [`workflow/graph_context.py`](https://github.com/OpenBMB/ChatDev/blob/main/workflow/graph_context.py) | Provides graph context and final message handling |

## Summary

- **Session identification** relies on UUID-based `session_id` values propagated through `RuntimeContext` and stored in `WorkflowSessionStore`.
- **State persistence** occurs through an in-memory registry linking session IDs to `WorkflowSession` objects containing executor references, graph configurations, and attachment stores.
- **Workflow recovery** enables clients to reconnect using existing session IDs, reattaching to running executors without losing progress or uploaded files.
- **Graceful cancellation** uses per-session `cancel_event` flags checked by `WebSocketGraphExecutor` to ensure clean termination.
- **Automatic cleanup** removes session data only when workflows complete and no active connections remain, preventing premature state loss.

## Frequently Asked Questions

### How does ChatDev maintain state when a WebSocket disconnects?

ChatDev maintains state through the `WorkflowSessionStore` in-memory registry, which persists `WorkflowSession` objects independently of WebSocket connections. When a connection drops, the session object retains the executor instance, current graph position, and attachment references. Upon reconnection with the same `session_id`, the `WebSocketManager` retrieves the existing session from the store and resumes message forwarding without restarting the workflow.

### Where is session data stored in ChatDev?

Session data is stored in an in-memory Python dictionary within the `WorkflowSessionStore` class located in [`server/services/session_store.py`](https://github.com/OpenBMB/ChatDev/blob/main/server/services/session_store.py). This registry maps session ID strings to `WorkflowSession` dataclass instances containing the executor, graph configuration, token usage trackers, and attachment metadata. Note that this implementation is ephemeral for single-server instances; persistent storage would require extending the store to interface with Redis or a database.

### Can I reconnect to a running workflow after a network failure?

Yes, ChatDev supports seamless reconnection to running workflows. If you preserve the `session_id` received during the initial connection, you can pass it during a new WebSocket handshake. The server validates the ID against the `WorkflowSessionStore`, and if the session exists and the workflow is still active, the system reattaches your connection to the existing executor instance, allowing you to receive updates from the current node position.

### How are file attachments handled during session recovery?

File attachments are persisted in isolated directories under `<WARE_HOUSE>/session_<id>/code_workspace/attachments` managed by `AttachmentService`. When a session is created, an `AttachmentStore` is instantiated and referenced in the `WorkflowSession`. During recovery, the same attachment store is retrieved via `AttachmentService.get_attachment_store(session_id)`, ensuring that uploaded documents remain accessible to agents even after network interruptions or client reconnections.