IPyflow: A Reactive Python Kernel That Tracks Notebook Dataflow
A reactive Python kernel for Jupyter notebooks.
At a glance
- What is it?
- IPyflow is a drop-in replacement for ipykernel that infers cell dependencies at runtime and can re-execute out-of-sync cells. It is opt-in reactivity, with a real cost in autosave behavior and dependency inference limits.
- Who is it for?
- Adopt IPyflow if you work in JupyterLab or Notebook 7 and repeatedly hit stale state when running cells out of order. Do not adopt it if you need a kernel for classic Notebook, JupyterLite, or non-Jupyter frontends, or if autosave-on-change conflicts with your workflow.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 6 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 16, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The stale-state problem IPyflow targets
Jupyter's default kernel, ipykernel, executes cells in whatever order you click them. Nothing tracks which cells read a variable that a later cell rebinds. The result is the familiar failure mode: you edit cell 3, run it, then look at cell 7 and cannot tell whether its output reflects the new value or an older one. Restart and run all is the standard workaround, and it discards everything you built interactively.
IPyflow's stated goal is to enforce a specific invariant: whenever you execute a cell, the resulting output appears as it would if you had performed a restart plus run-all. It is aimed at Jupyter users who want that guarantee without giving up the notebook interface. The README positions it as a drop-in replacement for ipykernel, meaning it intends to be a strict superset of the default kernel's behavior rather than a new notebook format.
That framing matters. Tools like Observable, Pluto.jl, and Marimo bake reactivity into the notebook model itself. IPyflow instead bolts reactivity onto the existing kernel protocol, which is why it can be selected from the kernel list in a notebook you already have.
How dependency inference actually works
IPyflow tracks dataflow relationships between symbols and cells during an interactive session. According to the README, it infers dependencies beyond simple variable names. The example given is a subscript reference: if cell B reads x[0], IPyflow understands that B depends on cell A because of that specific element, and it will not reactively execute B when x[1] changes. That is the precision claim, and it is the part worth testing against your own code.
The inference is runtime-based rather than purely static. The README states that IPyflow peeks at runtime state in order to infer dependencies. That is why it needs to keep the notebook content in sync with the kernel's memory state, even across browser refreshes. The dependency information is persisted to notebook metadata, so you can start a fresh kernel session, jump to any cell, run it, and get output consistent with what the notebook author intended.
Two execution modes exist. By default, IPyflow runs only the cell you execute, same as ipykernel. Ctrl+shift+enter (cmd+shift+enter on Mac also works) reactively executes a single cell along with its dependents without changing the default mode. The magic command %flow mode reactive makes reactive execution the default. Reactivity is opt-in, and the README is explicit about that.
Separately, the JupyterLab extension color-codes dependencies regardless of whether reactivity is on. Selecting a cell marks cells that would re-execute with an orange dot, and up-to-date upstream dependencies with purple dots. This is a visualization layer, not execution behavior, and it works even if you never enable reactive mode.
Installing and switching kernels
Installation is a single command:
pip install ipyflow
After that, the kernel appears in the launcher as Python 3 (ipyflow). You select it from the Launcher tab, or switch an existing notebook via the Change kernel menu item. There is no separate server process to configure and no project file to create.
Once the kernel is running, reactivity is controlled by the magic command %flow mode reactive. The README also documents ctrl+shift+enter and cmd+shift+enter as one-off reactive executions that do not change the default mode, which is the lower-commitment way to try the behavior before switching defaults.
One library utility is documented for recovering prior executions: reproduce_cell. The README gives this example:
from ipyflow import reproduce_cell reproduce_cell(4, lookback=1)
That call reproduces the previous execution of cell 4 within a given kernel session. It exists because reactive execution can overwrite a cell's output, and autosave can write that overwrite to disk. The recovery path is session-scoped, so it is not a substitute for version control.
Autosave-on-change is a real trade-off
IPyflow enables autosave-on-change by default. The README's justification is specific: because the kernel peeks at runtime state to infer dependencies, it needs the notebook UI's in-memory state and the file on disk to stay in sync with kernel memory, including across browser refreshes.
This is a design consequence, not an incidental setting. If you rely on manual saves to stage changes, or if you edit a notebook in another editor while the kernel is live, autosave-on-change can write intermediate states to disk that you did not intend to commit. The reproduce_cell utility recovers prior executions within a session, but it does not undo a disk write once it has happened.
The README acknowledges the risk directly, noting that autosave may overwrite a previous result on disk during a reactive execution. That is an honest disclosure, and it is also the clearest signal that this tool assumes a workflow where the notebook file is the live artifact, not a staging area.
If your team treats .ipynb files as build outputs, or if you keep notebooks under review before saving, this default deserves attention before you install.
Where the precision claim can break down
The subscript example in the README is the strongest case for IPyflow's inference: x[0] and x[1] are distinguished, so only the dependent cell re-executes. That precision depends on the dependency being observable at runtime.
The README does not document the failure modes. It does not describe what happens when a dependency is created through aliasing, when a cell mutates an object in place rather than rebinding a name, or when a function reads a global that was never passed as an argument. Those are exactly the patterns where runtime tracing and static analysis diverge, and the README's topics list both dataflow and static-code-analysis without explaining how the two are combined.
The practical implication is that the invariant (output as if you had run the notebook top to bottom) holds only as far as the inferred dependency graph is correct. A missed edge means a cell that should have re-executed does not, and the stale output problem returns in a form that is harder to notice because the tool is supposed to prevent it. There is no documented way to inspect the inferred graph and correct it.
IPyflow is also scoped to JupyterLab and Notebook 7 per the README. Classic Notebook, JupyterLite, and other frontends are not listed as supported, and the reactive execution model is delivered through extensions for those two frontends. If your environment is anything else, the kernel may install but the reactivity and the dependency dots will not appear.
IPyflow versus Marimo and Pluto.jl
The README names Observable, Pluto.jl, and Marimo as comparable reactive notebooks, and states that IPyflow's reactivity behaves differently because it was designed for Jupyter users specifically.
The difference is architectural. Marimo and Pluto.jl are notebook environments: the reactive dependency graph is part of the notebook format, and the file you save encodes the structure. IPyflow is a kernel. It runs inside Jupyter's existing notebook format, stores inferred dependency information in notebook metadata, and infers the graph from execution rather than from a declared structure.
That gives IPyflow a migration path the others do not have. You can open an existing .ipynb, switch the kernel, and keep working. You cannot do that with Marimo without converting the notebook. The trade-off runs the other way too: because IPyflow infers dependencies from runtime behavior, the graph is only as good as the inference, and it is not something you can read and edit. In Marimo or Pluto.jl, the dependency structure is explicit and inspectable by construction.
A second difference is the default. Marimo and Pluto.jl are reactive by design. IPyflow ships with normal execution as the default and reactivity as an opt-in, which the README frames as a compatibility decision. Users who want reactivity everywhere will need to run %flow mode reactive in each session or set it as their habit.
Maintenance, licensing, and what to verify
IPyflow is licensed under BSD-3-Clause, which permits use, modification, and redistribution with the standard conditions: retain the copyright notice and license text, and do not use the project's name to endorse derived work. That is a permissive license and does not impose copyleft obligations on your notebooks or your code. This is a description of the license text, not legal advice; consult your own counsel for anything beyond that.
The repository is not archived and last received a push in September 2026. Releases are versioned in the 0.0.x series, with 0.0.228 dated June 2026, following 0.0.217 and 0.0.212 in November 2025. The 0.0.x versioning signals that the project does not yet claim API stability, which matters if you plan to depend on the reproduce_cell utility or the %flow magic in scripts and shared notebooks. The README does not publish a deprecation policy or a compatibility matrix for JupyterLab versions, so an upgrade of the frontend is a change worth testing rather than assuming.
The README links to Read the Docs for full documentation and to a JupyterLite demo, which is the fastest way to see the dependency dots and reactive execution without installing anything. Before adopting it in a shared repository, verify three things against a real notebook: that your dependency patterns are inferred correctly (especially subscripts and in-place mutation), that autosave-on-change does not conflict with your review process, and that switching the kernel back to plain Python 3 leaves the notebook in a state your collaborators can open.
Editorial conclusion
Adopt IPyflow if you work in JupyterLab or Notebook 7 and repeatedly hit stale state when running cells out of order. Do not adopt it if you need a kernel for classic Notebook, JupyterLite, or non-Jupyter frontends, or if autosave-on-change conflicts with your workflow. Before committing, verify that your notebook's dependency patterns (subscripts, aliasing, mutation) are inferred correctly, and test %flow mode reactive on a copy of a real notebook.
Community notes