Prevent AI agents from breaking marimo notebooks with Python/Jupyter idioms.
This skill is designed to keep agents on track when building interactive notebooks (the standard use case).
It focuses on preventing DAG errors and broken UI reactivity while also covering the parts of marimo agents commonly get wrong in practice: UI patterns, SQL, state management, validation, deployment, and export. It still does not aim to document the entire Marimo API, and it does not cover advanced programmatic use cases like Cell.run().
LLMs are overfitted on Jupyter notebooks and imperative Python scripts. When they try to write marimo, they usually break the reactivity model by:
- Defining functions for cells (Marimo cells share the global scope; passing args is redundant and breaks the DAG).
- Using
print()(Fails silently inmarimo run/ app mode; must usemo.mdormo.stat). - Returning values (Cells don't "return" data; they define global variables).
- Mutating state (Reading and writing a variable in the same cell creates cycles).
This skill provides context prompts to force the model into "Marimo Mode."
The skill actively prompts the agent to follow these rules:
| Category | Rule | Reason |
|---|---|---|
| Variables | Global, not Parameter | Cells are not functions. Don't write def process(df):. Just use df directly. |
| Output | mo.md() / mo.stat() |
print() outputs go to the console, not the UI, when running as an app. |
| Flow Control | mo.stop(), not raise |
Use mo.stop() to conditionally halt execution without crashing the app UI. |
| Reactivity | Split Definition & Read | If you define slider = mo.ui.slider(), you cannot read slider.value in the same cell. |
| Locals | Use _ prefix |
_var stays local to the cell. Anything else becomes a global variable in the DAG. |
| Callbacks | Prefer reactivity over on_change |
Most notebooks should read .value in downstream cells; if you do use callbacks, marimo passes the raw value (int/str), not a Jupyter-style change dict. |
| SQL | Direct DataFrame | mo.sql() returns a DataFrame directly. Do not try to access .value. |
| Forms | Check for None |
form.value is None until submitted. Guard against this with mo.stop(). |
| Mutation | Mutate Locally | Do not mutate a global variable (like a DataFrame) in a different cell than where it was defined. |