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.

Zur Installation springen

Quellinformationen

Repository
pymc-labs/daimon
Letzte Quellaktivität
9. August 2026 um 03:52
Erkannte Sprache von SKILL.md
Englisch
Sterne
15
Forks
5

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
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,
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen