How to Integrate E2B or Daytona Cloud Code Interpreters into MathModelAgent

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 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 that instantiates the appropriate interpreter based on configuration.
  • Settings – Pydantic configuration class in 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). 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:


# backend/app/config/.env.dev

E2B_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx

The Settings class in backend/app/config/setting.py automatically loads this value:

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 handles the selection logic:

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 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 inheriting from BaseCodeInterpreter:

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:

DAYTONA_API_KEY: Optional[str] = None

3. Update the Factory

Modify create_interpreter() in backend/app/tools/interpreter_factory.py to recognize a new kind="daytona":

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

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


# 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


# 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 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 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, 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). 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 implementing the BaseCodeInterpreter interface, add DAYTONA_API_KEY to backend/app/config/setting.py, and extend the conditional logic in 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.

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 →