Facio's Notebook Editing: How AI Agents Read and Modify Jupyter Notebooks at the Cell Level
Jupyter notebooks are the lingua franca of data science. Every data scientist, ML engineer, and analytics professional has notebooks in their workflow — exploratory analysis, model training, results reporting, stakeholder presentations. But to most AI agents, notebooks are an opaque blob: a .ipynb file is JSON wrapped around interleaved code, outputs, and metadata, and the agents that try to edit them typically break them in subtle ways.
Facio's notebook_edit tool fixes this by working at the cell level, not the file level. The agent reads, replaces, inserts, and deletes individual code and markdown cells — preserving notebook structure, keeping outputs intact, and never accidentally clobbering metadata. Here's why notebook-aware editing matters and how it enables data science agent workflows.
Why Notebooks Are Hard for Agents
A Jupyter notebook file is JSON. Inside the JSON, cells are an array of objects — each with a cell type (code or markdown), a source array, execution counts, metadata, and (for code cells) an outputs array. The structure is precise, version-sensitive, and easy to break.
When an agent uses a generic file editing tool on a notebook, several things go wrong:
-
The agent edits "the code" but doesn't realize it's modifying a string array. Jupyter
sourcefields are arrays of strings (one per line), not single strings. Naive text replacement can produce invalid JSON. -
Outputs get clobbered. Outputs include
data,metadata, and sometimes large base64-encoded images. A careless overwrite destroys the rendered output that took minutes to generate. -
Execution counts get reset. A notebook that took 200 minutes of kernel time to run gets its
execution_countset tonullbecause the agent rewrote the source. -
Cell types get confused. Replacing a code cell with markdown (or vice versa) silently changes what executes when the notebook is run.
The result: notebooks that look right but are subtly broken. Code that "should" work produces different results. Outputs that "should" be there are missing. The notebook no longer reproduces.
Cell-Level Operations
Facio's notebook_edit tool supports four operations, each at the cell level:
| Operation | What it does | Use case |
|---|---|---|
replace | Replace the content of an existing cell at a specific index | Fix a bug, update a function, add a parameter |
insert | Add a new cell after a specific index | Add a new analysis step, prepend documentation |
delete | Remove a cell at a specific index | Drop a dead-end exploration, clean up scratch work |
The tool handles all the structural details: keeping source as an array of strings, preserving execution counts and metadata, maintaining the cell type, and keeping outputs intact for cells that aren't being modified.
# Replace cell #5 (a code cell with a buggy function)
notebook_edit(
path="projects/analysis/main.ipynb",
cell_index=5,
new_source="def calculate_metrics(data):\n return data.agg(['mean', 'std', 'count'])\n",
cell_type="code",
edit_mode="replace"
)
The cell type and edit mode are explicit. The tool never infers them from context — which is exactly what prevents the silent type confusion that breaks notebooks in less-aware agents.
The Three Edit Modes
Replace
Replace the source of an existing cell, preserving all other fields (cell type, execution count, outputs, metadata). The agent modifies code or markdown in place — outputs from the original cell (if any) stay attached to the new source, which is usually what you want for a code fix that produces different but still-valid output.
For code cells, replacing the source means a subsequent kernel run will produce fresh outputs — but the old outputs remain in the file until a kernel execution regenerates them. This is correct behavior: the file accurately reflects what the cell produced last, even if the source has since been updated.
Insert
Add a new cell after a specific index. The new cell has no execution count and no outputs (it hasn't been run yet). Insert preserves the relative order of existing cells and slots the new cell in at the requested position.
This is the operation for adding a new analysis step, injecting a debugging cell, or prepending documentation. The agent can build up notebooks incrementally — cell by cell, step by step — without rewriting the entire file.
Delete
Remove a cell at a specific index. The deletion is final (no soft-delete or undo), and the remaining cells shift their indices accordingly. The agent can clean up exploratory cells that didn't lead anywhere, remove duplicate imports, or strip scratch work from a final-report notebook.
Integration with the Workspace and Credential Systems
Notebook editing slots naturally into the broader Facio workspace and credential systems:
- Notebooks live in
projects/. Following the WORKSPACE.md conventions, substantial multi-file work — including notebooks — belongs inprojects/<project-name>/. The agent finds the notebook withgloborgrep, edits the specific cell, and continues the workflow. - Notebooks can reference credentials. A data pipeline notebook that pulls from a database can use
${credentials.DATABASE_URL}placeholders in the code, resolved at execution time. The agent edits the cell, the placeholder stays in place, the kernel resolves it. - Notebooks integrate with the file tool surface.
read_file(path="...")returns the raw notebook JSON, butnotebook_editprovides the structured alternative. The agent picks the right tool for the operation.
Production Patterns
Pattern 1: Automated Notebook Cleanup
After running an exploratory analysis, the notebook has 50 cells — some with the actual analysis, some with dead-end explorations, some with debugging prints. The agent:
read_fileto see the structure.notebook_edit(action="read", cell_index=N)to inspect each cell.notebook_edit(edit_mode="delete", cell_index=M)to remove dead cells.notebook_edit(edit_mode="replace", cell_index=K, new_source="...")to clean up the surviving cells.- The result: a focused, publishable notebook — produced by the agent, not the human.
Pattern 2: Versioned Analysis Updates
A quarterly report notebook has been running for a year. Q3 2026 needs a new metric. The agent:
- Greps the notebook to find the metrics section.
notebook_edit(edit_mode="insert", cell_index=42, new_source="new_metric = ...", cell_type="code")to add the new metric calculation.notebook_edit(edit_mode="replace", cell_index=44, new_source="updated_table", cell_type="code")to update the visualization cell.- The notebook now has the new analysis, the old analysis is preserved, and outputs will regenerate on the next kernel run.
Pattern 3: Documentation Generation
A complex data pipeline notebook needs documentation. The agent:
- Reads the notebook cell by cell.
- For each code cell, generates a markdown explanation.
notebook_edit(edit_mode="insert", cell_index=N, new_source="## Explanation\n\nThis cell calculates...", cell_type="markdown")to insert a markdown cell above each code cell.- The result: a self-documenting notebook, generated by the agent from the code itself.
Pattern 4: Multi-Agent Notebook Collaboration
A parent agent spawns three sub-agents — one for data loading, one for feature engineering, one for model training. Each sub-agent works on a different section of the same notebook:
- Parent agent creates the initial notebook structure.
- Sub-agent 1:
notebook_edit(insert, cell_index=5, ...)adds data loading cells. - Sub-agent 2:
notebook_edit(insert, cell_index=10, ...)adds feature engineering cells. - Sub-agent 3:
notebook_edit(insert, cell_index=20, ...)adds model training cells. - Parent agent reads the final notebook and presents it for human review.
The sub-agents don't step on each other because they work on different cell ranges. The notebook is a shared collaboration surface with deterministic ordering.
What notebook_edit Doesn't Do
The tool has deliberate constraints:
- It doesn't execute notebooks. The agent edits the file structure, not the kernel state. For execution, the agent uses
execto run Python scripts directly, or spawns sub-agents that have their own kernel access. - It doesn't manage kernels. Jupyter kernels (Python 3, R, Julia) are external to the notebook file. The agent edits the file; a separate process (or human) runs it.
- It doesn't validate syntax. If the agent writes invalid Python, the tool writes it anyway. The error surfaces when the cell runs. This is correct behavior — the agent should be able to write experimental code that fails to run, and learn from the error.
Bottom Line
Data science lives in notebooks. AI agents that can't edit notebooks cleanly aren't really data science agents — they're text agents that happen to know what .ipynb is. The difference matters when notebooks are the deliverable: a quarterly analysis report, a model training pipeline, a reproducible research artifact.
Facio's notebook_edit gives agents true notebook awareness: cell-level operations, structural preservation, output-aware updates. The agent modifies a single function without destroying the outputs of every other cell. It inserts a documentation cell without resetting execution counts. It deletes a dead-end exploration without breaking the JSON.
Because a notebook that runs is worth more than a notebook that's "fixed" but no longer reproduces.
See the notebook editing documentation for cell type handling, execution count preservation, and multi-agent collaboration patterns.