One Rust binary · MCP server · publishable notebooks

Notebooks your AI can write. Pipelines it can run.

Point Claude, Cursor, or any MCP agent at Loom and get back a runnable, explained notebook — the SQL, the Python/TypeScript, the charts, the prose — backed by a real workflow run. Workflow DAGs, serverless functions, streaming ingestion, and embedded SQL in one process. Tasks run in ~1 ms on warm workers.

$ docker run -p 7420:7420 -e LOOM_PASSWORD=change-me ghcr.io/danielmcsheehy/rust-orchestrator
run 4f2a91 · etl-pipeline · running
extractpython · 96ms transformtypescript · 41ms scorepython · running loadqueued SELECT zone, AVG(value) FROM telemetry · 248ms 1.8M rows · embedded Polars ↑
▸ score │ loom.query() aggregating on the Rust engine…
0
serverless invoke p50
0
3-task workflow, end-to-end
0
streaming NDJSON ingest
0
SQL over 1.8M rows
0
invocations @ 32-way
0
RAM at idle
Built by agents, on Loom

Six teaching labs. Zero clicks in the UI.

Live demo: every lab below is published read-only on a real Loom instance — open one and scroll. Outputs are the saved results of the run; nothing can be executed or edited without signing in.

Each notebook below was produced end to end by an AI agent talking to Loom's MCP server: it wrote a Python + TypeScript DAG, ran it, ingested the results, queried them with SQL, and wrote the explanation and charts. The numbers are from the real runs.

A notebook section: the chain rule written out step by step in markdown, a Python cell that does the five steps on one sample, and the resulting table checking the analytic gradient against a finite difference
Math, then code. The backprop lab walks the chain rule step by step in markdown, then does the same five steps in a Python cell and checks ∂L/∂W against a finite difference — absolute error 0.
Recommendations lab: the everyday question, a SQL cell, a radar chart of hit rate by music taste for two recommenders, and the plain-English answer
Question → chart → answer. The recommendations lab asks who gets good suggestions, runs one SQL cell, and reads the radar: taste-twins win everywhere except for pop fans.

No matplotlib, no image uploads — charts are the console's own SVG renderer over the data grid behind each cell. Publish the notebook and the outputs ship with it, read-only.

See it move

Write a cell. Run it. Pick a chart.

A Python cell returning a DataFrame becomes a data grid with column profiles; the Chart tab opens the controls — nine marks, colour by column, min/avg/max lines, per-series colours — and the spec saves with the notebook.

Typing a Python cell that returns a pandas DataFrame, running it, then flicking through bar, line, area, scatter, donut and radar charts

Pipelines you can actually see.

Every workflow is a card with its task graph, trigger and last run. Open one and click a task for its code, params and the selected run's result; open a run for a Gantt timeline and task rows that expand into tables, charts and logs.

Workflows page: one card per workflow with a mini task graph, trigger, last run and success rate
Workflows · the DAG, trigger, last run and success rate — before you click.
Workflow detail with the full-width task graph and a task open in the side panel showing its code and the run's result
Task panel · click a node for its code, retries, timeout and the run's result as a table or chart.
Everything included

Five products. One process.

The orchestrator, the serverless runtime, the analytics engine, the notebook, and the agent interface usually arrive as five vendors and a Kubernetes cluster. Loom ships them as a single binary with SQLite inside.

Polyglot DAGs

Python, TypeScript, and JavaScript tasks in one graph — a Python task's output becomes a TS task's input. Parallel layers, retries, timeouts, cycle-validated at deploy.

Warm pools or sandboxes

Interpreters stay alive between tasks — 36 ms → 1.2 ms per Python invoke. When trust matters more than latency, flip one env var to run each task in a locked-down container or a microVM (Kata / Firecracker).

Streaming everything

Run states, task logs, and metrics broadcast over SSE. Ingestion consumes NDJSON chunk-by-chunk at ~780 MB/s — gigabyte payloads never sit in memory.

SQL on a Rust engine

Every dataset is a table. Embedded Polars aggregates 1.8M rows in ~250 ms — from the API, the SDKs, notebook cells, or loom.query() inside any task. Postgres, ClickHouse & chDB connectors included.

Serverless functions

Deploy a handler in either language, invoke over HTTP with JSON or stream logs + result via SSE. ~775 invocations/s on a dev container.

Notebooks

Observable-style executable documents: markdown with images, code cells in any runtime with data-aware autocomplete (it knows the fields of the cell above), SQL cells, and nine chart marks — bar, line, area, scatter, donut, histogram, radar, min/avg/max bands — with a colour picker. Publish read-only with one click.

Agent-native (MCP)

A built-in Model Context Protocol server with 15 tools: agents compose workflows, execute code, query data, create notebooks, and read results — a self-correcting loop out of the box. claude mcp add loom … and go.

One password. Publish what you want.

Set LOOM_PASSWORD and the API, MCP, and console are locked. Hit Publish on a notebook and anyone with the link can read it — cells, outputs, charts — but nothing public can execute or edit. Unpublish any time.

Zero infrastructure

One 67 MB binary, one SQLite file (WAL), one volume. 16 MB RAM idle, 0% CPU. Deploys on a $4 VPS, docker compose, or Coolify in three clicks.

Measured, not marketed

Benchmarks

Every number below was measured on a modest shared dev container — release build, real CPython 3.11 / Node 22 workers, real HTTP round-trips. Reproduce them from the repo.

Warm pool vs. cold spawn — invoke latency (p50)

same code, same machine; the pool reuses live interpreters
Python · warm
1.2 ms
Python · cold
36 ms
TypeScript · warm
1.6 ms
TypeScript · cold
124 ms
29–78× faster per task. A 3-task mixed-runtime workflow completes end-to-end in 7.4 ms — vs 143 ms before pooling.

Data path — one process, no warehouse

118 MB / 1.8M-row NDJSON dataset, embedded Polars engine
Ingest 1.8M rows
0.15 s
GROUP BY · 4 zones
248 ms
Filter + top-5
207 ms
Control-plane call
0.6 ms
Sustained ~775 invocations/s at 32-way concurrency · ~780 MB/s streaming ingest · server peaks at 205 MB RSS during the heaviest query.
Developer experience

Define in your language. Run on Rust.

pipeline.pypython sdk · zero deps
from loom_sdk import LoomClient, Flow, task

@task
def extract(params, inputs):
    return {"values": list(range(params["n"]))}

@task(depends_on=[extract], retries=2)
def total(params, inputs):
    return sum(inputs["extract"]["values"])

client = LoomClient("http://localhost:7420")
wf = client.deploy(Flow("sum", params={"n": 100},
                        tasks=[extract, total]))
run = client.trigger(wf["id"], wait=True)
# run["state"] == "completed" · 7 ms later
inside any taskrust engine bindings
import loom   # injected into every worker

def handler(params, inputs):
    rows = loom.query("""
      SELECT zone, AVG(value) AS avg_v
      FROM telemetry GROUP BY zone
    """)  # 1.8M rows, ~250ms, in Rust
    loom.ingest("zone-aggregates", rows)
    return {"zones": len(rows)}

# agents drive the same surface over MCP (15 tools):
$ claude mcp add --transport http loom http://localhost:7420/mcp \
    --header "Authorization: Bearer $LOOM_PASSWORD"
> "Build a lab that teaches gradient descent: data,
>  three learning rates, loss curves, explain the math."
# → workflow + run + datasets + a published notebook
Ship it

Your infra bill will think
it's a rounding error.

# everything — console, API, workers, SQL engine: docker run -p 7420:7420 -v loom-data:/data -e LOOM_PASSWORD=change-me \
  ghcr.io/danielmcsheehy/rust-orchestrator # or Coolify: New Resource → /docker-compose.coolify.yml · or git clone && docker compose up --build
Star it on GitHub →