Minimal ML experiment platform wrapping Azure ML, Vertex AI, and Vercel Sandbox
Project description
NUCL
Minimal ML experiment platform. 2,000 lines of code.
NUCL wraps Azure ML, Vertex AI, and Vercel Sandbox behind a unified CLI and web dashboard. No servers to manage, no databases to maintain, no collectors to deploy. Every feature delegates to a managed service.
nucl run --name "vision/resnet-50-v2" --script train.py --gpu-type t4
nucl ps vision/
nucl log vision/resnet-50-v2 -f
nucl pull vision/resnet-50-v2
Architecture
graph LR
subgraph Clients
CLI["CLI (Python)"]
Web["Web Dashboard"]
end
subgraph Vercel
API["Next.js API Routes"]
Auth["Clerk (auth + API keys)"]
end
subgraph Sandbox["Vercel Sandbox (Python 3.13)"]
AzureSDK["azure-ai-ml SDK"]
VertexSDK["vertex AI SDK"]
end
subgraph Platforms
AzureML["Azure ML"]
VertexAI["Vertex AI"]
SandboxRun["Sandbox (CPU)"]
end
CLI -- HTTP --> API
Web -- HTTP --> API
API --> Auth
API -- "job submission" --> Sandbox
API -. "read ops (list, logs, cancel)" .-> Platforms
AzureSDK --> AzureML
VertexSDK --> VertexAI
Sandbox --> SandboxRun
Job submission spins up a short-lived Vercel Sandbox with Python 3.13 and uses the official cloud SDKs to upload code and create training jobs. Read operations (list, logs, cancel) use direct REST API calls.
Three platforms:
| Platform | GPU | Use case |
|---|---|---|
| Azure ML | Yes | Production training on Azure |
| Vertex AI | Yes | Production training on GCP |
| Sandbox | No (CPU) | Quick tests, no cloud account needed |
Setup
Web (Vercel)
cd web
bun install
cp .env.example .env.local # fill in values
bun dev
Required environment variables:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_...
CLERK_SECRET_KEY=sk_...
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
ENCRYPTION_KEY=<openssl rand -hex 32>
VERCEL_TOKEN=...
CLI
cd cli
uv sync
uv run nucl --help
Or install globally:
uv tool install .
nucl --help
CLI Commands
Auth
nucl auth login # Browser-based sign-in
nucl auth logout
nucl auth status
Teams (Clerk Organizations)
nucl team show # Current team info
nucl team set <org-id> # Switch team
nucl team config azure # Set Azure credentials for team
nucl team config vertex # Set Vertex credentials for team
Jobs
nucl run --name "project/experiment" --script train.py --gpu-type t4
nucl ps # List all experiments
nucl ps project/ # Filter by folder
nucl log <job-id> -f # Stream logs
nucl stop <job-id>
nucl pull <job-id> ./outputs # Download results
Models
nucl model ls
nucl model pull <name>
HPO
nucl hpo run config.yaml
Experiment Organization
Experiments use path-based naming with / as the folder separator:
lung-cancer/detection/yolov9-baseline
lung-cancer/detection/yolov9-augmented
lung-cancer/segmentation/unet-v1
breast-cancer/screening/resnet-50
Filter by prefix with nucl ps lung-cancer/detection/. The web dashboard renders folder paths with a muted prefix and bold experiment name.
Cloud Credentials
Credentials are stored per-team using Clerk Organization privateMetadata, encrypted with AES-256-GCM. They never touch the CLI or browser -- only the server-side API routes decrypt them to submit jobs.
An org admin configures credentials once via the Team Settings page or nucl team config. All team members use them.
In-Job Logging
NUCL does not ship a custom SDK. Use MLflow directly in your training scripts:
import mlflow
mlflow.log_param("learning_rate", 0.001)
mlflow.log_metric("accuracy", 0.95)
mlflow.log_artifact("model.pth")
Both Azure ML and Vertex AI natively support MLflow.
Tech Stack
| Layer | Technology |
|---|---|
| CLI | Python 3.11+, Click, httpx |
| Web | Next.js 16, React 19, TypeScript 6 |
| UI | shadcn, Tailwind CSS 4, TanStack Table |
| Data fetching | TanStack Query 5 |
| Auth | Clerk 7 (Organizations, API keys) |
| Job submission | Vercel Sandbox (Python 3.13) |
| Encryption | AES-256-GCM |
| Package management | uv (Python), Bun (JS) |
Tests
# Web (158 tests)
cd web && bunx vitest run
# CLI (94 tests)
cd cli && uv run pytest -v
What NUCL Does Not Do
NUCL delegates everything it can. It does not:
- Provision VMs (cloud platforms do this)
- Collect logs (cloud platforms do this)
- Manage GPU quotas (cloud platforms do this)
- Handle preemption (cloud platforms do this)
- Build Docker images (use platform environments)
- Provide an in-job SDK (use MLflow)
- Run a backend server (Vercel handles this)
- Manage a database (Clerk handles credentials, no DB needed)
License
Internal use only.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nucl-0.6.0.tar.gz.
File metadata
- Download URL: nucl-0.6.0.tar.gz
- Upload date:
- Size: 19.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0f7597aa2d9fc61a9e88c06f59ba58d0074d016249d3a2d8e36efdb430a92d9f
|
|
| MD5 |
f9e0842e8ed33099160b0ecfd8c64b88
|
|
| BLAKE2b-256 |
4357a021bb91e6a04fd4ac0eec8f5fbdfb3b001f5a99e79d87c8fce996072178
|
Provenance
The following attestation bundles were made for nucl-0.6.0.tar.gz:
Publisher:
publish-cli.yml on lunit-io/nucl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nucl-0.6.0.tar.gz -
Subject digest:
0f7597aa2d9fc61a9e88c06f59ba58d0074d016249d3a2d8e36efdb430a92d9f - Sigstore transparency entry: 1306720947
- Sigstore integration time:
-
Permalink:
lunit-io/nucl@be8e8315f52b2c4afd6051cae57721a34acbc03a -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/lunit-io
-
Access:
internal
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-cli.yml@be8e8315f52b2c4afd6051cae57721a34acbc03a -
Trigger Event:
push
-
Statement type:
File details
Details for the file nucl-0.6.0-py3-none-any.whl.
File metadata
- Download URL: nucl-0.6.0-py3-none-any.whl
- Upload date:
- Size: 13.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6611af75ba5b674157809b8563ea6d0b39d4f17364c93dd350599db31df51001
|
|
| MD5 |
f563300d0b4fc8c97892c2f6527db20c
|
|
| BLAKE2b-256 |
78fdecc025fe72863914de1a30f6748f81fa49bbebc5af0f91cc063ca66442f0
|
Provenance
The following attestation bundles were made for nucl-0.6.0-py3-none-any.whl:
Publisher:
publish-cli.yml on lunit-io/nucl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nucl-0.6.0-py3-none-any.whl -
Subject digest:
6611af75ba5b674157809b8563ea6d0b39d4f17364c93dd350599db31df51001 - Sigstore transparency entry: 1306720980
- Sigstore integration time:
-
Permalink:
lunit-io/nucl@be8e8315f52b2c4afd6051cae57721a34acbc03a -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/lunit-io
-
Access:
internal
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-cli.yml@be8e8315f52b2c4afd6051cae57721a34acbc03a -
Trigger Event:
push
-
Statement type: