Memory Management and Context Persistence Across Agent Interactions in ChatDev: A Deep Dive into the Architecture

ChatDev implements a layered memory system that allows AI agents to store, retrieve, and persist context across workflow runs through abstracted memory stores, configurable attachments, and automatic graph-level orchestration.

The OpenBMB/ChatDev framework treats memory as a first-class component, enabling agents to reason over past information both within a single execution and across successive sessions. This article examines the concrete implementation of the memory subsystem, from the abstract MemoryBase API to the GraphExecutor orchestration logic that binds persistent stores to individual agent nodes.

Memory Architecture Overview

ChatDev’s memory system is built on a three-tier architecture that separates storage abstractions from concrete implementations and runtime management.

The MemoryBase Abstraction Layer

At the core of the system lies the MemoryBase abstract class defined in runtime/node/agent/memory/memory_base.py. This interface declares four fundamental operations that every memory store must implement:

  • load() – Restores persisted data from disk or external storage.
  • save() – Persists the current memory state to durable storage.
  • retrieve(query, top_k) – Fetches relevant memory items based on a query snapshot.
  • update(payload) – Writes new memory content and immediately triggers persistence.

The MemoryManager class (lines 33-44 in the same file) acts as the runtime intermediary between agent nodes and their underlying stores. It maintains a registry of memory attachments—configurable bindings that define read/write permissions, retrieval stages, and similarity thresholds for each store.

Concrete Store Implementations

The framework provides three built-in persistence strategies, each optimized for different scalability and sharing requirements:

SimpleMemory (runtime/node/agent/memory/simple_memory.py) maintains an in-memory dictionary suitable for transient sessions and testing scenarios where durability is not required.

FileMemory (runtime/node/agent/memory/file_memory.py) extends the base functionality with JSON-based persistence and optional embedding-based similarity search, automatically resolving auto paths to workflow-specific directories.

BlackboardMemory (runtime/node/agent/memory/blackboard_memory.py) implements a shared knowledge pool that multiple agents can concurrently read and write, enabling collective reasoning across distributed nodes.

Configuration and Factory Pattern

ChatDev decouples memory store instantiation from workflow logic through a factory pattern and YAML-based configuration.

Defining Stores in Workflow YAML

Workflow authors declare memory stores in the top-level memory section and bind them to specific agents via the memories attachment list. The MemoryAttachmentConfig model (defined in entity/configs/node/memory.py) validates these bindings at runtime.

memory:
  - name: session_mem
    type: simple
    memory_path: auto

nodes:
  - id: agent1
    type: agent
    config:
      memories:
        - name: session_mem
          read: true
          write: true
          top_k: 5
          retrieve_stage: [input]

When the executor encounters memory_path: auto, it generates a JSON file under the workflow directory (e.g., memory_session_mem.json), ensuring automatic persistence without manual path management.

The MemoryFactory Registry

The MemoryFactory class in runtime/node/agent/memory/builtin_stores.py (lines 37-42) registers concrete store types and instantiates the appropriate implementation based on the type field. This registry pattern allows developers to extend ChatDev with custom storage backends by registering new MemoryBase subclasses.

from runtime.node.agent.memory.builtin_stores import MemoryFactory
from entity.configs.node.memory import SimpleMemoryConfig

simple_cfg = SimpleMemoryConfig(name="test_mem", type="simple", memory_path="tmp_mem.json")
store = MemoryFactory.create_memory(simple_cfg)  # Returns SimpleMemory instance

store.load()  # Hydrates from existing JSON or initializes empty

Runtime Orchestration and Persistence

The GraphExecutor class in workflow/graph.py orchestrates memory lifecycle events, ensuring that persistence occurs automatically without explicit agent intervention.

GraphExecutor Memory Lifecycle

During initialization, GraphExecutor.__init__ prepares two critical dictionaries: global_memories (shared stores) and agent_memory_managers (per-node managers). The _build_global_memories method (lines 50-81) iterates over the workflow configuration, resolves file paths, invokes MemoryFactory.create_memory, and calls load() to restore previous session data.

Subsequently, _build_agent_memories constructs a MemoryManager instance for every agent node, mapping declared attachments to the already-initialized global stores. This two-phase construction ensures that shared stores exist before any agent attempts to reference them.

Per-Agent MemoryManager Binding

When a node executes, it receives its dedicated MemoryManager via the ExecutionContext. The manager exposes two primary methods used by node executors:

  • retrieve(agent_role, query, current_stage) (lines 49-88) – Scores items across all readable attachments and returns a MemoryRetrievalResult containing formatted text ready for injection into the agent’s prompt.
  • update(payload) – Accepts a MemoryWritePayload containing input/output snapshots, persists the data via the underlying store’s save() method, and updates the retrieval index.
from runtime.node.agent.memory.memory_base import MemoryContentSnapshot

# Inside a node executor

mem_mgr = node.execution_context.memory_managers[node.id]
query = MemoryContentSnapshot.from_message(user_message)

result = mem_mgr.retrieve(
    agent_role="assistant",
    query=query,
    current_stage=AgentExecFlowStage.INPUT
)

Automatic Save and Load Mechanics

At workflow completion, GraphExecutor._save_memories (lines 55-59) iterates through all global stores and invokes save(), flushing in-memory state to disk. On the next execution, the initialization sequence calls load() on each store, restoring the exact knowledge state from the previous run and providing true context persistence across sessions.

Retrieval Scoring and Context Traces

Beyond simple storage, ChatDev implements intelligent retrieval algorithms and cross-turn context management.

Time-Decay Scoring Algorithm

The _score_memory method in memory_base.py (lines 90-104) implements a multi-factor relevance calculation that combines:

  • Time-decay factor – Exponentially reduces scores for older memories.
  • Length bias – Prefers concise memories to minimize token consumption.
  • Lexical relevance – Measures similarity between the query and memory content.

This composite scoring ensures that recent, relevant, and compact memories surface first, while older information remains accessible with reduced priority.

Cross-Turn Context Persistence

For agents requiring conversation continuity within a single workflow, ChatDev utilizes the context trace mechanism. When a node produces output containing a "context_trace" metadata field (a serialized list of previous messages), the GraphExecutor._restore_context_trace method (lines 92-116) automatically deserializes this payload and prepends the messages to the node’s input queue on the next execution cycle.

This feature is activated when a node configuration specifies context_window != 0, allowing agents to "remember" their previous actions and responses across distinct turns without requiring external memory stores.

Implementation Examples

Programmatic Memory Management

For testing or custom integrations, developers can manually instantiate stores and managers:

from runtime.node.agent.memory.memory_base import MemoryManager, MemoryWritePayload, MemoryContentSnapshot
from runtime.node.agent.memory.builtin_stores import MemoryFactory
from entity.configs.node.memory import MemoryAttachmentConfig

# Initialize store

store = MemoryFactory.create_memory(simple_cfg)
store.load()

# Configure attachment

attachment = MemoryAttachmentConfig(
    name="test_mem",
    read=True,
    write=True,
    top_k=3,
    retrieve_stage=None,
    similarity_threshold=0.5
)

# Create manager and write data

mgr = MemoryManager([attachment], {"test_mem": store})

payload = MemoryWritePayload(
    agent_role="assistant",
    inputs_text="User asked about weather",
    input_snapshot=MemoryContentSnapshot.from_message("User asked about weather"),
    output_snapshot=MemoryContentSnapshot.from_message("It will be sunny tomorrow.")
)

mgr.update(payload)  # Persists instantly via store.save()

Context Trace Serialization

Nodes can preserve conversation state for subsequent turns:


# Creating a context trace

output_msg = Message(
    role=MessageRole.ASSISTANT,
    content="Processing complete.",
    metadata={"context_trace": [msg.to_dict() for msg in node.input]}
)

# On next execution, GraphExecutor._restore_context_trace automatically

# deserializes the trace and injects messages into node.input,

# maintaining continuity across the context window.

Summary

  • ChatDev implements a robust memory management and context persistence system through the MemoryBase abstraction and GraphExecutor orchestration.
  • Three concrete store types—SimpleMemory, FileMemory, and BlackboardMemory—provide flexibility for transient testing, durable persistence, and multi-agent sharing respectively.
  • The MemoryManager mediates all agent interactions with stores, handling retrieval scoring and automatic updates via retrieve() and update() methods.
  • GraphExecutor manages the complete lifecycle, constructing global stores at startup, binding per-node managers, and ensuring persistence via _save_memories() at workflow completion.
  • Time-decay scoring and context traces provide intelligent retrieval and cross-turn continuity without manual state management.

Frequently Asked Questions

How does ChatDev persist memory between workflow runs?

ChatDev persists memory through the FileMemory implementation and the GraphExecutor._save_memories method. At the end of each workflow execution (lines 55-59 in workflow/graph.py), the executor calls save() on every global memory store, serializing the state to JSON files. On the next run, _build_global_memories invokes load() on each store, restoring the previous session's data and enabling agents to resume with full historical context.

What is the difference between SimpleMemory and FileMemory in ChatDev?

SimpleMemory (runtime/node/agent/memory/simple_memory.py) stores data only in Python dictionaries, making it ideal for unit tests or ephemeral sessions where disk I/O is unnecessary. FileMemory (runtime/node/agent/memory/file_memory.py) extends this with JSON serialization to disk and optional embedding-based retrieval, automatically managing file paths and ensuring data survives process termination. Production workflows typically use FileMemory or BlackboardMemory for durability.

How does the scoring algorithm prioritize recent memories?

The _score_memory method in runtime/node/agent/memory/memory_base.py (lines 90-104) applies a composite algorithm that weights recent items with an exponential time-decay factor, penalizes excessively long memories with a length bias, and rewards lexical similarity to the query. This ensures that the retrieve() method returns memories that are recent, concise, and semantically relevant to the current agent context.

Can multiple agents share the same memory store in ChatDev?

Yes, through BlackboardMemory (runtime/node/agent/memory/blackboard_memory.py). This implementation acts as a shared knowledge pool where multiple nodes can read and write to the same underlying storage. When configuring the workflow YAML, attach the same blackboard store name to multiple agent nodes, and the GraphExecutor will inject the same store instance into each agent's MemoryManager, enabling collective memory and cross-agent communication.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →