Reactive execution

ipyflow’s headline feature is reactive execution: run a cell and let ipyflow re-run exactly the other cells that need it, so the notebook always looks as though it were run top to bottom. This behavior is governed by three orthogonal settings – the execution mode, the flow direction, and the execution schedule – plus an inline reactive-variable syntax. This guide covers each.

All four are also settable per session with the %flow magic (see The %flow magic) or made the default in your IPython profile.

Execution mode: lazy vs. reactive

The execution mode is an ExecutionMode:

lazy (the default)

Only the cell you execute runs. ipyflow still tracks dependencies and flags stale cells in the UI, but it does not act on them. This is the fully backwards-compatible ipykernel behavior.

reactive

Executing a cell also re-executes its stale upstream and downstream cells, in dependency order.

Switch modes at runtime with %flow mode; the change is reflected on flow().mut_settings:

run_cell("from ipyflow import flow")
run_cell("get_ipython().run_line_magic('flow', 'mode reactive')")
run_cell("assert flow().mut_settings.exec_mode.value == 'reactive'")

run_cell("get_ipython().run_line_magic('flow', 'mode lazy')")
run_cell("assert flow().mut_settings.exec_mode.value == 'lazy'")

Even in lazy mode you can reactively execute a single cell on demand with ctrl+shift+enter (or cmd+shift+enter on macOS), which runs that cell plus its dependents without changing the default mode. When the default is reactive, the same shortcut inverts to a one-off non-reactive run.

To make reactive mode the default for every new kernel, add to your IPython profile (usually ~/.ipython/profile_default/ipython_config.py):

c = get_config()
c.ipyflow.exec_mode = "reactive"  # defaults to "lazy"

Flow direction: in-order vs. any-order

The flow direction is a FlowDirection that decides which dataflow edges are eligible to drive reactive execution:

in_order (the default)

A cell may only reactively trigger cells that appear after it in the notebook. ipyflow still tracks backward references (a cell using a symbol defined later), but omits those edges when scheduling reactivity.

any_order

Spatial position is ignored; any dependency edge can drive reactivity.

In-order semantics are less flexible but encourage cleaner, reproducible notebooks that convert cleanly to top-to-bottom scripts. Toggle with %flow direction (aliases: order, semantics; values also accept ordered / unordered):

run_cell("from ipyflow import flow")
run_cell("get_ipython().run_line_magic('flow', 'direction any_order')")
run_cell("assert flow().mut_settings.flow_order.value == 'any_order'")

run_cell("get_ipython().run_line_magic('flow', 'direction in_order')")
run_cell("assert flow().mut_settings.flow_order.value == 'in_order'")

Or set the default in your profile:

c = get_config()
c.ipyflow.flow_direction = "any_order"  # defaults to "in_order"

Execution schedule

The schedule is an ExecutionSchedule controlling how the set of cells to re-run is computed:

dag_based (the default)

Uses the dynamic dataflow DAG built from actual runtime dependencies. The most precise option.

liveness_based

Uses static liveness analysis of cell source (which symbols each cell reads and writes). Useful when runtime edges are unavailable – for instance the JupyterLite/Pyodide build defaults to this.

hybrid_dag_liveness_based

Combines both; required for incremental reactivity (see below).

run_cell("from ipyflow import flow")
run_cell("get_ipython().run_line_magic('flow', 'schedule liveness_based')")
run_cell("assert flow().mut_settings.exec_schedule.value == 'liveness_based'")

A related knob, ReactivityMode (%flow reactivity [batch|incremental]), chooses whether reactive updates are applied all at once (batch, the default) or one cell at a time (incremental). Incremental mode requires a liveness-aware schedule; selecting it while on dag_based automatically upgrades the schedule to hybrid_dag_liveness_based.

Reactive-variable syntax

Reactivity is normally cell-level. The $ prefix opts an individual symbol into reactivity so that updates propagate even in lazy mode:

# Cell 1
x = 1

# Cell 2 -- `$x` marks this usage as reactive
y = $x + 1

# Now re-running Cell 1 with a new value reactively refreshes Cell 2,
# even though the global execution mode is still lazy.

A double prefix, $$x, additionally makes the definition cascade: downstream reactive readers update transitively. The $ syntax is applied by ipyflow’s input transformer in the JupyterLab/Notebook frontend; because it is a frontend-level source rewrite it is demonstrated here as prose rather than an executed example.

See also

Configuration (ipyflow.config) for the full enum reference and The %flow magic for every %flow subcommand that toggles these settings.