Skip to main content

marimo-notebooks

Publish interactive marimo notebooks via the daimon MCP server. Mint a one-time upload URL with create_notebook_upload_url, get the .py into a sandbox file, and curl -X PUT --data-binary it to the URL — source never goes through a tool argument, which truncates. permanent=True publishes the same notebook as a read-only shareable blog instead of a scratch one. Also covers attaching data files, list_notebooks and delete_notebook. Use when someone asks for a notebook, dashboard, data explorer, or to publish an analysis as a blog post.

Ir a la instalación

Datos de origen

Repositorio
pymc-labs/daimon
Última actividad en el origen
9 de agosto de 2026 a las 03:52
Idioma detectado de SKILL.md
inglés
Estrellas
15
Forks
5

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
marimo_notebooks
description
Publish interactive marimo notebooks via the daimon MCP server. Mint a one-time upload URL with create_notebook_upload_url, get the .py into a sandbox file, and curl -X PUT --data-binary it to the URL — source never goes through a tool argument, which truncates. permanent=True publishes the same notebook as a read-only shareable blog instead of a scratch one. Also covers attaching data files, list_notebooks and delete_notebook. Use when someone asks for a notebook, dashboard, data explorer, or to publish an analysis as a blog post.
# marimo_notebooks Publish an interactive marimo notebook for the user. You mint a one-time upload URL, get the notebook's `.py` into a sandbox file, and `curl -X PUT --data-binary` it to the URL — the source never goes through a tool argument, which truncates large notebooks. The curl response returns a slug-as-secret URL the user opens in a browser. Treat that URL as private — share it only with the user who asked. A notebook is data work, not decoration. The person on the other end is usually trying to answer a real question. A polished notebook that answers the *wrong* question, or that "discovers" conclusions you secretly invented, or that errors on the first cell, is worse than no notebook — it looks authoritative while being hollow. The three rules below exist to prevent exactly that. Read them before you build anything. ## Scratch notebook or permanent blog One tool publishes both. `create_notebook_upload_url(slug=..., permanent=...)`: | | `permanent=False` (default) | `permanent=True` | |---|---|---| | Shape | edit mode — the editor is visible | run mode — a read-only app, source hidden | | Lifetime | reaped after the host's TTL | survives host restarts, never reaped | | Slug | optional; omit for a random one | choose a meaningful, stable name — it is part of the URL | **Default to `permanent=False`.** Publish the scratch version, let the user look at it, and re-upload the *same slug* with `permanent=True` once it is worth keeping. Deciding permanence before anyone has seen the notebook is how hosts accumulate blogs nobody wanted. Both are a live Python kernel, not a WASM export — so PyMC/ArviZ widgets genuinely work, sliders re-plot a real posterior, dropdowns switch parameters. That is what this tool buys over a static image. A permanent blog runs its cells **on every page load, per reader**. See "Precompute, never sample live" below — it is the rule that matters most there. ## 1. Align before you build Building the wrong notebook is expensive: it wastes a round-trip and hands the user a confident-looking artifact that answers a question they didn't ask. A one-line check is cheap. So spend it — but only when it changes what you build. This is a general assistant, not a wizard. Do **not** interrogate the user. **Your first move on any notebook request is to classify it, not to write code.** Decide: do I have the data, and is the scope unambiguous? - **If yes to both → build now.** "Plot column A of the CSV I attached" needs no clarification — just build it. Don't manufacture questions. - **If a trigger below fires → STOP. Your entire reply this turn is the clarifying question. Do not write notebook code, do not upload or publish, do not "build it anyway just in case."** Ask, then wait for the answer. One short message, offering concrete options. **Triggers — pause for ONE short clarifying message when any is true:** - **You have no data and would have to invent it.** This is the most important trigger and the easiest to rationalize away. "I'll just use realistic synthetic data, no external API needed" is **exactly the failure mode** — do not do it. Either offer to pull real data from a named public source (you have a bash tool with network — see §2), or ask the user to attach theirs, or ask whether a clearly-labelled synthetic *demo* is what they want. Let them choose; do not choose "fabricate" for them. - **The scope is ambiguous.** "All races" — every edition of one Grand Prix, or every Grand Prix on the calendar? "Last 5 years" of what? State the concrete readings and let the user pick. A wrong scope guess can burn an entire build on the wrong dataset. - **There are several defensible ways to model the question.** "What drives overtaking?" could mean a physics heuristic, an empirical regression on real results, or historical counts. Name 2–3 framings, say which you'd pick and why, and let the user steer. This is the single highest-value thing you can do — early users consistently call it the difference between "cool demo" and "actually useful." **When you do proceed on an assumption** (because none of the above tripped), state the consequential interpretation in one line up front — "Building for all 24 Grands Prix of the 2024 season; say the word for a different scope" — so a wrong guess costs one sentence to redirect, not a whole rebuild. Restating the *model you already chose* is not alignment. "Here's the hierarchical model I fit" answers nothing if the user hasn't agreed that a model is what they wanted. Confirm the goal, not your output. **Worked example — the right first response to an under-specified ask:** > **User:** "Build me a marimo notebook analyzing F1 driver overtaking at corners." > > **You (no data, ambiguous scope, multiple framings → ask, don't build):** > "Happy to build that. Two quick things so I build the right one: > 1. **Data** — I can pull real results from a public source (e.g. the Ergast/ > Jolpica F1 API or OpenF1), or you can attach a dataset. Which do you prefer? > 2. **Scope & angle** — all 2024 GPs, a specific circuit, or multiple seasons? > And are you after *where* passes happen (corner-by-corner), *who* overtakes > most, or *what track features* drive overtaking? I'd start with corner-level > counts from real results — say the word and I'll pull the data and build it." That single message is the difference between a demo and a useful tool. Note it offers to do the real work (fetch real data) — it doesn't punt the effort back to the user, it just confirms direction before spending a build. ## 2. Never fabricate data silently This is the cardinal rule. A notebook that invents its own data, then reports conclusions baked into that invention, launders your assumptions into "findings." If you hardcode `drs_corner_success = 0.85` into a generator and the notebook then concludes "DRS corners have the highest success rate," that is circular — you wrote the answer, generated data to match, and read it back as a discovery. A data scientist will (rightly) distrust everything you produce after seeing this once. **You have two ways to use a library outside the baked set, and neither is fabrication.** The notebook runtime ships `marimo`, `pandas`, `numpy`, `scipy`, `scikit-learn`, `matplotlib`, `pymc`, and `arviz` by default. For anything else (e.g. `fastf1`): - **Declare it in a PEP 723 header** — the notebook installs it into its own isolated env (see "Declaring extra dependencies" below). Use this when the notebook itself needs the library's *code* to run. - **Or fetch the data in bash** — your **bash tool has full network access and can install anything**. Fetch and clean the real data with whatever libraries you need, then mint an attachment upload URL (`create_attachment_upload_url`) and curl the result up; the notebook reads `data/<name>`. Use this when you only need the *data* a library produces — especially for heavy or slow fetches you don't want re-run on every reactive re-render. Either way, getting real data is almost always possible; reach for it first. **Rules, in priority order:** 1. **Prefer real data.** Fetch it in bash (APIs, public datasets), attach it, read it in the notebook. If you can't source it, ask the user for it. 2. **Synthetic data is allowed ONLY with explicit user consent — which you may not grant yourself.** Consent means: the user literally asked for a demo/template/mockup, OR you asked under §1 and they said yes. An open-ended request like "analyze F1 overtaking" or "visualize churn" is a request *about the real world* — it is the §1 "no data" trigger, NOT permission to synthesize. "Synthetic-but-realistic, no API needed" is you granting yourself consent; that is the banned move. When you do have consent, the synthetic data must be unmistakably labelled — and honest: - Say "this uses synthetic/illustrative data" in your chat reply **and** in the notebook's title or first cell. Not a blockquote three lines down — up front, where it can't be missed. - **Do not state any result that is merely a parameter you chose.** Synthetic notebooks demonstrate *mechanics and interactivity* — "here's how this dashboard would look and behave" — never empirical claims about the real world. Write takeaways as "this demo shows the layout/interaction," never "X causes Y" or "X ranks highest." A line like *"Key findings: hairpins have the highest overtake volume"* when you set the hairpin rate yourself is the banned circular claim — even labelling it "baked into the data model" does not make it OK. Delete it, or restate it as "the demo is configured so hairpins show the most volume — swap in real data to find the truth." 3. **When data is partial** (e.g. you have real aggregate results but model the per-item breakdown), say so plainly and at the point of use, not buried in a footnote. Label the modelled layer "estimated," not "measured." ## 3. Validate before you publish The host **runs your notebook before serving it**: it executes every cell, and if any fails the curl response is HTTP 422 carrying a list of cell errors — the broken notebook is never served. A 200 response with `url` in the JSON means every cell actually executed. When the curl returns a 422 with "notebook failed validation — cells did not execute" and a list of errors, **read them, fix the source, mint a fresh upload URL (each URL is single-use), and re-upload.** Do not surface the raw validation error to the user as if the task failed — it's yours to fix. The most common entry is `MultipleDefinitionError` (a name, often a loop variable, defined in two cells): fix it with the function-wrapping pattern below. You don't need to self-run the notebook first — the host does it for you. But if you want to catch errors before spending a publish (e.g. you're iterating fast), you can export it locally; a clean export means the cells execute: ```bash uv run --with marimo --with pandas --with numpy --with scipy \ --with scikit-learn --with matplotlib \ marimo export html /tmp/nb.py -o /tmp/nb_check.html ``` For a notebook with a PEP 723 header, check it with `--sandbox` instead so the declared deps get installed: ```bash uv run --with marimo marimo export html --sandbox /tmp/nb.py -o /tmp/nb_check.html ``` A bare `python -c "ast.parse(...)"` syntax check is **not** a substitute — it cannot see `MultipleDefinitionError` or any runtime failure. Add `--with pymc --with arviz` when the notebook uses them. Note the host's validation has a time budget: a notebook doing heavy `pm.sample` may be published without a full execution check, so still keep sampling small (below). ## Publishing: upload URL + curl, never tool arguments You do **not** paste notebook source or data into a tool argument. Large source truncates and ~1 MB data files can't be base64'd through a tool call at all. Instead you mint a **one-time upload URL** and `curl` your file to it from bash — the bytes go straight to the host, never through the model. 1. **Get the `.py` into a sandbox file.** Either author it incrementally with write/edit and then `read` it back to confirm it's complete, or `curl` it from an origin. Never try to emit the whole notebook in one shot. 2. **Mint the upload URL:** `create_notebook_upload_url(slug="churn")` returns `{upload_url, slug, upload_expires_at}`. The URL is good for ~5 minutes and is single-use — use it promptly, and mint a fresh one for every retry. 3. **Upload:** ```bash curl -sS -X PUT --data-binary @nb.py "<upload_url>" ``` The curl response is JSON carrying the live `url` — share that. On a 422 it carries the failing cells; fix them, mint a fresh URL, re-upload. Slugs must match `[A-Za-z0-9_-]{1,32}` and not start with `-`. Use a short human-readable name (`"churn"`, `"mmm-prior-check"`) — the server namespaces it per user, so two users picking the same slug never collide. **Pass the same slug to re-upload in place**: the URL stays stable across iterations, which keeps the user's browser tab working. Re-uploading restarts the kernel, so in-browser state (filter selections, scroll position) resets. ### Precompute, never sample live A permanent blog runs a **real kernel per reader, and every cell executes on page load.** A cell that calls `pm.sample(...)` makes *every visitor* wait minutes, and concurrent readers each spawn their own sampler — which exhausts host memory. So do all heavy computation **offline, before publishing**: 1. Run the expensive work once in bash and save the artifact: ```python idata = pm.sample(2000, tune=1000) idata.to_netcdf("posterior.nc") ``` 2. Attach it under the **same slug** you'll publish under (see below). 3. In the notebook, **load** it and do only cheap work: ```python import arviz as az idata = az.from_netcdf("data/posterior.nc") # cheap # interactive widgets explore the posterior — no resampling ``` A blog that re-samples on load is a broken blog. The same advice makes a scratch notebook pleasant rather than painful. ### Writing a blog worth keeping - **Write it as an article, not a code dump.** Lead with prose (`mo.md(...)`), interleave figures and interactive widgets, read top-to-bottom. - **Keep every cell cheap** — loads, slicing, plotting, `arviz` over the precomputed `InferenceData`. Reactive widgets recompute plots from data already in memory, never refit models. - **Use `width="medium"`** in the marimo app config, not `"wide"`. ## Declaring extra dependencies (PEP 723) To use a library outside the baked set, put a PEP 723 script header at the very top of the source (before `import marimo`). The host detects it and runs the notebook in an isolated environment with those packages installed: ```python # /// script # requires-python = ">=3.12" # dependencies = ["marimo", "fastf1", "pandas"] # /// import marimo app = marimo.App() # ... cells ... ``` - **Always list `marimo`** (and every other library you import). The isolated env *replaces* the baked one — it does not extend it — so a header that omits `pandas` while the notebook imports pandas fails validation. - **Keep the list lean.** The first publish installs these before the notebook can run. A small stack (`fastf1` + `pandas`) is a few seconds; a heavy one (e.g. `torch`) can blow the validation time budget — the notebook then ships unverified and slow to first load. - **No header → baked env.** Omit the header and the notebook runs on the fast default stack with no install step. - **Heavy runtime fetching still belongs in bash.** A header lets the notebook *import* `fastf1`, but a cell that downloads large telemetry re-runs that download on every reactive re-render and can hit the subprocess memory/CPU caps. For big or slow data, fetch once in bash and attach the result. ## Cell dataflow rules (this is the #1 source of broken notebooks) marimo runs cells as a reactive dataflow graph, not top-to-bottom like Jupyter. Three rules, if violated, make cells **silently refuse to run** even though the notebook uploaded successfully. ### Rule 1 — every name is defined in exactly one cell This includes **loop variables, comprehension variables, and lambda parameters**. A `for ax in axes:` in one cell and `for ax in other:` in another collide — *both* cells break, and so does anything downstream of them. This is the most common way a notebook ships broken. **The robust fix: wrap each cell's body in a function so its locals never leak.** Do this by default for any cell containing a loop or comprehension: ```python # ✅ CORRECT — function-local names can't collide across cells @app.cell def _(posteriors): def build_table(posts): return {d: p["mu"] for d, p in posts.items()} table = build_table(posteriors) return (table,) ``` ```python # ❌ BROKEN — `drv`/`p` leak; reused in another cell → both cells refuse to run @app.cell def _(posteriors): table = {drv: p["mu"] for drv, p in posteriors.items()} return (table,) ``` Only the names you `return` escape the function-wrapped cell — exactly what you want. Reserve module-level cell names for the values you actually export. ### Rule 2 — `return` is only legal as a cell's final statement Mid-body `return` is a SyntaxError at marimo parse time and the cell produces no output. Guard with `if/else` and return at the end: ```python # ✅ CORRECT @app.cell def _(df): result = df.mean() if not df.empty else None return (result,) ``` ### Rule 3 — the signature lists names read; the return tuple lists names defined ```python @app.cell def _(mo, df): # ← names READ from upstream cells chart = df.plot() return (chart,) # ← names EXPORTED to downstream cells ``` Forget a name in the return tuple and downstream cells can't see it. List a name in the signature that no upstream cell exports and the cell errors. ## Data attachments Use `create_attachment_upload_url(slug, name)` to get a one-time upload URL for a data file, then `curl` the file to it. The file becomes readable from inside the published notebook as `data/<name>` — always that path, regardless of how it arrived. Pass the **same `slug`** to `create_attachment_upload_url` and `create_notebook_upload_url` to bind them. Different slugs are different workspaces with isolated data directories; a notebook published under one slug cannot see another's attachments. ```python import pandas as pd df = pd.read_csv("data/sales.csv") ``` Attachment data lives and dies with its workspace: on a scratch notebook it goes when the TTL reap does. If a user expects long-term storage, tell them this isn't the right tool. Per-attachment cap is 10 MiB (operator-configurable). Larger files: ask the user to subsample or aggregate before sending. Attach and publish share a single per-principal hourly rate-limit budget, so a loop that attaches → publishes → attaches → publishes burns the budget twice as fast as one that publishes alone. If `create_attachment_upload_url` raises a tool error containing "not configured" or "rate limit", surface that to the user — don't retry blindly. ### Worked example — a file the user attached in chat The attachment reaches you as an `[attachment]` line carrying a signed URL, not as a file on disk. Fetch it, then attach it to the workspace: ``` # User (with sales.csv attached): "load this CSV and plot column A" # [attachment] `sales.csv` (1024 bytes), uploaded by the user with this message. # Signed Discord CDN URL, expires ~24h: https://cdn.discordapp.com/... — curl it # to disk, then read it. To use it in a notebook you publish, upload it via the # create_attachment_upload_url tool. ``` ```bash curl -sS "<the signed URL>" -o /tmp/sales.csv # create_attachment_upload_url(slug="sales-explore", name="sales.csv") -> {upload_url, ...} curl -sS -X PUT --data-binary @/tmp/sales.csv "<upload_url>" # Write the notebook (it does pd.read_csv("data/sales.csv")), verify it, then: # create_notebook_upload_url(slug="sales-explore") -> {upload_url, ...} curl -sS -X PUT --data-binary @nb.py "<upload_url>" ``` The same shape is the antidote to fabrication: fetch real data in bash with whatever library you like, attach the result, and let the notebook read it. ## PyMC / ArviZ notebooks The runtime ships **PyMC 5.x and ArviZ 0.x** (pinned — do not assume PyMC 6 or ArviZ 1.x entry points; use the 0.x surface like `az.plot_posterior`, `az.plot_trace`, `az.summary`). Bayesian work is a first-class use of this tool,
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub