# Handling Attachments and File Uploads in ChatDev Multi-Agent Workflows

> Learn how ChatDev handles file uploads and attachments seamlessly using AttachmentStore and MessageBlock for efficient agent communication. Explore our OpenBMB/ChatDev repo.

- Repository: [OpenBMB/ChatDev](https://github.com/OpenBMB/ChatDev)
- Tags: how-to-guide
- Published: 2026-04-01

---

**ChatDev treats files as first-class assets through the `AttachmentStore` class, which registers local files or byte streams, deduplicates by SHA-256, and embeds references into `MessageBlock` objects for seamless agent-to-agent transfer.**

The OpenBMB/ChatDev framework enables complex multi-agent workflows where files must persist across execution boundaries. Handling attachments and file uploads in ChatDev multi-agent workflows relies on a filesystem-backed storage layer that couples immutable metadata with MIME-type-aware message blocks. This architecture allows agents to share images, PDFs, and binaries through a consistent API that abstracts physical file paths.

## Core Attachment Architecture

ChatDev implements file handling through three layered abstractions defined in [`utils/attachments.py`](https://github.com/OpenBMB/ChatDev/blob/main/utils/attachments.py) and [`entity/messages.py`](https://github.com/OpenBMB/ChatDev/blob/main/entity/messages.py).

### AttachmentRef and Immutable Metadata

The `AttachmentRef` dataclass stores immutable properties including a unique `attachment_id`, MIME type, SHA-256 hash, file size, and physical location. This reference serves as the canonical identity for any file within the system. According to the source code in [`entity/messages.py`](https://github.com/OpenBMB/ChatDev/blob/main/entity/messages.py) at line 43, `AttachmentRef` objects are lightweight and hashable, making them suitable for indexing and comparison operations.

### AttachmentRecord and Message Coupling

`AttachmentRecord` binds an `AttachmentRef` to a specific `MessageBlockType` (e.g., `file`, `image`) and optional user-supplied descriptions. Defined in [`utils/attachments.py`](https://github.com/OpenBMB/ChatDev/blob/main/utils/attachments.py) at line 18, this record acts as the bridge between raw storage and conversational content. The method `as_message_block()` converts the record into a `MessageBlock` instance that can be inserted directly into a message's content list.

### AttachmentStore and Workflow Scoping

The `AttachmentStore` class provides the primary interface for registration, retrieval, and persistence. Each workflow execution receives a dedicated store instance backed by a per-run directory structure (`<run-root>/attachments/<attachment_id>/`). The store maintains an in-memory index and writes persistent records to [`attachments_manifest.json`](https://github.com/OpenBMB/ChatDev/blob/main/attachments_manifest.json) via the `_save_manifest` method.

## Registering Files and Binary Data

The store exposes three registration helpers in [`utils/attachments.py`](https://github.com/OpenBMB/ChatDev/blob/main/utils/attachments.py) (lines 72-84) to accommodate different input sources:

- **`register_file`** – Ingests existing local files from disk. Accepts `file_path`, `kind` (defaults to `MessageBlockType.FILE`), `display_name`, `mime_type`, and a `deduplicate` boolean.
- **`register_bytes`** – Accepts in-memory binary data (e.g., generated PNGs). Requires `data`, `mime_type`, and `display_name`.
- **`register_remote_file`** – References files hosted by external providers (e.g., OpenAI file IDs). Uses `remote_file_id`, `name`, `mime_type`, and `size`.

All methods compute a SHA-256 hash for deduplication, copy the file into the per-run attachments directory, and return an `AttachmentRecord`.

```python
from pathlib import Path
from utils.attachments import AttachmentStore
from entity.messages import MessageBlockType

store = AttachmentStore(Path("/tmp/run/attachments"))
record = store.register_file(
    "/home/user/report.pdf",
    kind=MessageBlockType.FILE,
    display_name="Project Report.pdf",
    mime_type="application/pdf",
    deduplicate=True,
)

```

## Embedding Attachments in Agent Messages

Once registered, attachments enter the conversation flow through `MessageBlock` objects. The `AttachmentRecord.as_message_block()` method constructs a block with the appropriate type derived from the MIME type via `MessageBlockType.from_mime_type` (see lines 30-40 in [`entity/messages.py`](https://github.com/OpenBMB/ChatDev/blob/main/entity/messages.py)).

```python
from entity.messages import Message, MessageRole

msg = Message(
    role=MessageRole.ASSISTANT,
    content=[record.as_message_block()],
)

```

This design allows multimodal content to traverse the message history exactly like text tokens, ensuring that downstream agents receive both the metadata and the file reference.

## Runtime Context and Dependency Injection

Every workflow run receives its `AttachmentStore` through the runtime context, eliminating the need for agents to manage filesystem paths manually.

1. **RuntimeBuilder** instantiates the store during workflow initialization. Lines 31-36 in [`workflow/runtime/runtime_builder.py`](https://github.com/OpenBMB/ChatDev/blob/main/workflow/runtime/runtime_builder.py) create the `attachments` subdirectory under `code_workspace` and initialize the store.
2. **RuntimeContext** holds the store as a dependency injected into all nodes and tools. Line 25 in [`workflow/runtime/runtime_context.py`](https://github.com/OpenBMB/ChatDev/blob/main/workflow/runtime/runtime_context.py) exposes `attachment_store` as a context attribute.

Agents and tools access the store uniformly via `context["attachment_store"]` or `context.attachment_store`, depending on the context implementation.

## HTTP Upload Handling

The FastAPI server handles multipart uploads through `AttachmentService.save_upload_file` (lines 46-70 in [`server/services/attachment_service.py`](https://github.com/OpenBMB/ChatDev/blob/main/server/services/attachment_service.py)). The service:

1. Streams `UploadFile` chunks to a temporary directory.
2. Detects MIME types via `upload.content_type` or `mimetypes.guess_type`.
3. Registers the temporary file with the session-specific `AttachmentStore` using `register_file` with `kind=MessageBlockType.from_mime_type(...)`.
4. Cleans up temporary files after registration.

This ensures that files uploaded via HTTP become immediately available to the workflow's agents through the same store interface.

## Cross-Session Propagation and Deduplication

When tools or sub-graphs forward attachments between different sessions or agents, `AttachmentService.build_attachment_blocks` (lines 95-103 in [`server/services/attachment_service.py`](https://github.com/OpenBMB/ChatDev/blob/main/server/services/attachment_service.py)) orchestrates the transfer:

- Retrieves the source `AttachmentRecord` from the origin store.
- Copies the underlying file to a target store via `ingest_record` when stores reside in different workflow roots.
- Returns a list of `MessageBlock` objects ready for new messages.

```python
blocks = attachment_service.build_attachment_blocks(
    session_id="session_123",
    attachment_ids=["a1b2c3"],
    target_store=other_context.attachment_store,
)

```

Deduplication occurs at registration time through the `_hash_index` dictionary. When `deduplicate=True`, the store checks for existing SHA-256 hashes; if found, it returns the existing `AttachmentRecord` instead of copying duplicate data. Only records marked with `persist=True` are written to [`attachments_manifest.json`](https://github.com/OpenBMB/ChatDev/blob/main/attachments_manifest.json) via `_save_manifest`, which is reloaded on store startup via `_load_manifest`.

## Tool Integration

File-related tools in [`functions/function_calling/file.py`](https://github.com/OpenBMB/ChatDev/blob/main/functions/function_calling/file.py) expect an `AttachmentStore` in their `tool_context`. The `_require_store` helper at lines 42-45 validates the store's presence before tools attempt file operations. This pattern ensures that all file-aware tools operate within the workflow's scoped storage, maintaining isolation between concurrent runs.

```python
def generate_chart(context: ToolContext) -> List[MessageBlock]:
    img_bytes = create_plot()  # matplotlib or similar

    store = context["attachment_store"]
    record = store.register_bytes(
        img_bytes,
        mime_type="image/png",
        display_name="sales_chart.png",
        description="Sales performance chart",
    )
    return [record.as_message_block()]

```

## Summary

- **AttachmentStore** provides the central API for registering local files, byte streams, and remote references while handling SHA-256 deduplication.
- **AttachmentRef** and **AttachmentRecord** separate immutable metadata from message-specific presentation logic.
- The runtime builder injects a scoped store into **RuntimeContext**, making file operations available to all workflow nodes without hardcoded paths.
- **AttachmentService** bridges HTTP uploads and cross-session transfers through `save_upload_file` and `build_attachment_blocks`.
- Deduplication and manifest persistence optimize storage and enable workflow resumption across executions.

## Frequently Asked Questions

### How does ChatDev prevent duplicate file storage across workflow runs?

The `AttachmentStore` maintains a `_hash_index` mapping SHA-256 hashes to existing records. When `deduplicate=True` is passed to `register_file` or `register_bytes`, the store checks this index and returns the existing `AttachmentRecord` if the hash matches, avoiding redundant copies.

### Can agents access attachments from previous workflow sessions?

Yes, through the `ingest_record` method. When `AttachmentService.build_attachment_blocks` copies an attachment to a target store in a different session, it uses `ingest_record` with `copy_file=True` to physically transfer the file while preserving metadata, enabling cross-session file sharing.

### What MIME types automatically map to specific message block types?

The `MessageBlockType.from_mime_type` method at lines 30-40 in [`entity/messages.py`](https://github.com/OpenBMB/ChatDev/blob/main/entity/messages.py) maps common patterns (e.g., `image/*` to `MessageBlockType.IMAGE`, `application/pdf` to `MessageBlockType.FILE`). Tools can override this by explicitly specifying the `kind` parameter during registration.

### How do I register a file that exists only in memory?

Use `AttachmentStore.register_bytes`, which accepts a `bytes` object, `mime_type`, and `display_name`. This method writes the data to the store's directory structure and returns an `AttachmentRecord` identical to disk-based registrations, as shown in the tool integration examples.