marimo-notebook
marimo Notebook
marimo is a reactive Python notebook — cells form a dataflow graph, and changing one cell automatically re-runs its dependents. This skill encodes conventions for how to run marimo and how to build a canonical scatter-plot-with-selected-rows-table exploration UI.
Running marimo
Run marimo listening on all interfaces, with no token, in sandbox mode:
marimo edit --host 0.0.0.0 --no-token --sandbox notebook.py
# view-only (app mode):
marimo run --host 0.0.0.0 --no-token --sandbox notebook.py
Why each flag:
| Flag | Purpose |
|---|---|
--host 0.0.0.0 |
Bind to every interface so a phone, tablet, or another laptop on the LAN can hit http://<host-ip>:<port>. 127.0.0.1 (the default) only reaches localhost. |
--no-token |
Skip the URL token, so the link is paste-able. Also: servers started with --no-token register in marimo's local server registry, which is what tooling like marimo-pair relies on to auto-discover sessions. |
--sandbox |
Run the notebook in an isolated uv-managed venv driven by inline # /// script dependency metadata at the top of the .py file. Keeps per-notebook deps out of the system/project env. |
Use --no-token only on trusted networks (home LAN, local dev). On anything
shared, leave the token on and use MARIMO_TOKEN to pass it to tooling.
Inline dependencies for sandbox mode
With --sandbox, put PEP 723 script metadata at the top of the notebook so
uv knows what to install:
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "marimo",
# "polars",
# "jupyter-scatter",
# "pandas",
# ]
# ///
import marimo
app = marimo.App()
Edit this block when you add imports — sandbox mode will refuse to import anything that isn't declared here.
Scatter plots: always use jscatter
Default to jupyter-scatter (import jscatter)
for scatter plots. It's GPU-accelerated, handles millions of points, has a
built-in lasso selection tool, and bridges cleanly into marimo's reactive
graph through traitlets. Reach for matplotlib / plotly / altair only for
small, non-interactive cases.
Long-press activates the lasso
Explicitly enable long-press as the lasso initiator:
scatter.lasso(on_long_press=True)
on_long_press=True is actually jupyter-scatter's default, but set it
explicitly — the keyboard-modifier initiator (shift+drag) is unreliable on
some machines/keyboards/OSes, so readers of the notebook should be able to
see at a glance that long-press is the supported gesture and not have to
guess at a keyboard shortcut.
Always show a table of the selected rows below the plot
Bridge the jscatter widget's selection trait into marimo's reactive graph
with mo.state + .observe(...), then render a mo.ui.table in the next
cell that filters the dataframe by the current selection. The table updates
live as the user lassos.
@app.cell
def _(df, mo):
import jscatter
# jupyter-scatter wants pandas
pdf = df.to_pandas() if hasattr(df, "to_pandas") else df
scatter = (
jscatter.Scatter(x="x", y="y", data=pdf)
.height(500)
.color(by="category")
.legend(True)
.tooltip(True)
)
scatter.lasso(on_long_press=True)
# bridge jscatter's `selection` traitlet into marimo's reactive graph
get_selection, set_selection = mo.state(scatter.widget.selection)
scatter.widget.observe(
lambda _: set_selection(scatter.widget.selection),
names=["selection"],
)
scatter.widget
return get_selection, pdf
@app.cell
def _(get_selection, mo, pdf):
sel = get_selection()
mo.ui.table(
pdf.iloc[sel] if len(sel) else pdf.head(0),
page_size=25,
)
return
Notes:
- Use
scatter.widget, notscatter, for the observe/display.Scatteris a configuration object;scatter.widgetis the anywidget instance that owns theselectiontraitlet. - Return
get_selection(the getter), not the state value. Returning the scalar freezes the downstream cell at construction time; returning the getter lets the downstream cell re-read on every reactive tick. - Empty-selection handling: show
pdf.head(0)(empty frame with headers) rather than the full table when nothing is selected — the point of the table is to reflect the lasso, not to be a second data browser. mo.ui.tablevsquak.Widget:mo.ui.tableis the default. Reach formo.ui.anywidget(quak.Widget(df))when you want SQL-style filtering, faceting, and column stats inside the table itself.
Notebook skeleton
# /// script
# requires-python = ">=3.11"
# dependencies = ["marimo", "polars", "jupyter-scatter", "pandas"]
# ///
import marimo
app = marimo.App(width="medium")
@app.cell
def _():
import marimo as mo
import polars as pl
return mo, pl
@app.cell
def _(pl):
df = pl.read_parquet("data.parquet")
return (df,)
# ... scatter cell + selected-rows table cell from above ...
if __name__ == "__main__":
app.run()
Gotchas
- Don't edit the
.pyfile whilemarimo editis running on it. The kernel owns the file; writes from the outside will be clobbered or ignored. Use the marimo UI, or stop the server first. - Sandbox mode is per-notebook. Each
--sandboxinvocation resolves its own venv from the inline script metadata. Don't try to share a venv across notebooks via--sandbox; just run them with the project's regular env. --no-tokenleaks your notebook to the LAN. On coffee-shop wifi or a shared office network, either drop--host 0.0.0.0or keep the token.- Widget traitlets live outside the reactive graph. Setting
scatter.widget.selection = [...]directly from Python works, but won't flow into marimo state unless you've wired an.observe(...)bridge like the one above.
More from brojonat/llmsrules
ibis-data
Use Ibis for database-agnostic data access in Python. Use when writing data queries, connecting to databases (DuckDB, PostgreSQL, SQLite), or building portable data pipelines that should work across backends.
13go-service
Build Go microservices with stdlib HTTP handlers, sqlc, urfave/cli, and slog. Use when creating or modifying a Go HTTP server, adding routes, middleware, database queries, or CLI commands.
13temporal-go
Build Temporal workflow applications in Go. Use when creating or modifying Temporal workflows, activities, workers, clients, signals, queries, updates, retry policies, saga patterns, or writing Temporal tests.
13parquet-analysis
Analyze parquet files using Python and Ibis. Use when the user wants to explore, transform, or analyze parquet data files, perform aggregations, joins, or export results. Works with local parquet files and provides database-agnostic data operations.
12ducklake
Work with DuckLake, an open lakehouse format built on DuckDB. Use when creating or querying DuckLake tables, managing snapshots, time travel, schema evolution, partitioning, or lakehouse maintenance operations.
12temporal-python
Build Temporal applications in Python using the temporalio SDK. Use when creating workflows, activities, workers, clients, signals, queries, updates, child workflows, timers, retry policies, saga/compensation patterns, testing, or any durable execution pattern in Python.
12