<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Jupyter Blog - notebook format</title><link href="https://blog.jupyter.org/" rel="alternate"/><link href="https://blog.jupyter.org/feeds/tag-notebook-format.atom.xml" rel="self"/><id>https://blog.jupyter.org/</id><updated>2026-05-11T19:04:00+00:00</updated><subtitle>The notebook file format (.ipynb) and the tools that read, write, or convert notebook files.</subtitle><entry><title>nb-cli: A Command-Line Interface for AI Agents and Notebook Automation</title><link href="https://blog.jupyter.org/posts/2026/nb-cli-a-command-line-interface-for-ai-agents-and/" rel="alternate"/><published>2026-05-11T19:04:00+00:00</published><updated>2026-05-11T19:04:00+00:00</updated><author><name>Piyush Jain</name></author><id>tag:blog.jupyter.org,2026-05-11:/posts/2026/nb-cli-a-command-line-interface-for-ai-agents-and/</id><summary type="html">&lt;p&gt;The rise of AI coding agents has transformed how we think about developer tools. Large language models like Claude, GPT, and others are remarkably effective at using command-line interfaces — they’ve been trained on billions of lines of CLI usage from documentation, Stack Overflow, and GitHub.&lt;/p&gt;</summary><content type="html">&lt;p&gt;The rise of AI coding agents has transformed how we think about developer tools. Large language models like Claude, GPT, and others are remarkably effective at using command-line interfaces — they’ve been trained on billions of lines of CLI usage from documentation, Stack Overflow, and GitHub. But when it comes to working with Jupyter notebooks programmatically, there’s been a gap: existing tools focus on running agents within notebooks, but what about agents that need to work with notebooks as artifacts?&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/jupyter-ai-contrib/nb-cli"&gt;&lt;strong&gt;nb-cli&lt;/strong&gt;&lt;/a&gt;, an experimental open-source command-line interface designed specifically for AI agents, automation scripts, and developers who need programmatic access to Jupyter notebooks. Built with Rust for performance and reliability, nb-cli provides a fast, composable way to read, write, execute, and manipulate notebooks through a clean command line interface that follows the &lt;a href="https://nbformat.readthedocs.io/en/latest/"&gt;nbformat&lt;/a&gt; specification.&lt;/p&gt;
&lt;h2 id="the-problem-notebooks-as-black-boxes"&gt;The Problem: Notebooks as Black Boxes&lt;/h2&gt;
&lt;p&gt;While Jupyter notebooks are indispensable for interactive exploration, their underlying &lt;code&gt;.ipynb&lt;/code&gt; JSON structure has long been a friction point for programmatic interaction, especially for shell scripts and Large Language Models (LLMs).&lt;/p&gt;
&lt;p&gt;Traditional workflows often break down when automation or AI-driven analysis is required. Consider the following scenarios where standard notebook interfaces prove insufficient:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Autonomous Analysis&lt;/strong&gt;: An AI agent tasked with auditing a data science workflow must programmatically inspect individual cells to map out the analysis pipeline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automated Validation&lt;/strong&gt;: CI/CD systems require a reliable method to execute notebooks, validate outputs, and catch errors before deployment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation at Scale&lt;/strong&gt;: Developers need tools to automatically transform notebook content into clean, accessible documentation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Production Debugging&lt;/strong&gt;: Teams need a way to troubleshoot notebook execution failures in headless production environments without manual intervention.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Notebooks as Data&lt;/strong&gt;: Analysts may want to treat a notebook as a structured database to programmatically generate business reports, research summaries, or custom visualizations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Historically, solving these problems required labor-intensive workarounds like manually navigating the JupyterLab UI, writing brittle Python scripts to parse complex JSON files, or using execution tools that lack real-time integration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;nb-cli&lt;/strong&gt; bridges this gap by offering a CLI-first interface designed for the modern era of automation. By leveraging command-line patterns and Unix composability, it provides the structured output and predictable interface that AI agents and developers need to treat notebooks as first-class citizens in any software stack.&lt;/p&gt;
&lt;h2 id="key-features"&gt;Key Features&lt;/h2&gt;
&lt;h3 id="works-with-or-without-a-jupyter-server"&gt;Works With or Without a Jupyter Server&lt;/h3&gt;
&lt;p&gt;nb-cli doesn’t require a running Jupyter server. By default, it reads and writes &lt;code&gt;.ipynb&lt;/code&gt; files directly and communicates with kernels over ZeroMQ for execution. This makes it well-suited for scripting, CI pipelines, and any workflow where launching a server is unnecessary overhead.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create a notebook — no server needed&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb

&lt;span class="c1"&gt;# Add cells&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;cell&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;--source&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;import pandas as pd&amp;quot;&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;cell&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;--source&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;# Data Analysis&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--type&lt;span class="w"&gt; &lt;/span&gt;markdown

&lt;span class="c1"&gt;# Execute&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;execute&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb

&lt;span class="c1"&gt;# Read back with outputs&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Connecting to a server becomes valuable if multiple users and/or agents are editing the same notebook simultaneously within a JupyterLab session. Once connected, nb-cli uses Y.js, the same CRDT protocol JupyterLab uses internally for conflict-free real-time synchronization.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Auto-detect and connect to local Jupyter server&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;connect

&lt;span class="c1"&gt;# Or, connect to a specific server&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;connect&lt;span class="w"&gt; &lt;/span&gt;--server&lt;span class="w"&gt; &lt;/span&gt;http://localhost:9999&lt;span class="w"&gt; &lt;/span&gt;--token&lt;span class="w"&gt; &lt;/span&gt;abc

&lt;span class="c1"&gt;# Add a cell - it appears instantly in JupyterLab&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;cell&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;experiment.ipynb&lt;span class="w"&gt; &lt;/span&gt;--source&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;df.head()&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Execute via the remote kernel&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;execute&lt;span class="w"&gt; &lt;/span&gt;experiment.ipynb&lt;span class="w"&gt; &lt;/span&gt;--cell&lt;span class="w"&gt; &lt;/span&gt;fe456

&lt;span class="c1"&gt;# Restart the kernel before execution for reproducibility checks&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;execute&lt;span class="w"&gt; &lt;/span&gt;experiment.ipynb&lt;span class="w"&gt; &lt;/span&gt;--restart-kernel

&lt;span class="c1"&gt;# Disconnect&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;disconnect
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When connected to a Jupyter server, &lt;strong&gt;nb-cli&lt;/strong&gt; detects whether a notebook is open in JupyterLab and uses server APIs for conflict-free collaborative editing. If the notebook isn’t open, it seamlessly falls back to file-based operations.&lt;/p&gt;
&lt;h3 id="ai-optimized-markdown-format"&gt;AI-Optimized Markdown Format&lt;/h3&gt;
&lt;p&gt;Language models don’t parse JSON, they predict tokens. This distinction matters more than you’d think when you’re building a tool that feeds notebook content into an LLM’s context window. Jupyter’s native notebook format is deeply nested JSON. Source code is stored as arrays of strings. Outputs carry base64-encoded blobs. Metadata nests several levels deep. This is fine for a JSON parser, but for a language model working within a fixed context window, 30–40% of those tokens are structural characters — braces, brackets, escaped newlines that carry no semantic value. Another common option is plain Markdown which is token-efficient and human-readable, but it’s ambiguous. A # could be a markdown heading or a Python comment. A fenced code block could be a notebook cell or an example inside a markdown cell’s documentation. When an LLM is asked to “fix the error in cell 7,” it needs to reliably locate that cell in the text and plain markdown gives it no structural markers to count on, just a sequence of code fences that all look the same.&lt;/p&gt;
&lt;p&gt;So we designed a line-oriented sentinel format:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;@@&lt;/span&gt;&lt;span class="kc"&gt;n&lt;/span&gt;&lt;span class="err"&gt;o&lt;/span&gt;&lt;span class="kc"&gt;te&lt;/span&gt;&lt;span class="err"&gt;book&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;format&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ai-notebook&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;metadata&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;kernelspec&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;python3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;

&lt;span class="err"&gt;@@cell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;index&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;f68t57&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;cell_type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;code&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;execution_count&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;```py&lt;/span&gt;&lt;span class="kc"&gt;t&lt;/span&gt;&lt;span class="err"&gt;ho&lt;/span&gt;&lt;span class="kc"&gt;n&lt;/span&gt;
&lt;span class="err"&gt;d&lt;/span&gt;&lt;span class="kc"&gt;f&lt;/span&gt;&lt;span class="err"&gt;.head()&lt;/span&gt;
&lt;span class="err"&gt;```&lt;/span&gt;
&lt;span class="err"&gt;@@ou&lt;/span&gt;&lt;span class="kc"&gt;t&lt;/span&gt;&lt;span class="err"&gt;pu&lt;/span&gt;&lt;span class="kc"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;output_type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;execute_result&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;```&lt;/span&gt;&lt;span class="kc"&gt;te&lt;/span&gt;&lt;span class="err"&gt;x&lt;/span&gt;&lt;span class="kc"&gt;t&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;col_a&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;col_b&lt;/span&gt;
&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;
&lt;span class="err"&gt;```&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The format makes a few deliberate tradeoffs. &lt;code&gt;@@cell&lt;/code&gt; and &lt;code&gt;@@output&lt;/code&gt; sentinels give the model unambiguous structural boundaries without counting braces or tracking nesting. Inline JSON metadata on each sentinel line places cell type, index, and execution count in the tokens immediately before the content — matching how attention mechanisms locate information. Code in fenced blocks with language hints activates the model’s syntax-level training. And because each cell block is self-contained, truncation degrades gracefully — unlike JSON, where a cut anywhere breaks the entire structure.&lt;/p&gt;
&lt;h3 id="designed-for-composability"&gt;Designed for Composability&lt;/h3&gt;
&lt;p&gt;nb-cli follows Unix conventions — plain text output, stdin support, predictable exit codes — so it composes naturally with other CLI tools. For AI agents, this matters because a single shell command can replace what would otherwise be multiple tool calls with intermediate parsing.&lt;/p&gt;
&lt;p&gt;Consider an agent asked to “add a summary section to the notebook and run it.” Without nb-cli, this requires separate API calls to read the notebook, parse the structure, insert a cell, write the file, find a kernel, execute, read back the output — each consuming tokens for the request and response. With nb-cli:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;nb&lt;span class="w"&gt; &lt;/span&gt;cell&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;--source&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt;&amp;#39;EOF&amp;#39;&lt;/span&gt;
&lt;span class="s"&gt;@@markdown&lt;/span&gt;
&lt;span class="s"&gt;# Summary&lt;/span&gt;

&lt;span class="s"&gt;@@code&lt;/span&gt;
&lt;span class="s"&gt;print(f&amp;quot;Rows: {len(df)}, Columns: {len(df.columns)}&amp;quot;)&lt;/span&gt;
&lt;span class="s"&gt;df.describe()&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nb&lt;span class="w"&gt; &lt;/span&gt;execute&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;-2&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;-1&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;-1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Three operations — add cells, execute them, read the result — in a single shell invocation. The agent gets back only the output it needs without re-reading the entire notebook.&lt;/p&gt;
&lt;p&gt;The same principle applies to debugging. An agent investigating a failed notebook doesn’t need to read every cell to find the problem.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Find cells with errors — returns only the relevant cells&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;search&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;--with-errors
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;One call, targeted output, no wasted tokens on cells that ran successfully.&lt;/p&gt;
&lt;h3 id="stable-cell-referencing"&gt;Stable Cell Referencing&lt;/h3&gt;
&lt;p&gt;nb-cli supports two ways to reference cells.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Index-based&lt;/strong&gt;:&lt;code&gt;--cell-index 0&lt;/code&gt; (supports negative indexing: -1 = last cell)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ID-based&lt;/strong&gt;: &lt;code&gt;--cell f68t57&lt;/code&gt; (doesn’t change when cells move)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Reference by position&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;cell&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;--cell-index&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--source&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;x = 42&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Reference by stable ID — safe even after cells are reordered&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;cell&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;--cell&lt;span class="w"&gt; &lt;/span&gt;ce456&lt;span class="w"&gt; &lt;/span&gt;--source&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;print(&amp;#39;Done&amp;#39;)&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Execute the last cell&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;execute&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;--cell-index&lt;span class="w"&gt; &lt;/span&gt;-1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="powerful-search-capabilities"&gt;Powerful Search Capabilities&lt;/h3&gt;
&lt;p&gt;nb-cli includes built-in search to quickly locate cells by content, type, or execution errors. By default, search matches against cell source code, but a scope filter extends it to execution outputs.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Search for cells containing a pattern&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;search&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;import pandas&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Find all cells with execution errors&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;search&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;--with-errors

&lt;span class="c1"&gt;# Search within outputs instead of source&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;search&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;KeyError&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--scope&lt;span class="w"&gt; &lt;/span&gt;output

&lt;span class="c1"&gt;# Filter by cell type&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;search&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;TODO&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--cell-type&lt;span class="w"&gt; &lt;/span&gt;markdown
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For AI agents, &lt;code&gt;--with-errors&lt;/code&gt; is particularly useful — instead of reading an entire notebook to find what failed, the agent gets back only the cells that need attention. Combined with &lt;code&gt;--scope output&lt;/code&gt;, it can search error tracebacks directly without parsing every cell’s results. The same capabilities are useful for humans auditing notebooks for deprecated APIs, locating specific functions across large notebooks, or extracting patterns before a refactor.&lt;/p&gt;
&lt;h3 id="multi-cell-operations"&gt;Multi-Cell Operations&lt;/h3&gt;
&lt;p&gt;One of the most common patterns when working with notebooks programmatically is adding a sequence of cells — a markdown header, then setup code, then analysis. Doing this one cell at a time means multiple round-trips and index bookkeeping. Instead, nb-cli accepts multiple cells in a single call using the sentinels format we saw earlier.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Add a markdown header followed by a code cell in one command&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;cell&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;report.ipynb&lt;span class="w"&gt; &lt;/span&gt;--source&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt;&amp;#39;EOF&amp;#39;&lt;/span&gt;
&lt;span class="s"&gt;@@markdown&lt;/span&gt;
&lt;span class="s"&gt;# Results&lt;/span&gt;

&lt;span class="s"&gt;@@code&lt;/span&gt;
&lt;span class="s"&gt;import pandas as pd&lt;/span&gt;
&lt;span class="s"&gt;df = pd.read_csv(&amp;#39;results.csv&amp;#39;)&lt;/span&gt;
&lt;span class="s"&gt;df.head()&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For more control, sentinels also accept the full &lt;code&gt;@@cell {&amp;quot;cell_type&amp;quot;: &amp;quot;...&amp;quot;}&lt;/code&gt; JSON format.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;nb&lt;span class="w"&gt; &lt;/span&gt;cell&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;report.ipynb&lt;span class="w"&gt; &lt;/span&gt;--source&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt;&amp;#39;EOF&amp;#39;&lt;/span&gt;
&lt;span class="s"&gt;@@cell {&amp;quot;cell_type&amp;quot;: &amp;quot;markdown&amp;quot;}&lt;/span&gt;
&lt;span class="s"&gt;# Analysis Header&lt;/span&gt;

&lt;span class="s"&gt;@@cell {&amp;quot;cell_type&amp;quot;: &amp;quot;code&amp;quot;}&lt;/span&gt;
&lt;span class="s"&gt;print(&amp;quot;hello&amp;quot;)&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Both formats work with stdin, making it easy to compose cells from scripts or pipelines.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;printf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;@@markdown\n## Summary\n\n@@code\ndf.describe()\n&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nb&lt;span class="w"&gt; &lt;/span&gt;cell&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;report.ipynb&lt;span class="w"&gt; &lt;/span&gt;--source&lt;span class="w"&gt; &lt;/span&gt;-
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The same batching philosophy extends to execution and deletion:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Execute cells 2 through 5&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;execute&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;--start&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--end&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;

&lt;span class="c1"&gt;# Delete specific cells&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;cell&lt;span class="w"&gt; &lt;/span&gt;delete&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;

&lt;span class="c1"&gt;# Delete a range of cells&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;cell&lt;span class="w"&gt; &lt;/span&gt;delete&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;--range&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;:3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="environment-aware-execution"&gt;Environment-Aware Execution&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;--uv&lt;/code&gt; and &lt;code&gt;--pixi&lt;/code&gt; flags are supported on &lt;code&gt;nb connect&lt;/code&gt;, &lt;code&gt;nb execute&lt;/code&gt;, and &lt;code&gt;nb create&lt;/code&gt;, telling nb to discover Jupyter servers and kernels through the appropriate environment manager. &lt;code&gt;nb status --python&lt;/code&gt; returns the command prefix needed to run Python in the same environment as the connected kernel — useful when agent-generated shell commands need to match the active notebook environment.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Connect using a uv-managed environment&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;connect&lt;span class="w"&gt; &lt;/span&gt;--uv

&lt;span class="c1"&gt;# Execute in a pixi-managed environment&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;execute&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;--pixi

&lt;span class="c1"&gt;# Get the Python prefix for agent-generated shell commands&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;--python
&lt;span class="c1"&gt;# Returns: &amp;quot;uv run&amp;quot;, &amp;quot;pixi run&amp;quot;, or empty for system Python&lt;/span&gt;

&lt;span class="c1"&gt;# Use in a pipeline&lt;/span&gt;
&lt;span class="k"&gt;$(&lt;/span&gt;nb&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;--python&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;import pandas; print(pandas.__version__)&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="real-world-use-cases"&gt;Real World Use Cases&lt;/h2&gt;
&lt;h3 id="ai-agent-workflows"&gt;AI Agent Workflows&lt;/h3&gt;
&lt;p&gt;AI coding agents can now manipulate notebooks as a part of their analysis workflow.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Surface all failing cells&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;search&lt;span class="w"&gt; &lt;/span&gt;data_analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;--with-errors

&lt;span class="c1"&gt;# Apply the fix&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;cell&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt; &lt;/span&gt;data_analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;--cell-index&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--source&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;df = pd.read_csv(&amp;#39;data.csv&amp;#39;, encoding=&amp;#39;utf-8&amp;#39;)&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Re-execute to verify&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;execute&lt;span class="w"&gt; &lt;/span&gt;data_analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;--cell-index&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="cicd-integration"&gt;CI/CD Integration&lt;/h3&gt;
&lt;p&gt;Automated testing and validation of notebooks in continuous integration pipelines.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Executing notebook...&amp;quot;&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;execute&lt;span class="w"&gt; &lt;/span&gt;pipeline.ipynb&lt;span class="w"&gt; &lt;/span&gt;--allow-errors

&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Checking for errors...&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nb&lt;span class="w"&gt; &lt;/span&gt;search&lt;span class="w"&gt; &lt;/span&gt;pipeline.ipynb&lt;span class="w"&gt; &lt;/span&gt;--with-errors&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Notebook execution failed&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Clearing outputs before commit...&amp;quot;&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;output&lt;span class="w"&gt; &lt;/span&gt;clear&lt;span class="w"&gt; &lt;/span&gt;pipeline.ipynb

&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;✓ All cells executed successfully&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="programmatic-notebook-generation"&gt;Programmatic Notebook Generation&lt;/h3&gt;
&lt;p&gt;Generate documentation, reports, and analysis automatically.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create a report notebook&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;report.ipynb

&lt;span class="c1"&gt;# Add title, introduction, and analysis in one multi-cell command&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;cell&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;report.ipynb&lt;span class="w"&gt; &lt;/span&gt;--source&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt;&amp;#39;EOF&amp;#39;&lt;/span&gt;
&lt;span class="s"&gt;@@markdown&lt;/span&gt;
&lt;span class="s"&gt;# Monthly Sales Report&lt;/span&gt;

&lt;span class="s"&gt;@@markdown&lt;/span&gt;
&lt;span class="s"&gt;Generated on $(date)&lt;/span&gt;

&lt;span class="s"&gt;@@code&lt;/span&gt;
&lt;span class="s"&gt;import pandas as pd&lt;/span&gt;
&lt;span class="s"&gt;df = pd.read_csv(&amp;#39;sales_data.csv&amp;#39;)&lt;/span&gt;
&lt;span class="s"&gt;df.describe()&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Execute to populate outputs&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;execute&lt;span class="w"&gt; &lt;/span&gt;report.ipynb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="debugging-production-notebooks"&gt;Debugging Production Notebooks&lt;/h3&gt;
&lt;p&gt;Quickly inspect and diagnose issues in deployed notebooks.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Find all cells with errors&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;search&lt;span class="w"&gt; &lt;/span&gt;failing_notebook.ipynb&lt;span class="w"&gt; &lt;/span&gt;--with-errors

&lt;span class="c1"&gt;# Search for cells with deprecated API usage&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;search&lt;span class="w"&gt; &lt;/span&gt;analysis.ipynb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;pandas.np&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Find cells with potential security issues&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;search&lt;span class="w"&gt; &lt;/span&gt;notebook.ipynb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;eval(&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Examine specific failing cell with full output&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;failing_notebook.ipynb&lt;span class="w"&gt; &lt;/span&gt;--cell-index&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;

&lt;span class="c1"&gt;# Restart kernel and re-run for clean reproducibility check&lt;/span&gt;
nb&lt;span class="w"&gt; &lt;/span&gt;execute&lt;span class="w"&gt; &lt;/span&gt;failing_notebook.ipynb&lt;span class="w"&gt; &lt;/span&gt;--restart-kernel
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="nb-cli-in-action"&gt;nb-cli in Action&lt;/h2&gt;
&lt;p&gt;To illustrate how AI agents use nb-cli naturally, here are some examples of agent interactions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example 1&lt;/strong&gt;: Claude creating a RL for LLMs notebook&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;User Prompt&lt;/strong&gt;&lt;/em&gt;: Help me learn about reinforcement learning for LLMs by creating a notebook and explaining at each cell how it all works. Cover the key concepts: policy model, reward model, KL divergence penalty, PPO, and GRPO. Use a tiny toy model (small vocab, GRU-based) so everything runs on CPU without any API keys.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2026/nb-cli-a-command-line-interface-for-ai-agents-and/images/001-1_TGHiiVA5yiE4HBdvSAZ6vg.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example 2&lt;/strong&gt;: Codex fixing multiple bugs in a notebook&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;User Prompt&lt;/strong&gt;&lt;/em&gt;: The file churn_analysis.ipynb is a broken research notebook that was last updated in 2023. Fix it so it runs cleanly end-to-end. Identify every cell that fails, fix each issue and verify the notebook executes successfully. After fixing, add a brief markdown note above each cell you changed explaining what was broken and why.&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2026/nb-cli-a-command-line-interface-for-ai-agents-and/images/002-1_VrpFHv9vbJCo-SByBhAwmQ.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;Codex fixed four bugs in churn_analysis.ipynb: a hardcoded file path, DataFrame.append() (removed in pandas 2.0), sklearn.cross_validation (removed in sklearn 0.20), and plot_confusion_matrix (removed in sklearn 1.2) and verified the notebook runs end-to-end after these chages.&lt;/p&gt;
&lt;h2 id="getting-started-with-nb-cli"&gt;Getting Started with nb-cli&lt;/h2&gt;
&lt;h3 id="installation"&gt;Installation&lt;/h3&gt;
&lt;p&gt;Use the install script.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-fsSL&lt;span class="w"&gt; &lt;/span&gt;https://raw.githubusercontent.com/jupyter-ai-contrib/nb-cli/main/install.sh&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If your platform is not supported, and you get an error during install, use cargo to install.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cargo&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;nb-cli
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or build from source.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/jupyter-ai-contrib/nb-cli.git
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nb-cli
cargo&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;--release
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The binary will be available at &lt;code&gt;target/release/nb&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To enable your AI agents to use nb for all notebook operations, install the skill.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;npx&lt;span class="w"&gt; &lt;/span&gt;skills&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;jupyter-ai-contrib/nb-cli
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="about-the-developers"&gt;About the developers&lt;/h2&gt;
&lt;figure&gt;
&lt;img alt="Andrii Ieroshenko is a Software Development Engineer at AWS. He is a long term contributor to project Jupyter working on JupyterLab, Jupyter AI and several other projects. He is also a member of the Jupyter Media Strategy Working Group." src="https://blog.jupyter.org/posts/2026/nb-cli-a-command-line-interface-for-ai-agents-and/images/003-0_i4frZE6ZuXt6owQy.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;&lt;a href="https://github.com/andrii-i/"&gt;Andrii Ieroshenko&lt;/a&gt; is a Software Development Engineer at AWS. He is a long term contributor to project Jupyter working on JupyterLab, Jupyter AI and several other projects. He is also a member of the Jupyter Media Strategy Working Group.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt="Brian Granger is a Senior Principal Technologist at AWS. Brian is a cofounder of Project Jupyter, a board member of the Jupyter and PyTorch Foundations." src="https://blog.jupyter.org/posts/2026/nb-cli-a-command-line-interface-for-ai-agents-and/images/004-0_OkFJLojf_qblg8Uv.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;&lt;a href="https://github.com/ellisonbg"&gt;Brian Granger&lt;/a&gt; is a Senior Principal Technologist at AWS. Brian is a cofounder of Project Jupyter, a board member of the Jupyter and PyTorch Foundations.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt="Piyush Jain is a Principal Engineer at AWS working on Jupyter and Agentic AI. He is a distinguished Jupyter contributor and a member of the Jupyter Server Council." src="https://blog.jupyter.org/posts/2026/nb-cli-a-command-line-interface-for-ai-agents-and/images/005-0__uwMJpQQ-GZTs1Fh.jpeg" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;&lt;a href="https://github.com/3coins"&gt;Piyush Jain&lt;/a&gt; is a Principal Engineer at AWS working on Jupyter and Agentic AI. He is a distinguished Jupyter contributor and a member of the Jupyter Server Council.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="how-can-you-help"&gt;How can you help?&lt;/h2&gt;
&lt;p&gt;We’re just getting started with &lt;strong&gt;nb-cli&lt;/strong&gt;. Please join us!.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Install and use the nb-cli.&lt;/strong&gt; If you find any bugs or have suggestions, please create issues on GitHub.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Join the discussion&lt;/strong&gt; about nb-cli, open issues or add to discussion in &lt;a href="https://github.com/orgs/jupyter-ai-contrib/discussions"&gt;jupyter-ai-contrib&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contribute:&lt;/strong&gt; Your bug reports, feature requests, and pull requests will help improve this project for everyone.&lt;/li&gt;
&lt;/ul&gt;
</content><category term="AI"/><category term="notebook format"/></entry><entry><title>The Jupytext Menu is back!</title><link href="https://blog.jupyter.org/posts/2024/the-jupytext-menu-is-back/" rel="alternate"/><published>2024-01-31T20:29:00+00:00</published><updated>2024-01-31T20:29:00+00:00</updated><author><name>Marc Wouts</name></author><id>tag:blog.jupyter.org,2024-01-31:/posts/2024/the-jupytext-menu-is-back/</id><summary type="html">&lt;p&gt;A few weeks back, jupytext==1.16.0 went out. That release included multiple amazing contributions by Mahendra Paipuri, this post will go over the new features, and thank Mahendra for his great work.&lt;/p&gt;</summary><content type="html">&lt;p&gt;A few weeks back, &lt;code&gt;jupytext==1.16.0&lt;/code&gt; went out. That release included multiple amazing contributions by &lt;a href="https://github.com/mahendrapaipuri"&gt;Mahendra Paipuri&lt;/a&gt;, this post will go over the new features, and thank Mahendra for his great work.&lt;/p&gt;
&lt;p&gt;As we will see below, Mahendra restored the Jupytext Menu, made the extension fully compatible with JupyterLab 4 and Jupyter Notebook 7, and added the option to create Text Notebooks directly from the launcher.&lt;/p&gt;
&lt;h2 id="what-is-jupytext"&gt;What is Jupytext&lt;/h2&gt;
&lt;p&gt;Jupytext is a Python package that lets you save Jupyter Notebooks as text notebooks. Multiple formats are supported, and the notebooks can be saved either as &lt;a href="https://jupytext.readthedocs.io/en/latest/formats-markdown.html"&gt;Markdown documents&lt;/a&gt; with a &lt;code&gt;.md&lt;/code&gt; extension, or as &lt;a href="https://jupytext.readthedocs.io/en/latest/formats-scripts.html"&gt;scripts&lt;/a&gt; with e.g. a &lt;code&gt;.py&lt;/code&gt; extension (assuming you use Python - otherwise multiple &lt;a href="https://jupytext.readthedocs.io/en/latest/languages.html"&gt;languages&lt;/a&gt; are supported).&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;.py&lt;/code&gt; notebook in the percent format looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# %% [markdown]&lt;/span&gt;
&lt;span class="c1"&gt;# This is a markdown cell&lt;/span&gt;

&lt;span class="c1"&gt;# %%&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="c1"&gt;# %%&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To open a text notebook as a notebook in Jupyter, right-click on the document and select “Notebook” (you can also change the default viewer to “Jupytext Notebook” if you wish: see below the section about the settings)&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Right click to open a text notebook with the Notebook editor" src="https://blog.jupyter.org/posts/2024/the-jupytext-menu-is-back/images/001-1_OekPCzL8Obv0NYracG_-uA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Right click to open a text notebook with the Notebook editor&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Text notebooks are conveniently edited and executed in Jupyter as notebooks:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="A Text Notebook in JupyterLab" src="https://blog.jupyter.org/posts/2024/the-jupytext-menu-is-back/images/002-1_J6-ToiGplGQFE0eQTWo0iw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;A Text Notebook in JupyterLab&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;You can also edit them with your favorite text editor. You will get the changes back in Jupyter by re-opening or by &lt;em&gt;reloading&lt;/em&gt; the document: click e.g. on &lt;em&gt;reload Python file from disk&lt;/em&gt; in the &lt;em&gt;File&lt;/em&gt; menu.&lt;/p&gt;
&lt;p&gt;Text notebooks only contain the notebook inputs. For that reason they use much less disk space than &lt;code&gt;.ipynb&lt;/code&gt; notebooks. They are also better suited for version control as they only contain the content that was actually typed by the user. But even more useful are &lt;em&gt;paired&lt;/em&gt; notebooks: text notebooks paired with an &lt;code&gt;.ipynb&lt;/code&gt; notebook where the notebook outputs are preserved. When a paired notebook is saved, Jupytext writes the notebook to both files. When the notebook is read or reloaded in Jupyter, the notebook inputs are loaded from the most recent file, meaning that any edits on the text notebook will be reflected in Jupyter (and propagated to the &lt;code&gt;.ipynb&lt;/code&gt; file the next time it is saved).&lt;/p&gt;
&lt;h2 id="the-jupytext-menu-is-back"&gt;The Jupytext Menu is back!&lt;/h2&gt;
&lt;p&gt;One of the most frequent operation when using Jupytext is to &lt;em&gt;pair&lt;/em&gt; an &lt;code&gt;.ipynb&lt;/code&gt; notebook with a text notebook in the format of your choice.&lt;/p&gt;
&lt;p&gt;In earlier versions of Jupytext, pairing a notebook had to be done through the command palette.&lt;/p&gt;
&lt;p&gt;In Jupytext v1.16, thanks to Mahendra’s work on the front-end extension, you can directly use the Jupytext Menu for this. And the menu is available for both JupyterLab 4 and Jupyter Notebook 7!&lt;/p&gt;
&lt;p&gt;If you are new to Jupytext, we recommend that you pair your &lt;code&gt;.ipynb&lt;/code&gt; notebooks with either a &lt;em&gt;percent script&lt;/em&gt; or with a MyST Markdown file. The former format works well if you want to save and edit your notebook as a script, while the latter is well suited for writing documentation.&lt;/p&gt;
&lt;p&gt;Once you have paired the notebook with a Jupytext format, save your notebook, and the paired files will be created or updated.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="The Jupytext Menu" src="https://blog.jupyter.org/posts/2024/the-jupytext-menu-is-back/images/003-1_luqGwTI9TbORaPjfsquNIQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;The Jupytext Menu&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="creating-new-text-notebooks"&gt;Creating new text notebooks&lt;/h2&gt;
&lt;p&gt;For some notebooks you might decide that you don’t need to save the outputs at all. In that case you can work with an (unpaired) text notebook.&lt;/p&gt;
&lt;p&gt;To create a text notebook you can use the &lt;em&gt;New Text Notebook&lt;/em&gt; sub-menu under &lt;em&gt;File&lt;/em&gt;:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Create Text Notebooks directly from the menu" src="https://blog.jupyter.org/posts/2024/the-jupytext-menu-is-back/images/004-1_YLrRNo1MKSPrUSo1wwjsBQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Create Text Notebooks directly from the menu&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If, later on, you decide that you want to preserve the notebook outputs in a &lt;em&gt;paired&lt;/em&gt; &lt;code&gt;.ipynb&lt;/code&gt; notebooks, you will just have to &lt;em&gt;pair&lt;/em&gt; the document to an &lt;code&gt;.ipynb&lt;/code&gt; notebook using the Jupytext Menu documented at the previous paragraph.&lt;/p&gt;
&lt;h2 id="text-notebooks-in-the-launcher"&gt;Text Notebooks in the launcher&lt;/h2&gt;
&lt;p&gt;In Jupytext v1.16, thanks to Mahendra’s work on the front end, text notebooks are also available in a Jupytext section in the Jupyter launcher:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Text Notebooks are also available in the launcher" src="https://blog.jupyter.org/posts/2024/the-jupytext-menu-is-back/images/005-1_P_0hx6p-nfwkDpqHGJmewg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Text Notebooks are also available in the launcher&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="jupytext-settings-in-jupyterlab"&gt;Jupytext Settings in JupyterLab&lt;/h2&gt;
&lt;p&gt;Since there are many possible formats for text notebooks, we have decided to expose only the most common ones, by default, in the launcher and in the &lt;em&gt;New Text Notebook&lt;/em&gt; menu.&lt;/p&gt;
&lt;p&gt;You can include more formats in the menu and in the launcher by changing the Jupytext settings in the &lt;em&gt;Settings Editor&lt;/em&gt; (in the &lt;em&gt;Settings&lt;/em&gt; menu):&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="The Jupytext Menu/Launcher settings" src="https://blog.jupyter.org/posts/2024/the-jupytext-menu-is-back/images/006-1_n31jAKcy96wrrTYR3qCuXA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;The Jupytext Menu/Launcher settings&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;You might also want to open certain text documents as notebooks with a single click. You can achieve this by setting the default viewer for those documents to “Jupytext Notebook”. For instance, if you want to open &lt;code&gt;.py&lt;/code&gt; and &lt;code&gt;.md&lt;/code&gt; files as notebooks with a single click, you can configure the default viewers like this:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Changing the default viewers to open Text Notebooks with a single click" src="https://blog.jupyter.org/posts/2024/the-jupytext-menu-is-back/images/007-1_1oj4Jrdz8hOhG90rYZckEA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Changing the default viewers to open Text Notebooks with a single click&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Alternatively, you can also list and set the default viewers with the &lt;code&gt;jupytext-config&lt;/code&gt; utility (which was developed recently by Thierry Parmentelat), using e.g.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;jupytext-config&lt;span class="w"&gt; &lt;/span&gt;set-default-viewer&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;markdown
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;to set “Jupytext Notebook” as the default viewer for both Python and Markdown files.&lt;/p&gt;
&lt;h2 id="pairing-notebooks-globally"&gt;Pairing notebooks globally&lt;/h2&gt;
&lt;p&gt;The pairing commands provided through the Jupytext Menu and commands act on individual notebooks, by settings a &lt;code&gt;jupytext.formats&lt;/code&gt; metadata in the notebook.&lt;/p&gt;
&lt;p&gt;It is also possible to pair all the notebooks within a certain folder using a &lt;code&gt;jupytext.toml&lt;/code&gt; configuration file - see &lt;a href="https://jupytext.readthedocs.io/en/latest/config.html"&gt;Jupytext’s documentation&lt;/a&gt;. Please note that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The local metadata in the notebook takes precedence over the global configuration, and&lt;/li&gt;
&lt;li&gt;Jupyter is aware of the global &lt;code&gt;jupytext.toml&lt;/code&gt; file and will pair the notebooks accordingly, however the Jupytext Menu is not aware of the global configuration, so the paired formats selected through the global configuration will not be checked in the menu.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="installing-jupytext-v116"&gt;Installing Jupytext v1.16&lt;/h2&gt;
&lt;p&gt;The front end extension for JupyterLab shipped with Jupytext v1.16 requires JupyterLab 4, and/or Jupyter Notebook 7. Please upgrade Jupyter accordingly. Then, install Jupytext with&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;jupytext&amp;gt;=1.16.0&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;conda&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;jupytext&amp;gt;=1.16.0&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;conda-forge
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;and restart your Jupyter server with e.g. &lt;code&gt;jupyter lab&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="acknowledgments"&gt;Acknowledgments&lt;/h2&gt;
&lt;p&gt;I would like to thank &lt;a href="https://github.com/mahendrapaipuri"&gt;Mahendra Paipuri&lt;/a&gt; for his impressive work. I am pretty sure that the Jupytext Menu, and the option to create new text notebooks, will be much appreciated by Jupytext users!&lt;/p&gt;
&lt;p&gt;While users will mostly notice Mahendra’s work on the front end extension, it was actually not his only contribution to this release! Mahendra also thoroughly revisited the packaging of Jupytext, and helped us transition the project to the &lt;code&gt;src&lt;/code&gt; layout, and our old &lt;code&gt;setup.py&lt;/code&gt; to an up-to-date &lt;code&gt;pyproject.toml&lt;/code&gt; configuration that uses &lt;code&gt;hatch&lt;/code&gt; to build Jupytext.&lt;/p&gt;
&lt;p&gt;I also want to thank &lt;a href="https://github.com/LecrisUT"&gt;Cristian Le&lt;/a&gt; for his precious advice regarding the layout refactoring, and for helping us to tackle the CI reorganization. &lt;a href="https://github.com/parmentelat"&gt;Thierry Parmentelat&lt;/a&gt;, who had previously ported the front-end extension to JupyterLab 4, contributed much testing and feedback on this new version of the front-end extension.&lt;/p&gt;
&lt;p&gt;It is always a pleasure for me to maintain Jupytext, an amazing adventure that started five years ago already. However, I can get busy at times (Jupytext comes in addition to my day job, one cat, two bikes, three kids), so I am really thankful for receiving such contributions, especially when they are of such a great quality!&lt;/p&gt;
</content><category term="extensions"/><category term="JupyterLab"/><category term="notebook format"/></entry><entry><title>Jupyter Notebook format workshop outcomes</title><link href="https://blog.jupyter.org/posts/2023/jupyter-notebook-format-workshop-outcomes/" rel="alternate"/><published>2023-04-12T17:40:00+00:00</published><updated>2023-04-12T17:40:00+00:00</updated><author><name>Frédéric Collonval</name></author><id>tag:blog.jupyter.org,2023-04-12:/posts/2023/jupyter-notebook-format-workshop-outcomes/</id><summary type="html">&lt;p&gt;The Jupyter Community Workshop on the notebook file format took place at the Safran Campus near Paris from February 28th to March 2nd. It was a great opportunity to gather various Jupyter stakeholders from private and public affiliations to bootstrap new features for the Notebook file format.&lt;/p&gt;</summary><content type="html">&lt;figure&gt;
&lt;img alt="Workshop Social Event challenging our senses" src="https://blog.jupyter.org/posts/2023/jupyter-notebook-format-workshop-outcomes/images/001-1_OfVNHv8zp7Tn2I0hRIkN4Q.jpeg" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Workshop Social Event challenging our senses&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The Jupyter Community Workshop on the notebook file format took place at the Safran Campus near Paris from February 28th to March 2nd. It was a great opportunity to gather various Jupyter stakeholders from private and public affiliations to bootstrap new features for the &lt;a href="https://nbformat.readthedocs.io/en/latest"&gt;Notebook file format&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://blog.jupyter.org/posts/2022/jupyter-community-workshops/"&gt;Jupyter Community Workshops&lt;/a&gt; are a series of events designed to bring together small groups of Jupyter community members and core contributors for high-impact strategic work and community engagement on focused topics.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="community-discussions"&gt;Community discussions&lt;/h2&gt;
&lt;p&gt;The community has lots of &lt;a href="https://docs.google.com/document/d/1CZZ_EpMIeh3zDlqYEvUH4WLvjKrKGmDU6Afcz1NvMkg"&gt;ideas to improve the Notebook format&lt;/a&gt;. So we split in three smaller groups with the aim of drafting Jupyter Enhancement Proposal (JEP): the markdown group, the text-format group and the cell types group.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;markdown&lt;/strong&gt; group focused on backward compatible enhancement for the Markdown cells. The discussions focused on two subjects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The specification of the Markdown flavor (&lt;a href="https://github.com/jupyter/enhancement-proposals/issues/98"&gt;pre-proposal&lt;/a&gt;): the goals are to specify which Markdown flavor (e.g. GitHub, CommonMark, MyST,…) is used for the cell source, how to store rendered output for easier cross-compatibility and what is the default Markdown flavor.&lt;/li&gt;
&lt;li&gt;The persistence of user expression (&lt;a href="https://github.com/jupyter/enhancement-proposals/issues/94"&gt;pre-proposal&lt;/a&gt;): in order to display inline expressions within Markdown cells, the results obtained from the kernel should be stored in the notebook. This proposal aims to define the schema modification to store such information.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;strong&gt;text-format&lt;/strong&gt; group lays out a specification for an official Jupyter notebook textual format. The discussion went on after the meeting to prepare that &lt;a href="https://github.com/jupyter/enhancement-proposals/issues/102"&gt;proposal&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally the &lt;strong&gt;cell-types&lt;/strong&gt; group took the hypothesis of starting from the blank page to create the best Jupyter notebook format building on top of 10-years of experience. The discussion will take time to settle down on a new specification. So if you are interested, join the weekly discussion (see &lt;a href="https://hackmd.io/hHW8k7mKS5qFBhxnFtVTCw"&gt;the meeting notes&lt;/a&gt; for all the details). In addition to the fully new specification, two backward compatible enhancements have been proposed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adding &lt;em&gt;$schema&lt;/em&gt; to the notebook format and deprecate the nbformat version keys (see &lt;a href="https://github.com/jupyter/enhancement-proposals/pull/97"&gt;proposal&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Adding &lt;em&gt;extraSchema&lt;/em&gt; to the notebook format to optionally extend the schema to specify in particular metadata (see &lt;a href="https://github.com/jupyter/enhancement-proposals/issues/96"&gt;pre-proposal&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="follow-up"&gt;Follow-up&lt;/h2&gt;
&lt;p&gt;Six JEP’s are foreseen from the workshop discussions. But as mentioned earlier, the community has lots of great other ideas (like SQL cells, low-/no-code cells for inputs or visualization). So we would like to encourage anyone interested by any Jupyter enhancement to open issue on the &lt;a href="https://github.com/jupyter/enhancement-proposals"&gt;Jupyter Enhancement Proposals&lt;/a&gt; repository (see the &lt;a href="https://jupyter.org/enhancement-proposals/jupyter-enhancement-proposal-guidelines/jupyter-enhancement-proposal-guidelines.html"&gt;guidelines&lt;/a&gt; for more information).&lt;/p&gt;
&lt;p&gt;With the new &lt;a href="https://blog.jupyter.org/posts/2023/announcing-a-new-jupyter-governance-model-and-our-first/"&gt;Jupyter governance&lt;/a&gt; in place, the new Software Steering Council is responsible for ensuring those proposals get reviewed and go through the approval process.&lt;/p&gt;
&lt;h2 id="acknowledgements"&gt;Acknowledgements&lt;/h2&gt;
&lt;p&gt;I deeply want to thank all participants to the workshop that took the time (some of them despite time zone difference) to bring very constructive and thoughtful discussion.&lt;/p&gt;
&lt;p&gt;We are really grateful to Bloomberg and Amazon Web Services for their donations to the Jupyter Community Workshops program. This event would not have been possible without their generous support.&lt;/p&gt;
&lt;p&gt;We are also grateful to Safran Group for hosting this workshop and the NumFOCUS foundation for helping and mentoring this workshop organization.&lt;/p&gt;
</content><category term="community"/><category term="events"/><category term="notebook format"/><category term="workshops"/></entry><entry><title>Jupyter Community Workshop: The notebook file format</title><link href="https://blog.jupyter.org/posts/2022/jupyter-community-workshop-the-notebook-file-format/" rel="alternate"/><published>2022-12-08T16:09:00+00:00</published><updated>2022-12-08T16:09:00+00:00</updated><author><name>Frédéric Collonval</name></author><id>tag:blog.jupyter.org,2022-12-08:/posts/2022/jupyter-community-workshop-the-notebook-file-format/</id><summary type="html">&lt;p&gt;We are excited to announce the next in-person Jupyter Community Workshop! It will focus on the Notebook file format.&lt;/p&gt;</summary><content type="html">&lt;p&gt;We are excited to announce the next in-person Jupyter Community Workshop! It will focus on the &lt;a href="https://nbformat.readthedocs.io/en/latest"&gt;Notebook file format&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://blog.jupyter.org/posts/2022/jupyter-community-workshops/"&gt;Jupyter Community Workshops&lt;/a&gt; are a series of events designed to bring together small groups of Jupyter community members and core contributors for high-impact strategic work and community engagement on focused topics.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Jupyter notebook file format has been around for 10 years. Its usage has grown in countless fields from teaching to data analysis in production pipelines. A great number of software applications and online services have added support for it.&lt;/p&gt;
&lt;p&gt;We want this workshop to be an opportunity for various stakeholders to push forward the format while preserving its reusability in as many applications as possible. We could for example prototype a syntax for injecting variable values in Markdown cells, specify an alternative more textual format like RMarkdown, define the Markdown variant we support,… the boundary is our imagination. By the end of the workshop, we will submit those new specifications as &lt;a href="https://jupyter.org/enhancement-proposals/README.html"&gt;Jupyter Enhancement Proposals&lt;/a&gt; to kick start the validation process for enhancing the official notebook format.&lt;/p&gt;
&lt;p&gt;The workshop will last three days, with hands-on discussions, hacking sessions, and technical presentations. The goal of this event is to foster collaboration and the sharing of knowledge between maintainers of various platforms supporting the file format, downstream library authors and power users.&lt;/p&gt;
&lt;p&gt;The workshop will be held at the Safran Campus in &lt;a href="https://www.safran-group.com/locations/france/safran-campus-2117707"&gt;Paris suburb, France&lt;/a&gt; from February 28th to March 2nd, 2023. Travel funding assistance is available for attendees from academia and those from groups which are not well-represented within the Jupyter and wider tech community!&lt;/p&gt;
&lt;p&gt;Application and all other details can be found in this &lt;a href="https://docs.google.com/forms/d/e/1FAIpQLSfBQlor-UNtpGvyafefE9xEtBwd47q5ev5ju8wTYpP1Z9YRCA/viewform?usp=pp_url&amp;amp;entry.828738498=Tuesday,+February+28th&amp;amp;entry.828738498=Wednesday,+March+1st&amp;amp;entry.828738498=Thursday,+March+2nd&amp;amp;entry.541109147=No&amp;amp;entry.148929239=No"&gt;form&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;We are grateful to Safran Group for sponsoring this event. We are also grateful to the sponsors of the Jupyter Community Workshop series, Bloomberg and Amazon Web Services.&lt;/em&gt;&lt;/p&gt;
</content><category term="events"/><category term="notebook format"/><category term="workshops"/></entry><entry><title>Ploomber: Maintainable and Collaborative Pipelines in Jupyter</title><link href="https://blog.jupyter.org/posts/2021/ploomber-maintainable-and-collaborative-pipelines-in/" rel="alternate"/><published>2021-09-01T14:17:00+00:00</published><updated>2021-11-10T16:54:00+00:00</updated><author><name>Eduardo Blancas</name></author><id>tag:blog.jupyter.org,2021-09-01:/posts/2021/ploomber-maintainable-and-collaborative-pipelines-in/</id><summary type="html">&lt;p&gt;Ploomber is an open-source framework that allows teams to develop maintainable pipelines in Jupyter.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Ploomber is an open-source framework that allows teams to develop maintainable pipelines in Jupyter.&lt;/em&gt;&lt;/p&gt;
&lt;p class="standalone-image"&gt;&lt;img src="https://blog.jupyter.org/posts/2021/ploomber-maintainable-and-collaborative-pipelines-in/images/001-1_JJ-jCKjGCs71jmW_jUUo9Q.webp" alt="" loading="lazy" data-body-image=""&gt;&lt;/p&gt;
&lt;p&gt;Jupyter is a fantastic tool for data exploration. The ability to transform our data interactively and get immediate visual feedback allows us to understand it quickly. However, when working on large projects, collaboration can become difficult. Features such as live collaboration are a gigantic leap forward for teamwork. Still, it has to complement an asynchronous workflow that allows team members to work at different times, especially in a remote-first workplace.&lt;/p&gt;
&lt;p&gt;Back in 2020, I introduced &lt;a href="https://github.com/ploomber/ploomber"&gt;Ploomber&lt;/a&gt; at &lt;a href="https://www.youtube.com/watch?v=M6mtgPfsA3M"&gt;JupyterCon&lt;/a&gt; to help practitioners build maintainable and reproducible data workflows. Fortunately, the community is growing. We’ve received great feedback from teams that use Ploomber to develop production-ready pipelines using Jupyter, debunking the notion that notebooks are only for prototyping.&lt;/p&gt;
&lt;p&gt;As we’ve gathered more feedback from our community, we realized that while users had improved the reproducibility of their workflows, team dynamics didn’t change much. In many cases, collaborators worked in isolation, sharing processed data, which severely hindered reproducibility.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Teams often share processed data, which hinders reproducibility." src="https://blog.jupyter.org/posts/2021/ploomber-maintainable-and-collaborative-pipelines-in/images/002-1_IyPsYguxs-QiN5ECuRA3SQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Teams often share processed data, which hinders reproducibility.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Since its first release, Ploomber aimed to promote software development best practices to produce more maintainable data projects. We’re now doubling our efforts to enable a collaborative and asynchronous workflow.&lt;/p&gt;
&lt;h2 id="enabling-code-reviews"&gt;Enabling Code Reviews&lt;/h2&gt;
&lt;p&gt;We frequently hear from teams using Jupyter notebooks that it’s challenging to manage &lt;code&gt;.ipynb&lt;/code&gt; files. For example, we heard from a data scientist that his company considered banning Jupyter notebooks because they couldn’t figure out how to follow software engineering best practices. Our fellow data scientist was highly frustrated by this situation since Jupyter turbocharges his ability to explore and understand data.&lt;/p&gt;
&lt;p&gt;Managing &lt;code&gt;.ipynb&lt;/code&gt; files is challenging for multiple reasons. Suppose I push a notebook to a git repository. Then, I add a comment and push it to the repository again. The difference between the two versions looks like this:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Diff view from a notebook with a new cell." src="https://blog.jupyter.org/posts/2021/ploomber-maintainable-and-collaborative-pipelines-in/images/003-1_0b5P217N-mBvpKiQpltdXA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Diff view from a notebook with a new cell.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;.ipynb&lt;/code&gt; files contain input and outputs in a single file, making them extremely useful when we want to share code and results with a colleague but complicates code reviews where we want to compare the previous version with the current one. However, we can fix this problem by changing the underlying file format.&lt;/p&gt;
&lt;p&gt;Many practitioners don’t know that Jupyter is agnostic to the underlying file representation, allowing us to interact with different file formats as notebooks. &lt;a href="https://github.com/mwouts/jupytext"&gt;Jupytext&lt;/a&gt; is a fantastic project that enables us to open various file formats such as &lt;code&gt;.py&lt;/code&gt; and &lt;code&gt;.md&lt;/code&gt; as notebooks.&lt;/p&gt;
&lt;p&gt;Ploomber integrates with jupytext, allowing users to store their source code as &lt;code&gt;.py&lt;/code&gt; files and explore data interactively with Jupyter. Since source code exists in &lt;code&gt;.py&lt;/code&gt; files, this enables code reviews, file merging, and the flexibility to edit the code either in Jupyter or in any text editor, giving anyone on the team the freedom to use whatever tool they like the most.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="The same .py file is displayed as a notebook in JupyterLab and as a script in VS Code." src="https://blog.jupyter.org/posts/2021/ploomber-maintainable-and-collaborative-pipelines-in/images/004-1_2nJuHJ5mRSttNnPJlW2sxQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;The same .py file is displayed as a notebook in JupyterLab and as a script in VS Code.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="enabling-modularization"&gt;Enabling Modularization&lt;/h2&gt;
&lt;p&gt;Often, teams develop significant parts of a project in a single notebook for convenience; however, this makes it hard to maintain the project in the long run. We know from decades of advancement in software engineering practice that modularizing our code produces more maintainable projects. Yet, we tend to work on single notebooks because breaking down analysis in multiple parts involves managing project structure, ensuring we route outputs correctly, and writing code to orchestrate all steps.&lt;/p&gt;
&lt;p&gt;Ploomber allows users to concatenate multiple notebooks into a pipeline in two steps: list the notebooks in a YAML file and declare execution dependencies (e.g., download data, then clean it). Furthermore, Ploomber parses our execution dependencies and injects inputs into our notebook when opening it. Thus, there’s no need to hard-code any paths. The following image illustrates how to declare a pipeline in a &lt;code&gt;pipeline.yaml&lt;/code&gt; file and the notebook’s code injection process:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Ploomber integrates with JupyterLab to auto-complete outputs from task dependencies." src="https://blog.jupyter.org/posts/2021/ploomber-maintainable-and-collaborative-pipelines-in/images/005-1_gL2-ulFM2As0IQZy93TqDg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Ploomber integrates with JupyterLab to auto-complete outputs from task dependencies.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Modularization has another benefit. It allows teams to collaborate on separate, well-defined streams of work. Data projects have a sequential nature, and by explicitly structuring them into small tasks, it is easy to assign parts to various team collaborators. For example, if a two-person team is working on a project that uses two sources of data, each member can take one dataset:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Modularization allows teams to work efficiently." src="https://blog.jupyter.org/posts/2021/ploomber-maintainable-and-collaborative-pipelines-in/images/006-1_D_2-AzuaTKEXFwH1chCYLQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Modularization allows teams to work efficiently.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="enabling-testing"&gt;Enabling Testing&lt;/h2&gt;
&lt;p&gt;Modularization facilitates testing. A recommended practice when developing data pipelines is to test the output data from each task to ensure it has some minimum quality. Since we have clear boundaries among tasks, we can embed integration tests that check the output coming out of each step, ensuring that we know when something breaks.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Ploomber allows testing output artifacts from each task to check for data quality." src="https://blog.jupyter.org/posts/2021/ploomber-maintainable-and-collaborative-pipelines-in/images/007-1_slWI7s5zWClIrEV9nh3mBA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Ploomber allows testing output artifacts from each task to check for data quality.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Examples of integration tests include: checking there are no &lt;code&gt;NULL&lt;/code&gt; values in a specific column or verifying values fall into a certain range. In Ploomber, you can execute an arbitrary function after your notebook finishes to assert statements on your data:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Example of an integration test that ensures that a particular column does not have NAs." src="https://blog.jupyter.org/posts/2021/ploomber-maintainable-and-collaborative-pipelines-in/images/008-1_SoRQVeyGsXK3Zete9L04Ug.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Example of an integration test that ensures that a particular column does not have NAs.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="ensuring-reproducibility"&gt;Ensuring Reproducibility&lt;/h2&gt;
&lt;figure&gt;
&lt;img alt="Users can adopt a continuous integration workflow to test for reproducibility." src="https://blog.jupyter.org/posts/2021/ploomber-maintainable-and-collaborative-pipelines-in/images/009-1_pGlDQ8ykrqnTUsjeY67oVw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Users can adopt a continuous integration workflow to test for reproducibility.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;One of Jupyter notebook’s most recurring problems is &lt;em&gt;hidden state&lt;/em&gt;; this happens when we execute code cells in an arbitrary order. However, when running cells in sequential order, the results do not match the recorded output.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Hidden state&lt;/em&gt; creates significant issues. For example, teams often execute notebooks locally, store them in a git repository, and don’t execute them again. For instance, I heard from a fellow data scientist that her team struggled to update a model in production because of a broken notebook that prevented her team from re-training the model: the notebook had been executed locally once but never tested for reproducibility.&lt;/p&gt;
&lt;p&gt;The answer to &lt;em&gt;hidden state&lt;/em&gt; is straightforward: test continuously. In software engineering, it’s common to run code and test it on each &lt;code&gt;git push&lt;/code&gt;. However, such practice hasn’t found its way into the data world, primarily because running data processing code may take hours, making testing unfeasible.&lt;/p&gt;
&lt;p&gt;Fortunately, most errors are detectable with small amounts of data, and Ploomber simplifies managing pipeline configurations. For example, a user can define a &lt;code&gt;sample&lt;/code&gt; parameter to run the pipeline with a fraction of the input data:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="An example parametrized pipeline that can execute with a sample of the data." src="https://blog.jupyter.org/posts/2021/ploomber-maintainable-and-collaborative-pipelines-in/images/010-1_PRrGwgtnO4YTLZfUShegLw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;An example parametrized pipeline that can execute with a sample of the data.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Then, a continuous integration script can run the pipeline with a sample by switching the &lt;code&gt;sample&lt;/code&gt; parameter:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ploomber&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;--sample&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Furthermore, users can easily download remote artifacts to debug broken pipelines.&lt;/p&gt;
&lt;h2 id="enabling-pull-requests"&gt;Enabling Pull Requests&lt;/h2&gt;
&lt;figure&gt;
&lt;img alt="Users can submit Pull Requests containing code (.py files) and results (executed notebooks)." src="https://blog.jupyter.org/posts/2021/ploomber-maintainable-and-collaborative-pipelines-in/images/011-1_Y4Jy2E7cqM7tPWvpPU3l4g.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Users can submit Pull Requests containing code (.py files) and results (executed notebooks).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Once a new feature is ready, a team member can open a pull request. Since the code is in &lt;code&gt;.py&lt;/code&gt; files, the reviewer can easily compare code versions. However, metrics or charts are essential to evaluate data processing code. For this reason, Ploomber generates a &lt;code&gt;.ipynb&lt;/code&gt; file for each &lt;code&gt;.py&lt;/code&gt; file; such executed notebooks can be attached to a pull request to review code and output.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Pipeline execution generates executed notebook files that can be attached to a Pull Request." src="https://blog.jupyter.org/posts/2021/ploomber-maintainable-and-collaborative-pipelines-in/images/012-1_tVXeHFlmpPJDMewgRRCNYQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Pipeline execution generates executed notebook files that can be attached to a Pull Request.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;A CI process can orchestrate pipeline execution with a single command: &lt;code&gt;ploomber build&lt;/code&gt;. However, if working with large datasets, Ploomber can export pipelines to execute in AWS Batch, Airflow, or Kubernetes (Argo Workflows).&lt;/p&gt;
&lt;h2 id="enabling-fast-iterations"&gt;Enabling Fast Iterations&lt;/h2&gt;
&lt;figure&gt;
&lt;img alt="Ploomber skips execution of tasks whose source code has not changed since the last run." src="https://blog.jupyter.org/posts/2021/ploomber-maintainable-and-collaborative-pipelines-in/images/013-1_z1jqY7VI33dE2R2YO_SeBw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Ploomber skips execution of tasks whose source code has not changed since the last run.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Data projects are highly iterative and require us to run small experiments to evaluate our final results. For example, we may add a new feature and assess whether that improves model performance. The more experiments we try, the higher our chance of success.&lt;/p&gt;
&lt;p&gt;Such experiments are small and often only touch a small portion of the pipeline. If most of our tasks are unaffected, re-running tasks is a waste of time since they’ll generate the same results. Given that a training pipeline may take hours to run, it is essential to speed things up as much as possible. To enable faster iterations, Ploomber builds pipelines incrementally, skipping tasks whose source code hasn’t changed. Incremental builds help in multiple scenarios, for example, when trying out local experiments or running the pipeline in the CI system. Furthermore, they enable crash recovery: if we execute our pipeline and it crashes, we can fix the failing task, submit it again, and execution will take off from the point of failure.&lt;/p&gt;
&lt;h2 id="jupyterlab-is-a-production-ready-platform"&gt;JupyterLab Is a Production-Ready Platform&lt;/h2&gt;
&lt;p&gt;It is common for teams to develop prototypes in Jupyter, then refactor them into Python modules; such an approach creates a lot of overhead and a tremendous burden for data scientists (who produce the models) and engineers (who have to refactor notebook-based prototypes). Instead, we believe data projects should start with production in mind by following best software development practices that allow teams to iterate quickly.&lt;/p&gt;
&lt;p&gt;Providing such an experience is challenging. Nevertheless, we are working hard to achieve that goal; we want data scientists and engineers to collaborate to produce production-ready projects that instantly go from Jupyter to production.&lt;/p&gt;
&lt;h2 id="the-future"&gt;The Future&lt;/h2&gt;
&lt;p&gt;There is still a long way to capture our vision of the future of data workflows. Furthermore, we want to keep building the project with the help of our users. So if you’re interested in building Ploomber with us, join our &lt;a href="http://community.ploomber.io"&gt;community&lt;/a&gt;, or follow us on &lt;a href="https://twitter.com/ploomber"&gt;Twitter&lt;/a&gt;! And if you believe in our mission, please show your support with a star on &lt;a href="https://github.com/ploomber/ploomber"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Thanks to Ido Michael and Filip Jankovic for providing feedback.&lt;/em&gt;&lt;/p&gt;
</content><category term="machine learning"/><category term="notebook format"/><category term="reproducibility"/></entry></feed>