marimo/skills/marimo-ml/SKILL.md
marimo reactive notebook environment with Apache Airflow + GPU-accelerated OSM/GTFS analytics + martin vector tiles + 3D terrain via MapLibre. Composes 9 layers (agent-forwarding, marimo, airflow, osm-data, maputnik, notebook-osm, debug-tools, dbus, ov) into a single pod that exposes 5 host ports and 2 MCP servers. MUST be invoked before building, deploying, configuring, or troubleshooting the marimo-ml image.
npx skillsauth add overthinkos/overthink-plugins marimo-mlInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
GPU-accelerated marimo notebook environment that composes a full analytics + visualisation stack: marimo notebooks (also acting as MCP server), Apache Airflow (LocalExecutor + SQLite), OSM data pipeline (quackosm → tippecanoe → martin), GTFS transit pipeline (gtfs-parquet), and a MapLibre style editor (maputnik). Two MCP servers are exposed (marimo for notebook diagnostics, airflow for REST-API orchestration).
| Property | Value |
|----------|-------|
| Base | nvidia (CUDA 13 / cuDNN / ONNX Runtime on Fedora 43 nonfree) |
| Platforms | linux/amd64 only (cuDF + cu130 torch are amd64-only) |
| Layers | 9 (see "Layer stack" below) |
| Ports | 5 (see "Ports + host mappings") |
| MCP servers | 2 (marimo @ container 2718, airflow @ container 19999) |
| Registry | ghcr.io/overthinkos |
| Image tag pattern | CalVer (YYYY.DDD.HHMM) |
nvidia (base — CUDA 13 / cuDNN / ONNX Runtime)agent-forwarding — SSH/GPG agent forwarding for in-container git pushmarimo — /ov-marimo:marimo-layer — pixi env (cudf-polars-cu13,
torch cu130, geopandas, quackosm, gtfs-parquet, folium, marimo,
airflow Python deps), supervisord service marimo edit … --mcpairflow — /ov-marimo:airflow-layer — 4 supervisord services
(init/scheduler/dag-processor/webserver) + the airflow-mcp wrapperosm-data — /ov-marimo:osm-data-layer — tippecanoe (built from
source), osmium, gdal, jq, martin (Rust), pmtiles CLImaputnik — /ov-marimo:maputnik-layer — MapLibre style editor
built with Vite --base=/notebook-osm — /ov-marimo:notebook-osm — data-only layer
seeding the dual-DAG OSM+GTFS notebook into ~/workspace/notebooks/debug-tools — /ov-marimo:debug-tools-layer — 49 standard
debug utilities (network/process/file/system/session)dbus — D-Bus session busov — ov CLI binary inside the containerThe image declares container ports; marimo-ml-pod deploy entry
remaps to host ports for browser reachability:
| Service | Container port | Host port | Path |
|---|---|---|---|
| marimo edit + MCP | 2718 | 22718 | / for editor, /mcp/server for MCP |
| airflow api-server | 8080 | 28080 | /api/v2/, /auth/token, /api/v2/dags/<id>/dagRuns |
| airflow MCP | 19999 | 29999 | /mcp |
| martin tile server | 3000 | 23000 | /<source>/{z}/{x}/{y} (vector tiles), /<source> (TileJSON), /catalog |
| maputnik static editor | 8000 | 28000 | / (SPA root) |
Host port 8080 is NOT used for airflow because traefik already binds it on the operator's machine. Always use 28080 for airflow from the host.
The user's browser is on the host; container-internal localhost:N
URLs do NOT resolve there. The notebook reads four env vars to
bridge the two URL spaces:
marimo-ml-pod:
target: pod
image: marimo-ml
disposable: true
lifecycle: dev
ports: [22718:2718, 28080:8080, 29999:19999, 23000:3000, 28000:8000]
env:
# Browser-bound (embedded in folium/MapLibre HTML)
- "MARTIN_PUBLIC_URL=http://127.0.0.1:23000"
- "AIRFLOW_PUBLIC_URL=http://127.0.0.1:28080"
# Notebook-internal (server-side requests in the kernel)
- "AIRFLOW_API_INTERNAL_URL=http://localhost:8080"
- "AIRFLOW_DAGS_DIR=/home/user/workspace/dags"
For cross-pod topologies (airflow in a separate pod on the shared
ov podman network), override AIRFLOW_API_INTERNAL_URL to
http://airflow-pod:8080 and ensure both pods mount the same
workspace volume at the same path.
This image publishes two MCP endpoints (registered by this plugin's
.mcp.json):
| Name | Container URL | Tool count | Skill |
|---|---|---:|---|
| marimo | http://localhost:2718/mcp/server | 10 (read-only inspection) | /ov-marimo:marimo-mcp |
| airflow | http://localhost:19999/mcp | ~70 (REST API wrappers) | /ov-marimo:airflow-mcp |
ov update --build --force-seed marimo-ml-pod
ov eval live marimo-ml-pod # → 83 passed · 0 failed · 0 skipped
End-to-end notebook test (executes all 10 cells via marimo's own export):
podman exec ov-marimo-ml-pod /home/user/.pixi/envs/default/bin/marimo \
export ipynb /home/user/workspace/notebooks/osm-monaco-viz.py \
--include-outputs --sort topological -o /tmp/notebook-run.ipynb -f
Expected outputs (verified end-to-end):
state: success~/workspace/tiles/work/monaco.parquet~/workspace/tiles/pmtiles/monaco.pmtiles~/workspace/gtfs/raw/monaco.gtfs.zip~/workspace/gtfs/parquet/circleMarker callsimage: field. ~/.config/ov/deploy.yml
marimo-ml-pod entry must include image: marimo-ml — otherwise
ov update --build fails with "deploy has no 'image:' field".--force-seed), marimo
re-persists with its session's cell order. Surgical reset:
supervisorctl stop marimo && cp /data/workspace/notebooks/osm-monaco-viz.py /home/user/workspace/notebooks/ && supervisorctl start marimo.monaco.pmtiles → martin returns 500/204 forever until
restart. The OSM DAG's reload_martin task handles this automatically;
see /ov-marimo:osm-data-layer.image: field too. Already covered above —
same point worth emphasizing./ov-marimo:marimo-layer — marimo runtime layer (pixi env, service)/ov-marimo:marimo-mcp — marimo MCP server tool catalog/ov-marimo:airflow-layer — Airflow 3.x compat findings/ov-marimo:airflow-mcp — airflow MCP server tool catalog/ov-marimo:notebook-osm — the dual-DAG OSM+GTFS notebook/ov-marimo:maputnik-layer — Vite --base=/ build pattern/ov-marimo:osm-data-layer — martin reload pattern/ov-marimo:debug-tools-layer — debug toolkit/ov-eval:eval — eval verbs used by the 83 probes/ov-core:deploy — env block authoring + cross-pod topology/ov-internals:disposable — disposable: true semanticstools
Use when authoring or modifying a charly PLUGIN — a candy with a `plugin:` block that contributes Providers (verbs/kinds/deploy-targets/steps/builders/commands), its own CUE schema, builtin (compiled-in) or external (out-of-tree git repo). Covers the unified Provider model, the per-plugin CUE-schema contract (single source → Go params for dev + schema-over-Describe RPC for runtime), the SDK, and the loader.
tools
The CUE data-validation / configuration CLI (cue), pinned to v0.16.1. Use when working with the cue candy, installing the cue binary into a box or onto a target:local dev host, or running the offline schema-vendoring pipeline that feeds charly's egress validation.
tools
CUE EGRESS validation — validating (and, where it adds value, generating) the config files charly WRITES to a system BEFORE the bytes hit disk. MUST be invoked before working on charly/egress.go, the vendored schemas under candy/plugin-egress/egress-schemas/vendor/, the ValidateEgress / registerVendoredEgressKind path, the offline `task cue:vendor` pipeline, or adding an egress schema for any written artifact (cloud-init, k8s manifests, traefik routes, runtime config, install ledger, systemd/quadlet units, ssh_config, libvirt XML).
tools
Kubernetes cluster-probe declarative check verb — the `kube:` check verb (nodes, pods, ingress, storage class, addon health, apply/delete, and arbitrary resource GETs) served out-of-process by the candy/plugin-kube plugin (vendored client-go; no external kubectl required).