# How to Integrate E2B or Daytona Cloud Code Interpreters into MathModelAgent

> Integrate E2B or Daytona cloud code interpreters into MathModelAgent. Set E2B_API_KEY or extend interpreter_factory.py for custom Daytona interpreter integration.

- Repository: [Sanjin/mathmodelagent](https://github.com/jihe520/mathmodelagent)
- Tags: how-to-guide
- Published: 2026-03-04

---

**Integrate E2B or Daytona cloud code interpreters into MathModelAgent by setting the `E2B_API_KEY` environment variable to enable automatic remote sandbox selection, or extend [`interpreter_factory.py`](https://github.com/jihe520/mathmodelagent/blob/main/interpreter_factory.py) to add a custom `DaytonaCodeInterpreter` class that implements the `BaseCodeInterpreter` interface.**

MathModelAgent (jihe520/mathmodelagent) executes AI-generated Python code through a **code interpreter abstraction** that decouples execution logic from agent workflows. The architecture uses a factory pattern to select between **local Jupyter kernels** and **remote cloud sandboxes**, allowing you to offload computation to E2B or Daytona without modifying downstream agents like the CoderAgent or ModelerAgent.

## Architecture Overview

The interpreter system relies on three core components defined in the `backend/app/tools/` directory:

- **`BaseCodeInterpreter`** – Abstract base class defining the contract for code execution, file synchronization, and result formatting.
- **`create_interpreter()`** – Async factory function in [`backend/app/tools/interpreter_factory.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/tools/interpreter_factory.py) that instantiates the appropriate interpreter based on configuration.
- **`Settings`** – Pydantic configuration class in [`backend/app/config/setting.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/config/setting.py) that holds API keys and execution preferences.

When `create_interpreter()` is called, it checks `settings.E2B_API_KEY` (line 39 in [`setting.py`](https://github.com/jihe520/mathmodelagent/blob/main/setting.py)). If the key exists, the factory returns an **`E2BCodeInterpreter`** instance; otherwise, it falls back to **`LocalCodeInterpreter`**. Both classes expose an identical `execute_code()` method that returns a tuple of `(combined_text, error_occurred, error_message)`, ensuring the CoderAgent remains agnostic to the execution backend.

## Enabling E2B Cloud Execution

The E2B integration is fully implemented and production-ready. To activate remote sandbox execution:

### 1. Configure the API Key

Add your E2B API key to the environment configuration file:

```python

# backend/app/config/.env.dev

E2B_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx

```

The `Settings` class in [`backend/app/config/setting.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/config/setting.py) automatically loads this value:

```python
class Settings(BaseSettings):
    E2B_API_KEY: Optional[str] = None
    # ... other configuration

```

### 2. Factory Selection Logic

The `create_interpreter()` function in [`backend/app/tools/interpreter_factory.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/tools/interpreter_factory.py) handles the selection logic:

```python
async def create_interpreter(
    kind: str = "remote",  # or "local"

    task_id: str = "",
    work_dir: str = "",
    notebook_serializer=None,
) -> BaseCodeInterpreter:
    if settings.E2B_API_KEY:
        logger.info("使用远程解释器")  # "Using remote interpreter"

        interp = await E2BCodeInterpreter.create(
            task_id=task_id,
            work_dir=work_dir,
            notebook_serializer=notebook_serializer,
        )
        await interp.initialize(timeout=300)
        return interp
    else:
        logger.info("默认使用本地解释器")  # "Defaulting to local interpreter"

        # ... LocalCodeInterpreter instantiation

```

### 3. E2B Implementation Details

The `E2BCodeInterpreter` class in [`backend/app/tools/e2b_interpreter.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/tools/e2b_interpreter.py) wraps the `e2b_code_interpreter.AsyncSandbox` client. Its `initialize()` method establishes the sandbox connection using the configured API key, while `execute_code()` runs snippets via `self.sbx.run_code(code)` and converts E2B result objects into the internal `OutputItem` models used by the WebSocket manager.

## Implementing Daytona Cloud Interpreter Support

While the README mentions Daytona support, the current codebase implements only the E2B interpreter. To add **Daytona** as a third execution backend, extend the abstraction layer without modifying agent logic:

### 1. Create the Daytona Interpreter Class

Create [`backend/app/tools/daytona_interpreter.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/tools/daytona_interpreter.py) inheriting from `BaseCodeInterpreter`:

```python
from app.tools.base_interpreter import BaseCodeInterpreter
from app.config.setting import settings

# from daytona_sdk import DaytonaClient  # Hypothetical import

class DaytonaCodeInterpreter(BaseCodeInterpreter):
    @classmethod
    async def create(cls, task_id, work_dir, notebook_serializer):
        instance = cls(task_id, work_dir, notebook_serializer)
        return instance

    async def initialize(self, timeout: int = 300):
        self.client = DaytonaClient(api_key=settings.DAYTONA_API_KEY, timeout=timeout)
        await self._upload_all_files()
        await self._pre_execute_code()

    async def execute_code(self, code: str):
        # Execute via Daytona client, return standardized tuple

        result = await self.client.run_code(code)
        return (result.text, result.has_error, result.error_message)

```

### 2. Extend the Configuration

Add the Daytona API key to [`backend/app/config/setting.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/config/setting.py):

```python
DAYTONA_API_KEY: Optional[str] = None

```

### 3. Update the Factory

Modify `create_interpreter()` in [`backend/app/tools/interpreter_factory.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/tools/interpreter_factory.py) to recognize a new `kind="daytona"`:

```python
from app.tools.daytona_interpreter import DaytonaCodeInterpreter

async def create_interpreter(
    kind: str = "remote",  # Options: "local", "remote", "daytona"

    # ... other params

) -> BaseCodeInterpreter:
    if kind == "daytona" or settings.DAYTONA_API_KEY:
        interp = await DaytonaCodeInterpreter.create(
            task_id=task_id,
            work_dir=work_dir,
            notebook_serializer=notebook_serializer,
        )
        await interp.initialize(timeout=timeout)
        return interp
    elif settings.E2B_API_KEY:
        # Existing E2B logic

        pass
    # ... local fallback

```

Because both remote interpreters implement the same abstract base, no changes are required in the agent workflows located in `backend/app/core/agents/`.

## Practical Implementation Examples

### Running Code with E2B

```python
from app.tools.interpreter_factory import create_interpreter
from app.tools.notebook_serializer import NotebookSerializer

async def execute_with_e2b(task_id: str, work_dir: str, user_code: str):
    serializer = NotebookSerializer(task_id=task_id, work_dir=work_dir)
    
    # Automatically selects E2B when E2B_API_KEY is present

    interpreter = await create_interpreter(
        kind="remote",
        task_id=task_id,
        work_dir=work_dir,
        notebook_serializer=serializer,
    )
    
    result_text, error, msg = await interpreter.execute_code(user_code)
    return {"output": result_text, "error": error, "details": msg}

```

### Fallback to Local Execution

```python

# Ensure E2B_API_KEY is unset to trigger local kernel

interpreter = await create_interpreter(
    kind="local",
    task_id="local_demo",
    work_dir="/tmp/workspace",
    notebook_serializer=serializer,
)

# Uses jupyter_client.manager.start_new_kernel internally

```

### Daytona Integration Skeleton

```python

# backend/app/tools/daytona_interpreter.py

class DaytonaCodeInterpreter(BaseCodeInterpreter):
    async def _upload_all_files(self):
        # Sync local work_dir to Daytona workspace

        pass
    
    async def download_all_files_from_sandbox(self):
        # Pull results back to local filesystem

        pass
    
    async def execute_code(self, code: str):
        # Standardized execution interface

        pass

```

## Summary

- **Configuration-driven selection**: Set `E2B_API_KEY` in [`backend/app/config/setting.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/config/setting.py) to automatically enable E2B sandboxes; omit it to fall back to local Jupyter kernels.
- **Factory pattern**: The `create_interpreter()` function in [`backend/app/tools/interpreter_factory.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/tools/interpreter_factory.py) centralizes instantiation logic, returning ready-to-use interpreter instances.
- **Unified interface**: Both `E2BCodeInterpreter` and `LocalCodeInterpreter` inherit from `BaseCodeInterpreter`, ensuring `execute_code()` returns consistent `(text, error, message)` tuples.
- **Extensibility**: Add Daytona support by implementing a new `DaytonaCodeInterpreter` class and extending the factory's conditional logic without altering agent code in `backend/app/core/agents/`.

## Frequently Asked Questions

### What is the difference between E2B and Daytona interpreters in MathModelAgent?

**E2B is fully implemented** in the codebase via [`backend/app/tools/e2b_interpreter.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/tools/e2b_interpreter.py), using the `e2b_code_interpreter.AsyncSandbox` client. Daytona is conceptual—mentioned in the README but requiring you to implement a `DaytonaCodeInterpreter` class following the same `BaseCodeInterpreter` pattern. Both provide isolated cloud environments for executing untrusted code.

### How does MathModelAgent decide between local and cloud execution?

The `create_interpreter()` factory checks `settings.E2B_API_KEY` (defined in [`backend/app/config/setting.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/config/setting.py)). If the key exists, it instantiates `E2BCodeInterpreter`; otherwise, it defaults to `LocalCodeInterpreter` using `jupyter_client`. You can override this by passing explicit `kind` parameters to the factory.

### Can I use multiple cloud interpreters simultaneously in the same task?

**No**, the current architecture selects one interpreter per task based on the factory configuration. However, because the CoderAgent interacts only with the abstract `BaseCodeInterpreter` interface, you could modify the factory to return different interpreter instances for different code blocks, though this would require changes to the agent's execution loop in `backend/app/core/agents/`.

### What files do I need to modify to add Daytona support?

You must create [`backend/app/tools/daytona_interpreter.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/tools/daytona_interpreter.py) implementing the `BaseCodeInterpreter` interface, add `DAYTONA_API_KEY` to [`backend/app/config/setting.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/config/setting.py), and extend the conditional logic in [`backend/app/tools/interpreter_factory.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/tools/interpreter_factory.py) to recognize Daytona. No changes are needed in the agent workflows located in `backend/app/core/agents/` because they depend only on the abstract base class.