# Marimo Notebooks

> |

- **Type:** Skill
- **Install:** `agentstack add skill-pymc-labs-agent-skills-marimo-notebooks`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [pymc-labs](https://agentstack.voostack.com/s/pymc-labs)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [pymc-labs](https://github.com/pymc-labs)
- **Source:** https://github.com/pymc-labs/agent-skills/tree/main/skills/marimo-notebooks

## Install

```sh
agentstack add skill-pymc-labs-agent-skills-marimo-notebooks
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Marimo Notebooks

Marimo notebooks are reactive Python notebooks stored as pure `.py` files. Cells auto-execute when dependencies change, modeled as a directed acyclic graph (DAG).

## Core Concepts

### Reactivity Model
- marimo uses **static analysis** to build a dependency graph from variable references and definitions
- When a cell runs, all cells referencing its defined variables **automatically re-run**
- Execution order follows the dependency graph, **not visual cell order**
- Each global variable must be defined by **exactly one cell**
- marimo does **not track object mutations** (like `list.append()`)—mutate in the same cell that creates the object, or create new variables

### Avoiding Variable Name Conflicts
Each global variable must be defined by exactly one cell. Two strategies:

**1. Wrap code in functions (preferred for reusable patterns):**
```python
@app.cell
def _(data):
    def compute_mean_with_new_col(df):
        temp = df.copy()
        temp["new_col"] = temp["x"] * 2
        return temp.mean()

    return (compute_mean_with_new_col(data),)
```

**2. Use meaningful, unique variable names:**
```python
@app.cell
def _(model1_data):
    model1_transformed = model1_data.copy()
    model1_transformed["new_col"] = model1_transformed["x"] * 2
    return (model1_transformed,)
```

Never use underscore prefixes to generate unique variable names. No exceptions.

## Notebook Structure

```python
import marimo

__generated_with = "0.10.0"
app = marimo.App(width="medium")

@app.cell
def _():
    import marimo as mo
    return (mo,)

@app.cell
def _(mo):
    mo.md("# Hello")
    return

if __name__ == "__main__":
    app.run()
```

Key rules:
- Each cell is a function decorated with `@app.cell`
- Variables shared by returning tuples: `return (var1, var2,)`
- Cells receive variables as parameters: `def _(mo, df):`
- Execution order follows dependency graph, not position
- Name cells descriptively for CellTour targeting: `def model_specification():`

## CLI Commands

```bash
# Create & Edit
marimo new                           # Create new notebook
marimo edit notebook.py              # Open editor
marimo edit notebook.py --watch      # Live reload on file changes

# Run as App
marimo run notebook.py               # Run as app (code hidden by default)
marimo run notebook.py --include-code  # Show code in app view

# Convert
marimo convert notebook.ipynb -o notebook.py  # Jupyter to marimo

# Export
marimo export html notebook.py -o out.html    # Static HTML
marimo export ipynb notebook.py -o out.ipynb  # To Jupyter

# Validate
marimo check notebook.py             # Lint and validate
marimo check notebook.py --fix       # Auto-fix issues
```

## Code Visibility in Run Mode

**CRITICAL FOR TUTORIALS**: By default, `marimo run` hides code. Use `mo.show_code()` to display it.

### mo.show_code() - Per-Cell Display

**IMPORTANT**: Call `mo.show_code()` as a **statement on its own line**, NOT in the return statement.

```python
@app.cell
def model_definition(mo, pm, X, y):
    with pm.Model() as model:
        alpha = pm.Normal("alpha", mu=0, sigma=10)
        beta = pm.Normal("beta", mu=0, sigma=10)
        mu = alpha + beta * X
        pm.Normal("y", mu=mu, sigma=1, observed=y)

    # Show this cell's code alongside its output
    mo.show_code(model, position="above")
    return (model,)
```

**WRONG vs RIGHT patterns:**
```python
# WRONG - do not put mo.show_code() in return statement
return mo.show_code(result)

# RIGHT - call as statement, then return separately  
mo.show_code(result, position="above")
return (result,)
```

- `position="above"` shows code first, then output (best for tutorials)
- `position="below"` shows output first, then code (default)

## Markdown with mo.md()

```python
@app.cell
def _(mo):
    mo.md(r"""
    # Title

    Interpolate Python: {slider}

    **LaTeX**: $f(x) = e^x$

    $$\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}$$

    **Icons**: ::lucide:rocket:: or ::mdi:home::
    """)
    return
```

- Use raw strings (`r"""..."""`) for LaTeX
- Interpolate UI elements: `f"Value: {slider}"`
- For complex objects: `f"Plot: {mo.as_html(fig)}"`

## UI Components (mo.ui.*)

### Basic Inputs
```python
slider = mo.ui.slider(0, 100, value=50, label="Value")
number = mo.ui.number(0, 100, value=50)
text = mo.ui.text(value="", placeholder="Enter text")
checkbox = mo.ui.checkbox(value=False, label="Enable")
dropdown = mo.ui.dropdown(["a", "b", "c"], value="a")
radio = mo.ui.radio(["option1", "option2"], value="option1")
multiselect = mo.ui.multiselect(["a", "b", "c"])
```

### Buttons
```python
button = mo.ui.button(label="Click")
run_button = mo.ui.run_button(label="Run")  # For triggering computation
```

### Data Components
```python
table = mo.ui.table(df)            # Interactive table with selection
dataframe = mo.ui.dataframe(df)    # Editable dataframe
data_explorer = mo.ui.data_explorer(df)  # No-code exploration
```

### Grouping UI Elements
```python
# Forms (require submit button)
form = mo.ui.text().form()

# Batch in markdown
form = mo.md("""
**Name**: {name}
**Age**: {age}
""").batch(
    name=mo.ui.text(),
    age=mo.ui.number(0, 120)
).form()
```

See [references/ui_components.md](references/ui_components.md) for complete reference.

## Layout Functions

```python
# Stacking
mo.hstack([el1, el2, el3], justify="center", gap=2)
mo.vstack([el1, el2, el3], align="start", gap=1)

# Containers
mo.accordion({"Section 1": content1, "Section 2": content2})
mo.tabs({"Tab 1": content1, "Tab 2": content2})
mo.callout(content, kind="info")  # info, warn, success, danger, neutral
mo.sidebar([nav_content])

# Display
mo.tree({"a": {"b": 1}})    # Tree view
mo.stat(value="42", label="Users", caption="+5%")
mo.lazy(expensive_component)  # Defer until visible
```

## Output Functions

```python
mo.output.replace(new_content)  # Replace cell output
mo.output.append(additional)    # Append to output
mo.output.clear()               # Clear output

with mo.redirect_stdout():
    print("This goes to cell output")
```

## Status Indicators

```python
# Progress bar
for item in mo.status.progress_bar(items, title="Processing"):
    process(item)

# Spinner
with mo.status.spinner(title="Loading..."):
    load_data()
```

## Control Flow

```python
# Stop execution conditionally
mo.stop(condition, mo.md("*Message when stopped*"))

# Example: require button click
run_button = mo.ui.run_button()
mo.stop(not run_button.value, mo.md("Click Run to execute"))
expensive_computation()
```

## Caching

```python
# In-memory cache (session only)
@mo.cache
def expensive_function(x, y):
    return compute(x, y)

# Persistent cache (survives restarts)
@mo.persistent_cache(name="embeddings")
def compute_embeddings(text):
    return model.encode(text)
```

See [references/caching.md](references/caching.md) for model output caching patterns.

## State Management

**Warning**: Use sparingly—over 99% of cases don't need `mo.state()`.

```python
@app.cell
def _(mo):
    get_count, set_count = mo.state(0)
    return get_count, set_count

@app.cell
def _(mo, get_count, set_count):
    mo.ui.button(
        label=f"Count: {get_count()}",
        on_click=lambda _: set_count(lambda n: n + 1)
    )
    return
```

Use only when maintaining history, synchronizing UI bidirectionally, or introducing cycles.

## Interactive Plotting

```python
# Altair with selection
chart = alt.Chart(df).mark_point().encode(x="x", y="y")
selection = mo.ui.altair_chart(chart, chart_selection="point")
selected_data = selection.value  # DataFrame of selected points

# Plotly
fig = px.scatter(df, x="x", y="y")
interactive = mo.ui.plotly(fig)
interactive.value  # Selected points

# Matplotlib interactive
fig, ax = plt.subplots()
ax.plot(x, y)
mo.mpl.interactive(fig)
```

Supported: Matplotlib, Seaborn, Plotly, Altair, Bokeh, HoloViews, hvPlot

## Wigglystuff Widgets

```python
from wigglystuff import Slider2D, Paint, SortableList, Matrix, CellTour
import marimo as mo

slider2d = mo.ui.anywidget(Slider2D())
slider2d.x, slider2d.y

paint = mo.ui.anywidget(Paint(width=400, height=300))
paint.to_pil()

tour = mo.ui.anywidget(CellTour(
    steps=[
        {"cell_name": "intro", "title": "Welcome", "description": "..."},
        {"cell_name": "model", "title": "Model", "description": "..."},
    ],
    auto_start=False
))
```

See [references/wigglystuff.md](references/wigglystuff.md) for all widgets.

## Best Practices

1. **Wrap reusable code in functions** - Keeps intermediate variables local
2. **Use meaningful, unique variable names** - e.g., `model1_sigma`, `model2_sigma`
3. **Don't mutate across cells** - Mutate in same cell or create new variables
4. **Write idempotent cells** - Same inputs produce same outputs
5. **Use mo.stop()** - Gate expensive operations behind conditions/buttons
6. **Use lazy loading** - `mo.lazy()` for expensive components in tabs/accordions
7. **Cache expensive ops** - `@mo.cache` for session, `@mo.persistent_cache` for disk

## CRITICAL: Pre-Edit Checklist

**BEFORE making ANY edit to a marimo notebook:**

1. **Read the current file state** — The file may have been modified by marimo's editor
2. **Run `marimo check notebook.py`** — Verify valid before and after edits

**AFTER completing edits:**

3. **Grep for `print(`** — Replace ALL print statements with marimo output (`mo.md()`, `mo.stat()`, `mo.callout()`)
4. **Run `marimo check notebook.py`** — Verify no errors introduced

## Common Gotchas

### Output & Display

- **NEVER use print() in cells**: Print statements do NOT display in run mode. Always use:
  - `mo.md(f"**Label:** {value}")` — formatted text
  - `mo.stat(value=f"{x}", label="Label")` — metric cards
  - `mo.callout(content, kind="info")` — callout boxes
  - Return the dataframe/object directly — automatic display

- **mo.show_code() must be called as a statement, not in return**

- **Never delete output without replacing**: When removing print statements, replace with equivalent marimo output

### Reactivity & Variables

- **Closures in loops**: Use default args `lambda v, i=i: ...` not `lambda v: ... i`
- **on_change handlers**: Only work if element is bound to global variable
- **Dynamic UI elements**: Must wrap in `mo.ui.array()`, `mo.ui.dictionary()`, or `mo.ui.batch()`
- **Type annotations**: Registered as references unless quoted: `x: "SomeType"`

### Libraries

- **matplotlib cut-off**: Call `plt.tight_layout()` before outputting
- **dotenv**: Use `dotenv.load_dotenv(dotenv.find_dotenv(usecwd=True))`

## References

- **UI components**: [references/ui_components.md](references/ui_components.md)
- **Wigglystuff widgets**: [references/wigglystuff.md](references/wigglystuff.md)
- **Caching patterns**: [references/caching.md](references/caching.md)
- **Advanced features**: [references/advanced.md](references/advanced.md) (SQL, deployment, testing, routing)
- **Official docs**: https://docs.marimo.io

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [pymc-labs](https://github.com/pymc-labs)
- **Source:** [pymc-labs/agent-skills](https://github.com/pymc-labs/agent-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-pymc-labs-agent-skills-marimo-notebooks
- Seller: https://agentstack.voostack.com/s/pymc-labs
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
