Session State Persistence and Workflow Recovery in ChatDev: A Complete Technical Guide
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 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 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. 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.
# 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 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.
# 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.
# 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.
# 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.
# 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. 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:
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:
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:
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:
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 |
Defines RuntimeContext with session_id field for dependency injection |
server/services/session_store.py |
Implements WorkflowSessionStore in-memory registry and WorkflowSession dataclass |
server/services/websocket_manager.py |
Manages WebSocket lifecycles and session ID assignment |
server/services/workflow_run_service.py |
Orchestrates workflow execution, graph naming, and cleanup routines |
server/services/attachment_service.py |
Handles per-session attachment directories and file persistence |
server/services/message_handler.py |
Routes incoming WebSocket messages to appropriate services |
runtime/node/executor/agent_executor.py |
Persists attachments and checks cancellation events during execution |
workflow/graph_context.py |
Provides graph context and final message handling |
Summary
- Session identification relies on UUID-based
session_idvalues propagated throughRuntimeContextand stored inWorkflowSessionStore. - State persistence occurs through an in-memory registry linking session IDs to
WorkflowSessionobjects 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_eventflags checked byWebSocketGraphExecutorto 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. 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →