Skip to content

Notebook Edit

Notebook editing is the ability to modify .ipynb files from agent tool calls.

Unlike plain source files, notebooks are structured JSON documents with coupled concerns:

  • ordered cell arrays
  • execution counts and outputs
  • language/kernel metadata
  • large embedded output payloads

A notebook edit primitive is useful for data-science workflows, but fragile if treated as ordinary text.

The core problem is representation mismatch.

Users think in cells.

Patch engines usually operate on text lines.

That mismatch produces specific failure modes:

  • JSON is syntactically valid, but the notebook is structurally invalid
  • the model accidentally edits the wrong cell because it is “patching JSON text” instead of addressing a cell
  • the notebook becomes enormous because outputs get re-embedded into the file
  • the diff becomes unreadable because transient metadata churn dominates

The reliability bar for notebook edits is higher than for source code edits because the file format is both structured and noisy.

In practice, an .ipynb notebook is a JSON object with at least:

  • cells: array
  • metadata: object
  • nbformat: number
  • nbformat_minor: number

Cells are objects with at least:

  • cell_type: string (commonly code or markdown)
  • metadata: object
  • source: string or array of strings

Code cells commonly also have:

  • execution_count: number or null
  • outputs: array

Some notebooks also include a per-cell identifier field (commonly id), but this is not universal across all notebooks.

This matters because the best cell addressing mechanism is by a stable id, but many real notebooks require addressing by index or by content matching.

There are two different product meanings:

  1. raw JSON editing
  • “edit this file” using a generic patch tool
  • the agent treats the notebook as text
  1. semantic notebook editing
  • “edit cell #7” or “insert a markdown cell after the imports”
  • the agent treats the notebook as a notebook and preserves invariants

The reference repos mostly implement (1).

OpenOxide should aim for (2), while retaining (1) as a fallback.


  • File Edit for the shared patch execution pipeline used by notebook-as-text fallbacks.
  • File Write for full-document rewrite tradeoffs.
  • Edit Formats for patch grammar details not repeated in this page.

Aider at commit b9050e1d5faf8096eae7a46a9ecc05a86231384b does not provide a dedicated notebook-edit tool.

It edits notebooks via its generic file editing pipeline.

Aider has a “patch” edit format implemented by PatchCoder in references/aider/aider/coders/patch_coder.py.

Key facts:

  • PatchCoder.edit_format = "patch"
  • it uses prompts defined in references/aider/aider/coders/patch_prompts.py
  • it parses a V4A-like patch format:
    • *** Begin Patch
    • *** Update File: ...
    • hunks introduced by @@
    • + / - / lines

The prompt is explicit that the assistant should “describe the changes using the V4A diff format.”

That means notebooks are treated as normal files whose contents happen to be JSON.

PatchCoder.get_edits():

  • reads the assistant response text
  • detects patch sentinels (*** Begin Patch)
  • tolerates missing sentinels if the content looks patch-like (warns)
  • identifies which files are needed for parsing (identify_files_needed scans *** Update File: and *** Delete File:)
  • reads current file content for needed paths
  • parses patch actions into a Patch model (PatchAction, Chunk, etc.)

The parsing and application logic is line-based.

It includes fuzziness tolerance:

  • _norm() strips CR for CRLF compatibility
  • find_context_core() attempts exact match, rstrip match, and strip match with different fuzz penalties
  • find_context() includes an EOF marker behavior (*** End of File) to anchor at end

None of these are notebook-aware.

They are generic text patch affordances.

Aider Prompting and Its Notebook Implications

Section titled “Aider Prompting and Its Notebook Implications”

The patch prompt rules in references/aider/aider/coders/patch_prompts.py enforce:

  • each file appears only once in the patch
  • context lines must match “character for character, including indentation”
  • use full file path

This is compatible with .ipynb editing, but it shifts the burden to the model:

  • the model must avoid touching large output blocks
  • the model must preserve JSON structure

There is no cell addressing abstraction.

Aider’s history file (references/aider/aider/website/HISTORY.md) includes a release note:

  • “Improved editing performance on Jupyter Notebook .ipynb files.”

The codebase does not expose a notebook-specific editing tool.

So any improvements are likely in the general patch/text edit pipeline (parsing, matching, or formatting), not semantic cell transforms.

Aider’s notebook edits are:

  • possible
  • generic
  • fragile

You can get good results if you constrain the diff:

  • only edit small, stable source regions
  • avoid editing outputs unless explicitly requested
  • avoid changing ordering in the cells array

But there is no dedicated mechanism to make these constraints automatic.


Codex at commit 4ab44e2c5cc54ed47e47a6729dfd8aa5a3dc2476 also edits notebooks via the general apply_patch tool.

Codex does not implement a notebook-specific tool.

Codex exposes apply_patch through its tool registry.

Codex prompt instructions explicitly tell the assistant:

  • to use the apply_patch tool for edits
  • not to re-read files after calling apply_patch

These instructions appear in:

  • references/codex/codex-rs/core/gpt_5_2_prompt.md
  • references/codex/codex-rs/protocol/src/prompts/base_instructions/default.md

Those instructions are generally correct for source code edits.

For notebooks, they are more dangerous.

Notebook correctness often requires validating the produced JSON structure, which may require re-reading or at least verifying the edited file.

Codex applies patches through the same apply_patch machinery documented in features/tools/file-edit.

At a high level, apply_patch:

  • parses the patch text
  • verifies patch grammar
  • applies hunks to files
  • uses the approval/permission system for file mutations

There is no branch like:

  • “if extension is .ipynb then do special handling”

So .ipynb is treated as any file.

Notebook edits inherit the generic edit guardrails:

  • approval overlays can show diff summaries
  • sandbox/policy can restrict what paths can be modified
  • errors are returned when patch hunks fail to apply

This is good.

But it does not enforce notebook semantics.

Codex tests focus on patch correctness at the text level.

There is no dedicated “notebook edit” subsystem in the core.

So:

  • no fixtures for notebooks
  • no tests around stripping outputs
  • no tests around cell-level operations

Notebook support is incidental through apply_patch.


OpenCode at commit 7ed449974864361bad2c1f1405769fd2c2fcdf42 similarly uses its generic edit mechanisms for notebooks.

It does not implement cell-aware notebook edits.

OpenCode exposes apply_patch via references/opencode/packages/opencode/src/tool/apply_patch.ts.

OpenCode’s tool registry chooses between patch-style editing and edit/write tools depending on model:

  • GPT-family models use apply_patch
  • others use edit / write

So notebook editing on GPT models often uses the patch format.

Mechanics of patch application are documented in features/tools/file-edit.

Notebook-specific handling is not present.

Because notebook edits pass through the normal edit plumbing, they gain:

  • permission gating
  • consistent CLI/TUI tool rendering
  • filesystem watcher updates
  • output truncation policies for tool outputs (separate system)

But they do not gain:

  • a semantic model of cells
  • stable cell addressing
  • automatic output stripping

There is no notebook_edit tool id.

Notebook edits are performed using general tools:

  • apply_patch
  • edit
  • write

This means:

  • the model is not guided into safe notebook operations by tool shape
  • the UI cannot present a notebook-specific diff view

A notebook can be valid JSON but still be invalid for Jupyter.

Common structural errors:

  • missing top-level keys
  • cells missing required fields
  • wrong types (string vs array-of-strings in source)
  • outputs containing unexpected object shapes

Generic patch tools do not catch these.

The biggest failure mode is output bloat.

A single plotted image output can add:

  • huge base64 strings
  • large data blobs
  • long metadata maps

This can:

  • exceed tool limits
  • blow up token budget
  • make diffs unreadable

Execution Metadata Churn Pollutes Version Control

Section titled “Execution Metadata Churn Pollutes Version Control”

Even when code changes are small, notebook diffs can include:

  • execution_count updates
  • output timestamps
  • kernel metadata noise

Agents should not change these unless explicitly asked.

When addressing by index (“edit cell 10”), any insertion earlier shifts indices.

A model that is patching JSON text can accidentally:

  • edit the wrong cell
  • insert in the wrong location

This becomes catastrophic when many cells contain similar code.

Text-Level Patches Are Fragile Against Reformatting

Section titled “Text-Level Patches Are Fragile Against Reformatting”

Some notebook writers normalize JSON formatting.

If the file is reserialized:

  • whitespace changes
  • key ordering changes
  • source becomes string vs array

Text patch context matching becomes brittle.

OpenCode’s post-edit LSP diagnostics are valuable for .ts, .rs, etc.

For .ipynb:

  • most language servers do not understand notebook JSON
  • you may only get generic JSON parse errors

So notebooks need a dedicated validator.

Generic patch tools can undo file-level edits.

But users think in cells.

Undo at file granularity makes it hard to revert a single cell change while preserving other edits.

Tools Encourage “Don’t Re-Read” but Notebooks Often Require Verification

Section titled “Tools Encourage “Don’t Re-Read” but Notebooks Often Require Verification”

Codex’s base guidance (“do not re-read files after apply_patch”) is good for speed.

For notebooks, it can produce silent corruption.

OpenOxide should treat notebook edits as a special-case where verification is default.


The blueprint should aim for semantic notebook operations.

The references demonstrate that “just patch JSON” exists, but it is not robust.

  1. apply_patch fallback mode for raw text/JSON edits
  • for edge cases
  • for users who explicitly want raw diff edits
  1. dedicated notebook_edit semantic mode
  • for cell-level inserts/replaces/deletes
  • for output stripping and normalization
  • for stable diffs

A practical tool contract is an operation list:

  • path: string
  • operations: [...]

Operation variants:

  • replace_cell

    • cell_id?: string
    • index?: number
    • new_cell_type?: "code" | "markdown" | "raw"
    • new_source: string
    • preserve_outputs?: bool
  • insert_cell

    • index: number (insert before index)
    • cell_type: "code" | "markdown" | "raw"
    • source: string
  • delete_cell

    • cell_id?: string
    • index?: number
  • clear_outputs

    • mode: "all" | "code_cells"
  • set_metadata

    • key_path: string[]
    • value: any

This contract makes the model talk in notebook terms.

Preferred:

  • address by stable cell id (when present)

Fallbacks:

  • address by index
  • address by content match:
    • “first code cell whose source starts with import pandas as pd

OpenOxide should expose helper reads to support these:

  • notebook_read returning a compact cell list:
    • index
    • cell_type
    • id (if present)
    • first N lines of source

After applying semantic operations:

  1. parse notebook JSON into typed structs
  2. validate required keys and cell invariants
  3. optionally normalize fields:
    • enforce source as array-of-strings or string consistently
  4. write back with stable formatting

On validation failure:

  • do not write the file
  • return a structured error describing the failing invariant

Notebook diff hygiene must be policy-driven.

Policies worth supporting:

  • strip_outputs: default true
  • clear_execution_count: default true
  • preserve_metadata_keys: allowlist (default small)
  • drop_transient_metadata_keys: denylist

This makes diffs reviewable.

It also prevents agent-induced output bloat.

A notebook edit tool should produce a notebook-aware diff view:

  • show which cells changed
  • show inserted/deleted cell counts
  • show source diffs per cell

Approval overlays should be cell-first.

Raw JSON diffs should be optional.

Notebook edits should be applied atomically:

  • write to a temp file
  • fsync
  • replace

Undo should operate at the semantic layer:

  • capture “before cell” snapshots
  • allow reverting only selected cell edits

Unit tests:

  • parse/serialize roundtrips
  • cell operation application correctness
  • output stripping and metadata normalization

Golden tests:

  • real notebooks with:
    • large outputs
    • mixed cell types
    • missing cell ids
    • different source encodings

Fuzz tests:

  • random operation sequences should not produce invalid notebooks
  • serde_json for parsing
  • an internal nbformat-compatible schema module (structs + validation)
  • OpenOxide patch/permission/event crates
  • notebooks should be semantic-first, patch-second
  • validation must happen before write
  • diff hygiene must be configurable
  • UI should render cell-level changes