# How Notebook Serialization Preserves Code Execution History in MathModelAgent

> Discover how MathModelAgent preserves code execution history through notebook serialization. Learn how the NotebookSerializer captures cells, outputs, errors, and images in real-time.

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

---

**MathModelAgent preserves code execution history by serializing every interaction into a Jupyter notebook through the `NotebookSerializer` class, which captures code cells, outputs, errors, and images in real-time as the interpreter executes.**

The `jihe520/mathmodelagent` repository implements a robust notebook serialization system that transforms live Python execution into reproducible Jupyter notebooks. This mechanism ensures that every step of a mathematical modeling task—from data exploration to visualization—is permanently recorded with full fidelity. By leveraging the `NotebookSerializer` class located in [`backend/app/tools/notebook_serializer.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/tools/notebook_serializer.py), the system creates an immutable audit trail of the entire coding session.

## The Core Mechanism: NotebookSerializer Architecture

The foundation of code execution history preservation rests on the **`NotebookSerializer`** class. This component acts as a persistent log layer between the interpreter and the file system, converting transient kernel interactions into structured notebook cells.

When instantiated, the serializer immediately initializes an empty notebook structure using `nbf.new_notebook()` and establishes a storage location through the `init_notebook` method. The `__init__` method creates the notebook object (`self.nb = nbf.new_notebook()`), while `init_notebook` computes the definitive file path (`self.notebook_path`) based on the working directory and session name.

## Step-by-Step Execution Flow

### Initializing the Notebook

The serialization process begins with notebook creation. The serializer establishes the file path and prepares the internal cell list during initialization. This ensures that every subsequent execution event has a dedicated destination for persistence.

### Capturing Code Cells

Each time the interpreter receives a user-generated code snippet, it invokes `add_code_cell_to_notebook`. This method creates a new code cell using `nbf.new_code_cell` and appends it to `self.nb["cells"]`. The notebook file is written immediately to disk, guaranteeing that the on-disk representation always reflects the latest state even if the process terminates unexpectedly.

### Recording Standard Output and Errors

After kernel execution completes, the system captures outputs through specialized methods. For standard text output, `add_code_cell_output_to_notebook` converts ANSI escape sequences to HTML using `ansi_to_html` and constructs a Jupyter *display_data* output attached to the most recent code cell (`self.nb["cells"][-1]["outputs"]`).

When execution fails, `add_code_cell_error_to_notebook` creates an *error* output and attaches it to the current cell. This ensures that stack traces and exception messages are preserved alongside the code that generated them.

### Handling Visual Outputs (Images)

For graphical outputs, `add_image_to_notebook` processes binary image data (PNG or JPEG) and creates a `display_data` output with the appropriate MIME type (`image/png` or `image/jpeg`). This method captures matplotlib plots, seaborn visualizations, or any other image-based output generated during the modeling session.

## Segmentation and Logical Grouping

MathModelAgent enhances notebook serialization with **segmentation awareness**, grouping cells into logical sections such as "EDA" or "Modeling". The method `add_markdown_segmentation_to_notebook` records the current segmentation name and initializes a per-section output buffer (`self.segmentation_output_content`).

Every output cell updates this buffer, enabling later retrieval of all HTML output for a specific segment via `get_notebook_output_content`. This feature allows users to extract and review specific phases of their modeling workflow without parsing the entire notebook.

## Integration with the Interpreter

The serialization system tightly couples with execution engines through the `LocalCodeInterpreter` class in [`backend/app/tools/local_interpreter.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/tools/local_interpreter.py). During the `execute_code` workflow, the interpreter follows a strict sequence:

1. Calls `self.notebook_serializer.add_code_cell_to_notebook(code)` before execution
2. Invokes `add_code_cell_output_to_notebook` for stdout and rich text results
3. Triggers `add_image_to_notebook` for visual outputs
4. Executes `add_code_cell_error_to_notebook` when exceptions occur

This integration ensures that **every execution event**—code submission, stdout streams, rich HTML, images, and errors—is persisted in the notebook in the exact chronological order of occurrence.

## Practical Implementation Example

The following example demonstrates the complete serialization workflow as implemented in the MathModelAgent codebase:

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

# 1️⃣ Initialise the serializer (creates notebook.ipynb in the working directory)

serializer = NotebookSerializer(work_dir="/tmp/jupyter", notebook_name="session.ipynb")

# 2️⃣ Add a code cell

serializer.add_code_cell_to_notebook("import numpy as np\nnp.arange(5)")

# 3️⃣ Simulate a kernel output (plain text)

serializer.add_code_cell_output_to_notebook("[stdout]\n[0 1 2 3 4]")

# 4️⃣ Add a markdown section that starts a new logical segment

serializer.add_markdown_segmentation_to_notebook(
    "### Data Exploration", segmentation="eda"

)

# 5️⃣ Add an image (binary PNG data already base‑64‑decoded)

with open("plot.png", "rb") as f:
    png_bytes = f.read()
serializer.add_image_to_notebook(png_bytes, "image/png")

# 6️⃣ Retrieve all HTML output for the “eda” segment

html = serializer.get_notebook_output_content("eda")
print(html)   # → concatenated HTML of all outputs captured under the “eda” segment

```

This implementation mirrors the internal workflow used by the interpreter, demonstrating how each execution step is immediately serialized into the notebook file at [`backend/app/tools/notebook_serializer.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/tools/notebook_serializer.py).

## Summary

- **Real-time persistence**: The `NotebookSerializer` class writes every code cell and output to disk immediately, preventing data loss during execution.
- **Comprehensive capture**: The system records code, stdout, HTML-rich output, images, and errors through specialized methods like `add_code_cell_to_notebook` and `add_image_to_notebook`.
- **Structural organization**: Segmentation features allow logical grouping of cells into workflow phases (EDA, Modeling) with retrievable output buffers.
- **Interpreter integration**: `LocalCodeInterpreter` in [`backend/app/tools/local_interpreter.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/tools/local_interpreter.py) tightly couples execution with serialization, ensuring chronological accuracy.
- **Reproducible artifacts**: The final Jupyter notebook in [`backend/app/core/workflow.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/core/workflow.py) serves as a complete, replayable record of the mathematical modeling session.

## Frequently Asked Questions

### How does MathModelAgent prevent data loss if the kernel crashes during execution?

The `NotebookSerializer` writes the notebook file to disk immediately after every cell addition and output capture. Because `add_code_cell_to_notebook` and related methods persist changes to `self.notebook_path` instantly rather than buffering in memory, the on-disk notebook always reflects the latest state regardless of kernel stability.

### Can the notebook serialization system handle non-Python outputs like images or HTML?

Yes. The `add_image_to_notebook` method processes binary PNG and JPEG data with proper MIME type headers (`image/png`, `image/jpeg`), while `add_code_cell_output_to_notebook` converts ANSI sequences to HTML. This allows the system to capture matplotlib plots, seaborn visualizations, and rich terminal output within the Jupyter notebook structure.

### What is the purpose of segmentation in the notebook serialization process?

Segmentation allows the agent to group notebook cells into logical workflow phases such as "EDA" or "Modeling" using `add_markdown_segmentation_to_notebook`. The system maintains a dictionary (`self.segmentation_output_content`) that buffers all HTML output for each segment, enabling later retrieval of phase-specific results via `get_notebook_output_content` without parsing the entire notebook.

### Where does the integration between the interpreter and serializer occur in the codebase?

The integration occurs in [`backend/app/tools/local_interpreter.py`](https://github.com/jihe520/mathmodelagent/blob/main/backend/app/tools/local_interpreter.py) within the `execute_code` method (lines 58-90 and 96-111). Here, the `LocalCodeInterpreter` calls serializer methods immediately before and after kernel execution, ensuring that code, outputs, and errors are captured in the exact sequence they occur during the modeling task.