- name
- marimo-studio
- description
- Turn a Marimo notebook into focused web views. Use when an agent needs to inspect notebook and view source, edit a view safely, build it, show it in Studio, inspect its URL with browser tools, run it, or export it.
# Author Marimo Studio views
## Start with the installed workflow
At the start of every Studio task, run `help(marimo_studio.agent)` in the
notebook's Python environment, including when another copy of this skill is
already available:
```python
import marimo_studio.agent
help(marimo_studio.agent)
skill = marimo_studio.agent.agent_skill()
print(skill.body)
print(skill.tree())
```
The help exposes the current installed API and its packaged skill as a
traversable Python object. Follow that version-matched skill body and traverse
its bundled resources as needed. Repeat discovery when the notebook environment
or installed Studio version changes. Once this installed body is loaded,
continue with the workflow.
Use `marimo_studio.agent.current_workspace()` inside notebook code mode. From a
terminal, use `marimo_studio.authoring.open_workspace("notebook.py")` or the
CLI with `--target notebook.py`. Preview URL requests outside code mode also
need the running `server` URL or `--server`. Let code-mode execution finish
before browser interactions that need the same notebook kernel.
## Choose the delivery runtime
Choose before designing controls or exposing data:
| Runtime | Interaction | Privacy boundary |
| --------------------------- | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Server (`server`) | Python computes new states using server packages and services | Source and credentials stay on the server. Projected outputs reach visitors. |
| WASM (`wasm`) | Pyodide computes new states in the visitor's browser | Visitors receive notebook source and browser-accessible data. Never embed secrets. |
| Zero-Python (`zero-python`) | Visitors select finite prepared states, with browser-only interaction on published data | Python runs during preparation. Visitors receive prepared outputs and public files, including states they have not selected. |
Static export defaults to Zero-Python. Choose WASM explicitly when visitors
need unprepared states and the notebook supports Pyodide. Use Server for
interactions that need private services or native Python packages. The editor
can preview all three runtimes. A live `marimo run` serves Server or WASM.
Follow [Run or export](#run-or-export) to configure, preflight, and verify the
chosen delivery.
## Preserve notebook traceability
Apply these conventions while authoring every view, even when Lens is not
installed, so adding Lens exposes named targets and their source context.
Keep metadata on authored regions and projection hosts, outside native Marimo
output subtrees.
NEVER hardcode notebook-derived values in view HTML or source code when they
can be expressed with `mo-value`, `marimo-output`, or `marimo-cell`. This includes
metrics, counts, dates, categories, chart data, and analytical claims embedded in
prose. Copied values go stale and sever the link to their analytical context.
Expose missing results as named notebook values, then project them. Static UI
copy and design constants may remain literal.
Use `mo-value` for values, `marimo-output` for rich values, and `marimo-cell`
for native cell output. Keep analytical computation in the notebook. Custom
JavaScript rendering must consume live projections, handle their updates, and
declare every kernel input:
- Place hidden `mo-value` hosts directly inside the result, or use
`data-marimo-lens-inputs="rows-data summary-data"` to reference projection hosts by
unique, stable HTML IDs in the same document. Include every input, including
shared inputs used through JS transforms. References must point directly to
mounted `mo-value`, `marimo-output`, or `marimo-cell` hosts. Missing or duplicate
IDs make the result unavailable to Lens. Never fabricate runtime metadata.
- Annotate individual metrics, rows, charts, and report pages. Prefer narrow
selectors such as `summary.events`. Bind dynamic selectors and source IDs to
the state that renders the result. Use `data-marimo-allow="*"` for selectors
the provider cannot bound at build time. Unbounded selectors require Python
or Browser runtime (`--runtime wasm` for export). Prepared exports need finite
authored targets.
- Link browser-only aggregates to their actual kernel inputs and label the
browser calculation. Canvas and PDF picking is limited to the chart or page
unless the renderer supplies finer DOM targets.
- Set `aria-busy="true"` during asynchronous rendering and clear it on completion.
Verify selection and producer context after data updates. These links declare
dependencies, not automatic JS dataflow or historical values.
- Studio supplies native projection labels. Give custom regions a
`data-marimo-lens-label` and optional `data-marimo-lens-detail`. Display text
supplements the source links that connect results to the analytical graph.
- Give custom regions a `data-marimo-lens-render-source` JSON reference with
their actual project-relative source `path` and optional `symbol`. Keep it
current as source moves. Use `data-marimo-lens-context` on a chart, card, or
section when it defines the intended image context for selections.
## Activate the first view immediately
When the user requests their first view, make a visible result the first
milestone. In code mode, create a view named for the request, build its starter,
and call `show()` in the next execution. From a terminal, create and build the
view through the CLI or saved workspace, then open its preview URL. The user
should see the view while the rest of the work develops.
Do substantial data exploration, analysis expansion, custom layout, and styling
after that first visible result. Refine in small visible steps through edit,
build, reload, and verification. Keep each authoring call bounded to a coherent
source change.
Inspect configured views before creating one. In code mode:
```python
import marimo_studio.agent as studio_agent
workspace = studio_agent.current_workspace()
status = await workspace.status()
if any(item.name == "dashboard" for item in status.views):
view = workspace.view("dashboard")
else:
view = await workspace.create_view("dashboard")
await view.build()
```
Use the user's requested name and frontend starter when specified. Otherwise,
the default starter produces an editable HTML document populated with enabled
cells that may display output, including literal Markdown, and project-specific
agent instructions. Inspect installed starters when selecting a requested
frontend.
Run `show()` in the next execution so the Studio tab can finish the transition:
```python
import marimo_studio.agent as studio_agent
await studio_agent.current_workspace().view("dashboard").show()
```
`show()` activates the view in the user's Studio tab. Continue with
[Build, show, and verify](#build-show-and-verify) to inspect the view URL using
the environment's preferred browser tool.
Reimport `marimo_studio.agent` and reacquire the workspace and view in each
code-mode execution. Scratch imports and handles from a preceding execution
may be gone. If build or activation fails, repair the reported problem and
retry that milestone before expanding the view.
## Keep notebook and view ownership clear
The notebook computes. The view presents. Studio connects them.
Keep data access, transformations, controls, and reusable results in notebook
cells. Keep view structure, wording, styles, and browser interaction in view
source. Each named frontend project is a view.
## Inspect before editing
Inspect the notebook before changing data, computation, controls, or reusable
results:
```python
notebook = await workspace.inspect_notebook()
```
For a change to one result, include the cells that produce its inputs:
```python
producer = await workspace.inspect_notebook(
selectors=("summary",),
include_code=True,
context="upstream",
)
```
Inspect the view to discover the files Studio exposes:
```python
inspection = await view.inspect()
for document in inspection.documents:
print(document.path, document.language, document.access)
```
Read `AGENTS.md` when the selected project exposes it:
```python
if any(document.path.as_posix() == "AGENTS.md" for document in inspection.documents):
instructions = await view.read("AGENTS.md")
print(instructions.content)
```
The Studio skill owns notebook boundaries, projection semantics, view lifecycle,
and validation. The starter's `AGENTS.md` owns its opinionated frontend
structure, supplied adapters, preferred libraries, and build-specific
conventions. Read both before editing a starter project.
Treat the generated `AGENTS.md` as durable project context. Update it as the
conversation establishes the audience, analytical goal, concrete domain details,
aesthetic direction, interaction priorities, framework or library preferences,
and other decisions that should guide later agents. Keep transient task status
and short-lived implementation notes out of it.
## Author Python notebook analysis
For dataset work, keep the notebook markdown-led and reactive. Build named
dataframe results that a Studio view can present or consume.
### Pair one explanation with one analytical cell
Introduce each analytical question with a short markdown cell. Put one focused
Python cell directly after it. The Python cell should derive one well-defined
table, metric, model input, or visualization input from an upstream dataset.
Each fenced block represents one notebook cell:
```python
mo.md("""
## Revenue by segment
Aggregate valid revenue rows so the view can compare segments directly.
""")
```
```python
segment_summary = (
orders.lazy()
.filter(pl.col("revenue").is_not_null())
.group_by("segment")
.agg(pl.sum("revenue").alias("revenue"))
.sort("revenue", descending=True)
.collect()
)
segment_summary
```
The assignment makes `segment_summary` available to downstream cells. The final
expression renders the dataframe as this cell's output. Prefer Polars
expressions for dataframe-native transformations. Use DuckDB when the operation
is clearer as SQL, then materialize a dataframe at the same cell boundary:
```python
segment_summary_sql = duckdb.sql("""
SELECT segment, SUM(revenue) AS revenue
FROM orders
WHERE revenue IS NOT NULL
GROUP BY segment
ORDER BY revenue DESC
""").pl()
segment_summary_sql
```
### Keep the reactive dataflow legible
- Load or receive the base dataset once, then derive named results downstream.
- Give each cell one semantic responsibility and one principal result. Separate
filtering, aggregation, enrichment, modeling, and presentation inputs when
they answer different questions.
- Prefer expression-based transformations over in-place mutation. Materialize a
Polars or DuckDB result when the cell establishes a reusable dataframe
boundary.
- Use domain names such as `filtered_orders`, `segment_summary`, and
`retention_by_month`. Reserve underscore-prefixed values for cell-local
helpers.
- Put the dataframe, chart, control, or other intended result in the final
expression. Keep diagnostic dumps and large unbounded previews out of the
analytical flow.
- Keep the graph acyclic. Define each shared variable once and let downstream
cells react to it.
### Parameterize dimensions with Marimo controls
Create a control in one cell, display it as that cell's final expression, and
read its `.value` from downstream transformation cells. Choose the control from
the dimension's datatype and selection semantics:
| Dimension | Marimo control |
| ----------------------------------------- | -------------------------------------------------- |
| One value from a small categorical domain | `mo.ui.dropdown` |
| Several categorical values | `mo.ui.multiselect` |
| Ordered numeric value or interval | `mo.ui.slider` or `mo.ui.range_slider` |
| Exact numeric input | `mo.ui.number` |
| Date, datetime, or date interval | `mo.ui.date`, `mo.ui.datetime`, `mo.ui.date_range` |
| Boolean choice | `mo.ui.switch` or `mo.ui.checkbox` |
Derive options, bounds, and defaults from the dataframe when practical. Keep the
control label tied to the analytical dimension. For example, use one control
cell and one dependent dataframe cell:
```python
segment = mo.ui.dropdown.from_series(orders["segment"], label="Segment")
segment
```
```python
selected_orders = (
orders
if segment.value is None
else orders.filter(pl.col("segment") == segment.value)
)
selected_orders
```
## Author Studio view files
Edit source with Studio's guarded writes or the environment's filesystem
tools. Use `inspection.root` as the project root. Studio writes require a
catalog document with `access="edit"`. Provider inspection owns source discovery
and build inputs for either editing path. Keep generated `.artifacts/` files
under Studio's ownership. Use view files for structure, wording, styles, and
browser interaction.
The view project owns page layout, styles, icons, and browser dependencies.
Mount the authored page or component beneath `#app-shell`.
### Edit the selected provider's source
Use `view.inspect()` and the project's `AGENTS.md` to choose files before
editing. Bundled starters use these entry points:
| Provider | Page source | Styles |
| ----------------------- | --------------------------------- | -------------------------------- |
| Vanilla | `index.html` | Inline CSS or linked project CSS |
| React | `src/App.tsx` | `src/style.css` |
| Svelte | `src/App.svelte` | `src/style.css` |
| Observable Notebook Kit | `src/index.html`, `src/page.tmpl` | `src/style.css` |
Run dependency commands from the view root through the notebook's Python
environment with `marimo-studio[deno]` installed. This keeps authoring and
builds on the same Deno version. For a standalone tool environment, replace
`uv run -- deno` with `uvx --from 'deno==<installed-deno-version>' deno`.
Use the Deno package version installed in the notebook's Python environment.
Pinning Studio alone does not pin Deno, because its extra allows newer versions.
- React: `uv run -- deno add --frozen=false --save-exact <package>` updates
`deno.json` and `deno.lock`.
- Svelte: `uv run -- deno add --package-json --frozen=false --save-exact <package>`
updates `package.json` and `deno.lock` for Vite's package resolution.
- Notebook Kit: pin npm dependencies in `package.json`, then run
`uv run -- deno install --frozen=false --node-modules-dir=auto --no-save`
to regenerate `deno.lock`.
Preserve the project's minimum dependency age and frozen-build policy. Commit
the changed dependency manifest and lockfile together. Follow the selected
starter's `AGENTS.md` for its dependency workflow, then run `view.build()`.
### Choose visual direction
Choose visual direction in this order:
1. Follow the user's explicit style direction.
2. Follow the starter project's `DESIGN.md` when it exists.
3. Otherwise suggest and use Marimo's visual style from
[Marimo's `DESIGN.md`](https://raw.githubusercontent.com/marimo-team/marimo/refs/heads/main/DESIGN.md).
Name this file explicitly when explaining the chosen design. Use another
design whenever the user requests it.
Read the selected design source before visual authoring. Apply its visual
character, tokens, typography, surfaces, component treatment, and motion
guidance so the selected direction shapes the whole view.
### Keep view source maintainable
Treat agent-authored view source as maintained application code. A person should
be able to inspect it, understand its analytical flow, and change it from the
source and project context alone.
- Structure code as focused, composable components, functions, modules, or
actions with clear responsibilities.
- Prefer declarative markup, derived values, and pure data transformations over
deeply nested control flow, scattered DOM mutation, or repeated plumbing.
- Use domain-specific names and explicit intermediate values that expose data,
state, and interaction dependencies.
- Keep imports, formatting, and file organization consistent across the project.
Run a compatible formatter after substantive edits whenever the environment
provides one.
Use the formatter and configuration declared by the selected view project.
Review its diff before building. When the project declares no formatter,
preserve its existing formatting conventions and let the Studio build validate
the source.
### Add files when the starter needs more structure
A Vanilla view can keep its HTML entry document self-contained or reference
project-local CSS and JavaScript directly. Reference `.css` through
Ver en GitHub