The federated 2plot.dev docs network for Dash component libraries, with an llms.txt layer agents can actually use

I’ve been experimenting with llms.txt for Dash apps and wanted to share the approach, because I think the common pattern — a static text file that unloads your entire codebase to any scraper — misses most of what’s possible.

Full walkthrough (architecture, the gate concept, and the CI/CD side for the new components) is here:

Here’s the thing that started it: a rendered Dash layout is effectively invisible to an agent fetching the page. All that beautiful interactivity arrives as JavaScript the agent never executes. Most people treat that as a bug. I started treating it as a design feature: humans and agents want different documents anyway, so serve each one the version built for them.

The core idea: one URL, two honest renders

Every docs page on my sites serves a machine-readable twin at /<page>/llms.txt, and the same URL content-negotiates:

  • An agent (or anything sending Accept: text/markdown) gets clean markdown — the page’s prose plus the complete example source code, directives expanded, nothing truncated.
  • A browser gets a rendered viewer of that same markdown, with navigation, a network directory, and an embedded Claude instance — so the “machine view” is a first-class page, not a dead .txt.

Try it yourself in ten seconds:

# what an agent sees
curl -H "Accept: text/markdown" https://leaflet.2plot.dev/llms.txt

# what you see — same URL in a browser

https://leaflet.2plot.dev/llms.txt

And the site-level index at 2plot.dev/llms.txt maps every page plus the rest of the network, so an agent can walk the whole thing from one entry point.

It’s a network, not a site

The layer above is running across a federated set of docs apps — one hub, one satellite per component library, each on its own subdomain, all publishing the same llms.txt contract and cross-linking each other in a shared directory. An agent that lands on any one of them can discover all of them.

That network is also where the component news is. Everything below is live right now:

Library What it is Docs PyPI
dash-pannellum 2.0 360° panoramas, virtual tours, multi-res tiles and 360° video (WebGL, no plugins) pannellum.2plot.dev pip install dash-pannellum
flexlayout-dash 2.0 Docking window/tab layouts (drag, split, pop-out) flexlayout.2plot.dev pip install flexlayout-dash
dash-mui-charts 13 MUI X chart & tree components — Line, Bar, Candlestick, Pie, Scatter, Composite, Heatmap, Sparkline, LiveTrading, TreeView ×3, TimeClock muicharts.2plot.dev pip install dash-mui-charts
dash-mui-scheduler MUI X Scheduler — calendars, resource timelines, event scheduling muischeduler.2plot.dev pip install dash-mui-scheduler
dash-flows Node-graph editors built on React Flow flows.2plot.dev pip install dash-flows
dash-emoji-mart Slack-style emoji picker emojimart.2plot.dev pip install dash-emoji-mart
dash-email Compose emails as Dash components, send via Resend email.2plot.dev pip install dash-email
dash-leaflet2 Leaflet 2 maps leaflet.2plot.dev docs preview — PyPI soon
dash-improve-my-llms The package powering this whole llms.txt layer llms.2plot.dev pip install dash-improve-my-llms

The full catalogue (10 maintained libraries 8 waiting to migrate) lives at 2plot.dev/pip, and the whole docs stack is a reusable template — boilerplate.2plot.dev is the actual boilerplate app, deployed and eating its own dog food.

The parts I think are actually interesting

Docs that update themselves. The docs are dynamic and driven by the same apps that manage the components. The hub publishes a bulletin feed, and every satellite folds it into its llms.txt surfaces within 15 minutes — so when a dependency pin changes or a breaking release lands, the alert goes into the document agents read, instead of the llms.txt quietly going stale.

An authentication gate as a feature, not a wall. Pages have visibility tiers. Public tiers serve everyone. Deeper tiers serve a gate document to anonymous agents — but a signed-in user’s AI assistant can present a short-lived agent key (a signed URL parameter, capped at 30 days, revoked on logout) and get the full prose. Why a key and not a cookie? Because your assistant fetches without your cookies — a URL is the only credential that survives the copy-paste into an AI tool. The keys authorize documents only, never pages or mutations, and satellites hold no key material — they verify against the hub.

Honest bot taxonomy. Search and answer-engine crawlers (including OAI-SearchBot and Claude’s fetchers) get the markdown; scrapers that ignore robots.txt get nothing useful, because the interactive layer was never in the HTML to begin with.

The ops side. Every satellite ships on the same standard: secretless CI (the suite proves the app fails closed with zero env), a container-boot gate (build the real image, boot it, curl /healthz — because green tests from a working tree prove nothing about the image), a post-deploy battery against the live domain, and tag-driven PyPI publishing via OIDC trusted publishing, so there’s no API token stored anywhere.

Where this fits

This pairs with Plotly’s own work extending the MCP/callback story for agentic access — Dash 4.3+ ships an MCP server, and these docs apps expose their pages as MCP resources too. The llms.txt layer felt like the most neglected piece of that story: MCP answers “how does an agent call my app,” this answers “how does an agent read my docs.”

Happy to answer questions on any of it — the markdown/JS dual-render setup, the agent-key design, or any of the components. If you want this for your own component library, start from the boilerplate above; it’s the exact template every satellite in the table was built from.