ML-Dash
A simple and flexible SDK for ML experiment tracking and data storage with background buffering for high-performance training.
Features
Core Features
- Three Usage Styles: Pre-configured singleton (dxp), context manager, or direct instantiation
- Dual Operation Modes: Remote (API server) or local (filesystem)
- OAuth2 Authentication: Secure device flow; the SDK reads the token the
ml-dashCLI stores - Auto-creation: Automatically creates namespace, project, and folder hierarchy
- Upsert Behavior: Updates existing experiments or creates new ones
- Experiment Lifecycle: Automatic status tracking (RUNNING, COMPLETED, FAILED, CANCELLED)
- Organized File Storage: Prefix-based file organization with unique snowflake IDs
- Rich Metadata: Tags, bindrs, descriptions, and custom metadata support
- Simple API: Minimal configuration, maximum flexibility
Performance Features (New in 0.6.7)
- Background Buffering: Non-blocking I/O operations eliminate training interruptions
- Automatic Batching: Time-based (5s) and size-based (100 items) flush triggers
- Track API: Time-series data tracking for robotics, RL, and sequential experiments
- Numpy Image Support: Direct saving of numpy arrays as PNG/JPEG images
- Parallel Uploads: ThreadPoolExecutor for efficient file uploads
Nymph job Experiments broker (0.11.0)
The worker integration supplies DREAMLAKE_EXPERIMENT_BROKER and
DREAMLAKE_EXPERIMENT_BROKER_TOKEN to the job process. These select a local
memory-only broker; they are not DreamLake or Dash user credentials. With that
environment, Experiment() automatically uses the platform's pre-created run:
from ml_dash import Experiment
with Experiment().run as exp:
exp.params.set(lr=0.001)
exp.metrics("loss").log(step=0, loss=0.1)
exp.flush()
Closing the SDK logging scope flushes data; it does not complete or cancel the
compute task. DreamLake projects the execution receipt's terminal state onto
the experiment. Calling the job client's status mutation directly is rejected.
The returned results URL selects the exact run with one ?run= parameter.
ManagedConnection.from_job() also explicitly selects the same broker. The
broker must be literal HTTP loopback (127.0.0.1 or [::1]) with an explicit
port and no path, credentials, query or fragment. Each renewed lease goes back
through the broker, where the worker checks the live job attempt and project
authorization. Leases expire within 60 seconds; the SDK validates that expiry
and fixes the instance, project, run, workload, generation, API URL and experiment
page across renewals. The SDK reuses the existing run rather than creating one.
An explicit different prefix, another managed connection, standalone server, or custom local storage conflicts with job scope and is rejected. Partial broker environment, revocation, terminal jobs, stale attempts and changed destinations fail without falling back to standalone login, Keychain or local storage. Keep broker credentials in worker-provided process memory/environment; do not print, export into training configuration, or persist them. No long-lived user token is required in the job. The broker authorizes an exact task run, not arbitrary runs.
This integration is included in SDK 0.11.0 and requires the matching DreamLake
BFF job-session routes, Dash backend, control plane, and opt-in Nymph 0.1.28
worker broker. Installing the SDK does not upgrade an existing worker or launch
a task. Production compute execution has not been accepted in this release; the
worker-to-SDK flow is covered by local integration tests. Source-backed storage
remains outside this increment. The explicit application JobLifecycle contract
below is separate and unchanged.
DreamLake job lifecycle (explicit opt-in)
JobLifecycle reports application outcomes through the existing authenticated
DreamLake run API. It does not infer an outcome from Nymph process exit, experiment
status, inactivity, or an interpreter exit hook. Existing experiments without this
option keep their behavior.
from ml_dash import Experiment, JobLifecycle
lifecycle = JobLifecycle(
api_url="https://api.dreamlake.ai",
namespace="your-namespace",
job_id=tracked_run_id,
attempt_id=tracked_run_exec_id,
token=dreamlake_access_token, # supplied explicitly; independent of Dash auth
journal_dir="/durable/private/job-attempt-lifecycle",
)
with Experiment(prefix="your-namespace/project/run", job_lifecycle=lifecycle).run:
train()
Pass the exact existing tracked run ID and its stored execution ID; names,
experiment prefixes, and environment variables are never used to guess a binding.
Use the base URL for the API that owns /namespaces/{namespace}/runs/{job_id}.
The token must authorize that namespace and run. The journal never stores tokens.
The existing run.start(), complete(), fail(), and cancel() contract reports
running, succeeded, failed, and cancelled. Context-manager exit reports
completion only when the body returns normally; exceptions report failure.
Buffered-data flush failure keeps the SDK's existing completion-to-failure rule.
Terminal evidence is durably recorded before the unrelated Dash experiment status
request. Reporting errors propagate; they never manufacture an acknowledgment.
Use a dedicated private directory on durable local POSIX storage (mode 0700). Events and acknowledgments are atomically replaced and fsynced, and a nonblocking OS lock rejects concurrent writers. Do not put this directory on ephemeral storage or filesystems without local locking and fsync guarantees. Keep the directory after completion: it binds immutable event IDs to one exact API URL, namespace, job, and attempt. Reusing it with another binding fails before any request.
After a network error or process restart, reconstruct JobLifecycle with the same
binding and directory and call lifecycle.flush() to replay pending events. A lost
acknowledgment replays the original event ID and payload. flush() only sends
already-recorded evidence; it never creates completion. For a restarted application,
call run.start() again only if there is no recorded terminal outcome. A journal
with terminal evidence cannot begin another application execution. There is no
background sender or automatic restart hook: the caller must explicitly retry or
resume delivery. Missing terminal evidence remains unknown.
Installation
DreamLake managed Experiments (since 0.10.0)
The SDK supports ManagedConnection.from_login(org, project)
and Experiment(prefix="org/project/run", managed=connection). This uses
DreamLake project permissions and the existing DreamLake login; it does not
require another Dash organization. Matching server and CLI releases are required.
See the authoritative managed setup and Python guide.
Managed credentials are short lived, renewed in memory, and never exported into
plans. The managed transport currently supports flat run paths, parameters,
metrics, logs, lifecycle status, and root attachments up to 8 MiB. Standalone
logging remains available and unchanged.
This repo is the Python SDK — the library you import ml_dash from. The
ml-dash command line is a separate program, distributed on npm; see
The CLI moved to npm below.
SDK (this package)
| Using uv (recommended) | Using pip |
uv add ml-dash
|
pip install ml-dash
|
CLI
The CLI is distributed as @dreamlake/ml-dash on npm. Check the registry or
the original releases
for available versions. With Node.js >= 20.19:
npm install -g @dreamlake/ml-dash
Or a self-contained binary, needing neither Node.js nor Python:
# macOS / Linux
curl -fsSL https://pub-42e1dcc7de574d4a92984865fdc95f10.r2.dev/install.sh | sh
# Windows PowerShell
irm https://pub-42e1dcc7de574d4a92984865fdc95f10.r2.dev/install.ps1 | iex
On Alpine the musl builds need apk add --no-cache libstdc++.
The package is scoped because the unscoped ml-dash name is not claimable on
npm, but the command is still ml-dash. Either channel updates itself, in
place and through the channel it was installed from:
ml-dash update --check # report whether a newer release exists
ml-dash update # install it
The CLI moved to npm
Up to and including 0.6.27, pip install ml-dash also put an ml-dash
executable on your PATH. From 0.7.0 it does not: the command line has been
rewritten in TypeScript and ships on its own — as the npm package
@dreamlake/ml-dash or the standalone installer above — so it installs and
updates without touching your training environment's Python dependencies.
Upgrading the Python package to 0.7.0 removes the old executable; install the
new CLI alongside it.
Nothing about the SDK changes. pip install ml-dash is still how you get
ml_dash, and every Experiment, files, tracks, and storage API is
untouched.
| Before | Now | |
|---|---|---|
SDK (import ml_dash) |
pip install ml-dash |
pip install ml-dash |
CLI (ml-dash login, upload, …) |
pip install ml-dash |
npm install -g @dreamlake/ml-dash, or the standalone installer |
The command names and arguments carry over, and the two share a credential
store: the CLI writes its token to the same OS keyring entry (and the same
~/.dash/ fallback files) that ml_dash.auth.token_storage reads, so
ml-dash login still authenticates your Python code and an existing valid
login is normally picked up as-is. Re-run ml-dash login if it is not.
Command behaviour is not identical — the rewrite changes and fixes a number of things — and the CLI's own release notes are where those differences are described.
If you were importing ml_dash.cli or ml_dash.cli_commands.* directly from
Python — an unsupported path, but a real one — those modules are gone. Shell
out to the ml-dash binary instead.
Releases up to 0.6.27 keep the old Python CLI; pin ml-dash<0.7 only if you
cannot install the new one yet.
Quick Start
1. Authenticate (Required for Remote Mode)
npm install -g @dreamlake/ml-dash # one time; requires Node.js >= 20.19
# (or use the standalone installer above)
ml-dash login
This opens your browser for secure OAuth2 authentication. Your credentials are stored securely in your system keychain — where the Python SDK reads them.
2. Start Tracking Experiments
Option A: Use the Pre-configured Singleton (Easiest)
from ml_dash.auto_start import dxp
# Start experiment (uploads to https://api.dash.ml by default)
with dxp.run:
dxp.log("Training started", level="info")
dxp.params.set(learning_rate=0.001, batch_size=32)
for epoch in range(10):
loss = train_one_epoch()
dxp.metrics("train").log(loss=loss, epoch=epoch)
Option B: Create Your Own Experiment
from ml_dash import Experiment
with Experiment(
prefix="alice/my-project/my-experiment",
dash_url="https://api.dash.ml", # token auto-loaded
).run as experiment:
experiment.log("Hello!", level="info")
experiment.params.set(lr=0.001)
Option C: Local Mode (No Authentication Required)
from ml_dash import Experiment
with Experiment(
project="my-project", prefix="my-experiment", dash_root=".dash"
).run as experiment:
experiment.log("Running locally", level="info")
New Features in 0.6.7
🚀 Background Buffering (Non-blocking I/O)
All write operations are now buffered and executed in background threads:
with Experiment("my-project/exp").run as experiment:
for i in range(10000):
# Non-blocking! Returns immediately
experiment.log(f"Step {i}")
experiment.metrics("train").log(loss=loss, accuracy=acc)
experiment.files("frames").save_image(frame, to=f"frame_{i}.jpg")
# All data automatically flushed when context exits
Configure buffering via environment variables:
export ML_DASH_BUFFER_ENABLED=true
export ML_DASH_FLUSH_INTERVAL=5.0
export ML_DASH_LOG_BATCH_SIZE=100
📊 Track API (Time-Series Data)
Perfect for robotics, RL, and sequential experiments. Supports timestamp inheritance for synchronized multi-modal data and track slicing for efficient querying.
with Experiment("robotics/training").run as experiment:
for step in range(1000):
# First track auto-generates timestamp
experiment.tracks("robot/position").append(
step=step,
x=position[0],
y=position[1],
z=position[2]
)
# Other tracks inherit same timestamp with _ts=-1 (synchronized!)
experiment.tracks("camera/left").append(width=640, height=480, _ts=-1)
experiment.tracks("robot/velocity").append(vx=0.1, vy=0.0, _ts=-1)
experiment.tracks("sensors/lidar").append(ranges=[1.5, 2.0], _ts=-1)
experiment.tracks.flush()
# Query synchronized data with floor-match timestamp queries
pose_slice = experiment.tracks("robot/position").slice(0.0, 10.0)
# Single timestamp query
entry = pose_slice.findByTime(5.5) # Returns entry at timestamp 5.0
# Batch queries
entries = pose_slice.findByTime([1.0, 3.5, 7.0]) # Returns list of 3 entries
# Iterate through time range
for entry in pose_slice:
print(entry["timestamp"], entry["x"], entry["y"])
See the Tracks guide for complete documentation.
🖼️ Numpy Image Support
Save numpy arrays directly as images (PNG/JPEG):
import numpy as np
with Experiment("vision/training").run as experiment:
# From MuJoCo, OpenCV, PIL, etc.
pixels = renderer.render() # numpy array
# Save as PNG (lossless)
experiment.files("frames").save_image(pixels, to="frame.png")
# Save as JPEG with quality control
experiment.files("frames").save_image(pixels, to="frame.jpg", quality=85)
# Auto-detection also works
experiment.files("frames").save(pixels, to="frame.jpg")
See CHANGELOG.md for complete release notes.
Development Setup
Installing Dev Dependencies
To contribute to ML-Dash or run tests, install the development dependencies:
| Using uv (recommended) | Using pip |
uv sync --extra dev
|
pip install -e ".[dev]"
|
This installs:
pytest>=8.0.0- Testing frameworkpytest-asyncio>=0.23.0- Async test supportruff>=0.3.0- Linter and formattermypy>=1.9.0- Type checker
The Sphinx toolchain is gone with the docs tree — see Documentation below.
Running Tests
| Using uv | Using pytest directly |
uv run pytest
|
pytest
|
Documentation
The docs live at docs.dash.ml and are built from
dreamlake-ai/dash-workspace
(docs/), not from this repo. The Sphinx tree that used to sit in docs/ and
the Docusaurus site in website/ have both been retired — they had drifted
apart from each other and from the SDK, and one of them documented _ts as a
required argument that raises ValueError when it is optional and raises
ConfigurationError.
Doc changes therefore go to that repo. It runs a check:sdk guard that
verifies every python snippet against the installed ml_dash, so a signature
change here will fail the docs build there rather than sit unnoticed.
The skills/dash-docs/ directory is a vendored copy of the generated agent
skill. Never edit it by hand:
make sync-skill # refresh from docs.dash.ml
make check-skill # fail if the vendored copy is stale
For maintainers, to build and publish a new release: uv build && uv publish
(this publishes the SDK only — the CLI is released from its own npm repo).
Metadata
Release files for ml-dash 0.11.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ml_dash-0.11.0.tar.gz | 105.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ml_dash-0.11.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 219.2 kB
Release files / ml_dash-0.11.0.tar.gz
| Download URL | ml_dash-0.11.0.tar.gz |
|---|---|
| Size | 105.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d9e95a22776cdceee2d31798e3a49adeb9f2d106950645cded5cf3fd93139659
|
|
BLAKE2b-256 checksum How to use checksums |
269f322fe3840c2f28e1eb1e1c56a77d3f89fc04aa6aadb1f16fd3ae41ac1c4c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / ml_dash-0.11.0-py3-none-any.whl
| Download URL | ml_dash-0.11.0-py3-none-any.whl |
|---|---|
| Size | 113.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1bda96fbb79dfda2927f7fb108247b7b084e9ac88f6646aad7662fcd10e657d1
|
|
BLAKE2b-256 checksum How to use checksums |
6078990c54be434d0a2d77c29e8c0ee7c211e564b2f52ff608d922445761e9db
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency log