# How the Local Jupyter-Based Code Interpreter Functions in MathModelAgent

> Discover how MathModelAgent's local Jupyter-based code interpreter works. It runs Python code in isolation and saves every interaction to a notebook file post-execution.

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

---

**The local Jupyter-based code interpreter initializes an isolated Python kernel, executes user code cells, and automatically persists every interaction to a Jupyter notebook file via a dedicated serializer that writes to disk after each mutation.**

The `jihe520/mathmodelagent` repository implements a robust local execution environment through the `LocalCodeInterpreter` class. This component extends the abstract `BaseCodeInterpreter` to provide secure, stateful code execution while maintaining a complete audit trail in standard `.ipynb` format.

## Architecture and Initialization

### Kernel Startup and Environment Setup

In [`backend/app/tools/local_interpreter.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/tools/local_interpreter.py), the `LocalCodeInterpreter.initialize()` method creates a fresh Jupyter kernel using `jupyter_client.manager.start_new_kernel()`. The initialization runs a bootstrap script defined in `_pre_execute_code()` that ensures the task-specific working directory exists via `os.makedirs()` and switches the process current working directory to that sandbox. This guarantees all relative file operations performed by user code remain isolated within the task workspace.

### Notebook File Preparation

The `NotebookSerializer` class defined in [`backend/app/tools/notebook_serializer.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/tools/notebook_serializer.py) handles persistence logic. Upon instantiation, it receives the `work_dir` and constructs the absolute notebook path. The `init_notebook()` method initializes an in-memory `nbformat` notebook object that serves as the live document throughout the session, establishing the file location where all subsequent mutations are saved.

## Code Execution and Real-Time Persistence

### The Execution Pipeline

The `execute_code()` method orchestrates the interaction between the kernel and the notebook. It first calls `self.notebook_serializer.add_code_cell_to_notebook(code)` to append a new code cell to the in-memory notebook and immediately flushes it to disk via `write_to_notebook()`.

The interpreter then submits the code to the kernel using `self.kc.execute()` and polls the iopub channel with `self.kc.get_iopub_msg()` until detecting a `status` message with `execution_state == "idle"`. During this polling phase, it captures `stdout`, `execute_result`, `display_data`, and `error` message types.

### Output Processing and Serialization

Each output type undergoes specific transformation before persistence:

- **Text output**: ANSI escape sequences are converted to HTML using `NotebookSerializer.ansi_to_html()` before storage in the notebook.
- **Image payloads**: PNG or JPEG data is stored as base64 strings with appropriate MIME types (`image/png` or `image/jpeg`).
- **Errors**: Color control characters are stripped using `delete_color_control_char()` inherited from `BaseCodeInterpreter` before adding error outputs to the notebook.

After processing each message, `write_to_notebook()` persists the updated notebook state to disk, ensuring the file mirrors the interactive session in real time.

### WebSocket and Redis Integration

During execution, the interpreter assembles a combined plain-text log (`text_to_gpt`) for downstream LLM agents. It publishes structured `ResultModel` and `StdErrModel` objects to the WebSocket layer via `self._push_to_websocket()` and sends start/end execution messages to Redis, enabling real-time monitoring of the local Jupyter-based code interpreter's activity.

## Resource Management and Cleanup

When the task completes, the `cleanup()` method in [`backend/app/tools/local_interpreter.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/tools/local_interpreter.py) gracefully terminates the kernel. It calls `self.kc.shutdown()` to close the client connection and `self.km.shutdown_kernel()` to terminate the kernel manager, ensuring no orphaned processes or ZMQ connections remain.

## Practical Implementation Example

The following example demonstrates the complete lifecycle of the interpreter:

```python
from backend.app.tools.local_interpreter import LocalCodeInterpreter
from backend.app.tools.notebook_serializer import NotebookSerializer

# 1️⃣ Initialize the serializer (creates notebook path under the task workspace)

serializer = NotebookSerializer(work_dir="/tmp/task_12345")

# 2️⃣ Create the interpreter instance

interpreter = LocalCodeInterpreter(
    task_id="task_12345",
    work_dir="/tmp/task_12345",
    notebook_serializer=serializer,
)

# 3️⃣ Start the local Jupyter kernel

await interpreter.initialize()

# 4️⃣ Execute user-provided code

code = """
import pandas as pd
df = pd.DataFrame({'x': range(5), 'y': [2, 4, 6, 8, 10]})
df.head()
"""
result_text, error_flag, error_msg = await interpreter.execute_code(code)

print("Combined text for LLM:", result_text)
print("Was there an error?", error_flag)

# 5️⃣ Cleanup resources when finished

await interpreter.cleanup()

# 6️⃣ The notebook is now stored at

print("Notebook saved at:", serializer.notebook_path)

```

## Summary

- The `LocalCodeInterpreter` class in [`backend/app/tools/local_interpreter.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/tools/local_interpreter.py) manages the complete lifecycle of a local Jupyter kernel, from startup through cleanup.
- **Real-time persistence**: The `NotebookSerializer` writes to disk after every cell addition and output capture, ensuring the `.ipynb` file always reflects the current session state.
- **Sandboxed execution**: The bootstrap script in `_pre_execute_code()` establishes a dedicated working directory for each task, isolating file operations.
- **Multi-format output support**: The interpreter handles text (with ANSI-to-HTML conversion), base64-encoded images, and structured error traces.
- **Resource safety**: The `cleanup()` method explicitly shuts down both the kernel client and manager to prevent resource leaks.

## Frequently Asked Questions

### How does the local Jupyter-based code interpreter ensure notebook files are always up to date?

The interpreter calls `write_to_notebook()` from the `NotebookSerializer` class immediately after every mutation—whether adding a code cell, capturing stdout, or recording an error. This synchronous persistence strategy ensures the disk file mirrors the interactive session state without waiting for the session to end.

### Where are the notebook files stored in the MathModelAgent repository?

Notebook files are stored in task-specific working directories determined during `NotebookSerializer` initialization. The serializer constructs an absolute path combining the provided `work_dir` with the task identifier, creating a standard `.ipynb` file that can be opened in JupyterLab or VS Code.

### What happens if the code execution produces an error?

When the kernel returns an error message, the interpreter captures it through the iopub channel, strips color control characters using `delete_color_control_char()`, and adds it as a Jupyter error output cell via the serializer. The error flag is returned to the caller, and the complete traceback is preserved in the notebook file for later review.

### How does the interpreter prevent orphaned kernel processes?

The `cleanup()` method in `LocalCodeInterpreter` performs an orderly shutdown by first closing the kernel client connection (`self.kc.shutdown()`) and then terminating the kernel manager (`self.km.shutdown_kernel()`). This two-step process ensures all subprocesses and ZMQ connections are properly released when the task completes.