HUST'26 · SC26 · Chicago · November 15, 2026

$ module load uv$ srun uvx thing

uv-manager

A Transparent Wrapper for Safe, Architecture-Aware Python Environments on Heterogeneous HPC Clusters

Geoffrey Lentner · Purdue University
Lev Gorenstein · Globus – University of Chicago

Globus
Purdue University, Rosen Center for Advanced ComputingPurdue University, Rosen Center for Advanced Computing
SC26 Chicago, hpc unites.

Common ground

Moving away from Anaconda

CaRCC white paper · June 2026

Addressing the Challenges Posed by Anaconda's New Licensing Model

Alber, Creech, D'Auria, Gorenstein, Joshi, Kaufman, Lentner, Raney, Romero, Warriner

doi:10.5281/zenodo.20694933

Revised terms made the licensing status of Anaconda's default channels something each institution had to establish.

The working group documented, across member institutions:

compliance scopebudget exposureworkflow disruption

uv is one of the places that migration went.

Common ground

Many ways to build Python environments on HPC

Built by the center

Built by the user

Build and curate

Spack · EasyBuild · modules · curated conda

The stack cannot keep pace with the long tail, and curation never ends.

Bring your own

containers · personal conda · pip --user

Users learn a second toolchain, or fill their home quota.

Built per job

Too slow, until now.

Common ground

uv makes a per-user environment cheap

One binary

no bootstrap interpreter

It ships as one self-contained binary.

Fast

built per job

It builds an environment inside a job's own setup.

Locked

uv.lock · PEP 751

A lockfile from March resolves the same way in September.

Brings its own Python

uv python install

It installs the interpreter a project asks for.

Inline scripts

PEP 723 · uv run

A script declares its dependencies and runs with them.

Tools on demand

uvx · uv tool install

uvx runs a command-line tool with no install step.

We've been recommending uv to our users, but resisted a shared module.

The driving case · Anvil MEP

Submit a function

client
anywhere · Python 3.12.8
Globus Compute
the cloud service
Anvil MEP
multi-user endpoint · Python 3.13.13
UEP
per user · 3.12.8
workers
× N · 3.12.8
from globus_compute_sdk import Executor def analyze(n):    import numpy as np    return np.random.default_rng(n).standard_normal(10**6).std() with Executor(endpoint_id=ANVIL_MEP,              user_endpoint_config={"requirements": "numpy"}) as ex:    print(ex.submit(analyze, 42).result())
1

It runs anywhere with the SDK and a Globus login.

2

The requirements travel with the task.

3

The result returns as a future.

The driving case · Anvil MEP

Automatic environment provisioning

client
anywhere · Python 3.12.8
Globus Compute
the cloud service
Anvil MEP
multi-user endpoint · Python 3.13.13
UEP
per user · 3.12.8
workers
× N · 3.12.8
{% set major, minor, micro = user_runtime.python.version_tuple %}ENV_HASH=$(echo {{ requirements }} | md5sum | awk '{print $1}')ENV_PATH="$ENV_SITE/py{{ major }}.{{ minor }}.{{ micro }}-$ENV_HASH"uv venv "$ENV_PATH" --python {{ major }}.{{ minor }}.{{ micro }}source "$ENV_PATH/bin/activate"uv pip install globus-compute-endpoint=={{ sdk_version }}uv pip install -r <(echo -e {{ requirements }})
1

The client's Python and SDK versions arrive with the task.

2

The same requirements string, hashed, names the environment.

3

uv builds it on first use; later calls reuse it.

The driving case · Anvil MEP

The good, the bad, and the ugly

Our own slide, September 2026

It installed uv to ~/.local/bin.

It did not guard against user configuration.

It filled /home without bound.

It met x86_64 login nodes and aarch64 compute.

Three assumptions a cluster breaks

A1

Home has room for the cache. Ours has a 25 GB quota.

A2

A person with a shell profile is present.

A3

Every node shares one CPU architecture.

Exec format error

The architecture case fails deep in the job log, after the allocation is charged.

Other centers

Home vs scratch storage

HomeScratch
CenterQuota (GB)InodesPurge (days)Keyed onMore than one ISA
Anvil and RCAC community clusters251–2 M30–60atime, mtimeyes
NERSC4010 M56+atime·
TACC Frontera25none10atimeno
CSCS Alps501 M14, 30atimeyes
LUMI202 M··no
Sherlock1520 M90last data write·

From each center's published user documentation, August 2026. A dot marks a value the center does not publish. Full table and citations in the paper.

What uv-manager is

A thin layer over the real uv

uv · uvx
the user types the real name
argv[0]
one script, dispatched by the name it was called as
$UVM_ROOT
state root on capacity storage: the module's value, or the first writable scratch; never /tmp
uname -m
platform key, resolved on the node that runs the command
provision
uv for this architecture, on first use, under a lock
exec uv
export the UV_* storage variables and hand over

One shell script stands in for uv and uvx, sets up the environment for this node, and hands over to the real binary.

you still just type uv

What uv-manager is

Why put environments on scratch?

Home · small · never purged

$UVM_DATA/
├── bin/           trampolines
└── tools/<name>/  records

Two tools: 1,675 bytes, 573 per trampoline. This is the recipe.

Scratch · large · purged · one tree per architecture

$UVM_ROOT/<arch>/
├── versions/<ver>/  uv, uvx
├── cache/           UV_CACHE_DIR
├── tools/           UV_TOOL_DIR
├── python/          UV_PYTHON_INSTALL_DIR
└── bin/shims/       UV_TOOL_BIN_DIR

uv and the same two tools: 200 MB in 3,726 files. A user's tree reaches 1.2 GB and 17,798 files.

Treat the environment as a cache that a purge may evict, and keep its recipe on home.

What uv-manager is

Resolve the architecture on the node

Resolved at module load

login node
x86_64 · module load

sbatch --export=ALL

compute node
aarch64 · gets x86_64 tools
Exec format error
UV_TOOL_DIR=$SCRATCH/.uv/x86_64/tools
The modulefile fixed the architecture on the login node.

Resolved on the node

login node
x86_64 · module load uv

sbatch --export=ALL

compute node
aarch64 · appends uname -m
the job runs
with aarch64 tools
UVM_ROOT=$SCRATCH/.uv
uv-manager exports a base with no architecture, and the node completes it.

The other half

Can we repair environments on scratch?

Scratch purges each file on its own age

__init__.py core.py submodule.py tests/ RECORD purge line time since last read

What uv reports on that environment

uv tool listinstalled
uv tool upgradenothing to upgrade
uv tool installalready installed
mytoolImportError

uv trusts a .dist-info directory and never checks the files beside it. So does pip.

Our first detector read each package's RECORD, which the purge takes first. It reported OK on a stripped tqdm; with RECORD restored, missing 197 of 201 files.

The other half

What does a purge leave behind?

One environment after a purge

site-packages/
├── tqdm/
│   ├── __init__.py
│   ├── std.py
│   └── contrib/
│       └── logging.py
└── tqdm-4.66.0.dist-info/
    ├── METADATA
    └── RECORD
The directories stay: name and version survive.

The repair, in order

1

Interpreters

import json, os, ssl

A damaged interpreter reinstalls at the patch version it had.

2

Installed tools

$UVM_DATA/tools/<name>/ · home

Each package reinstalls at the version its dist-info names. A tool gone whole comes back from its record.

3

uv's cache

uv run · uvx

Only the damaged packages reinstall, and without the cache, so a damaged wheel cannot leak back in.

Checking a damaged tree took 2.5 to 4.4 s on Lustre.

The other half

Where should the repair run?

Where a repair step could runNodeAs
a line before sbatchthe login nodethe user
PrologSlurmctldthe controllerSlurm
SrunPrologthe submitting hostthe user
Prologthe compute noderoot
TaskPrologthe compute nodethe user, where the site configures it
UVM_REPAIR on the dispatch paththe compute nodethe user

Inside a job the first call checks the tree; every later call in that job costs one stat.

The other half

One rank repairs, every rank recovers

#SBATCH --nodes=2 --ntasks-per-node=64export UVM_REPAIR=1srun uvx thing
node 1 · 64 ranksnode 2 · 64 ranks
128ranks on 2 nodes, Lustre scratch
1repair, by the rank holding the lock
3 slock held
4 slongest wait

We killed the repairing rank partway through. All 127 waiting ranks timed out and named the lock and its holder. uvm clean lock removed it, and the rerun repaired once and passed on all 128 ranks.

The other half

Can installed tools survive a purge?

srun foo
on any node, in a job
trampoline
on home, never purged: 573 bytes
uname -m
the node's own architecture
UVM_REPAIR
check the tool against its record; repair once per job
exec
the tool's environment on scratch
# once, on a login nodeuv tool install foo# weeks later, after a purge, in a jobexport UVM_REPAIR=1srun foo
# $UVM_DATA/bin/foo · generated, simplifiedname=${0##*/}case ${UVM_REPAIR:-0} in0) arch=${UVM_PLATFORM:-$(uname -m)}   exec "$UVM_ROOT/$arch/bin/shims/$name" "$@" ;;esacexec uv-manager _trampoline "$name" "$@"

Two tools take 1,675 bytes on home, a trampoline and a record each. Everything else, uv included, takes 200 MB in 3,726 files on scratch.

Evidence

Benchmarks

3.23 msThe wrapper's own work before it hands over to uv.
+17.75 msA wrapped uv call took 17.75 ms longer than starting an empty shell, which took 269.5 ms on the same node.
8 msStarting an installed tool through its trampoline. The tool alone starts in 4 ms.
0.6 msAdded to every call while UVM_REPAIR is off. Once a job has checked, each call adds one stat.
17,798Files in one user's tree, 1.2 GB, with an interpreter, two tools and a warm cache.
1 of 64Of 64 processes started together on an empty tree, one installed uv, and all 64 ran the same version.

Dispatch, footprint and cold start: release 0.5.0, one x86_64 node, GPFS scratch. Trampoline and repair: release 0.8.0.

Lessons

What bit us: falling down the rabbit hole

Rebuilds drop pins

uv tool install --reinstall moved a ==4.66.0 pin to 4.70.0. Only uv tool upgrade --all --reinstall --no-cache kept it.

Locks cannot judge death

Our lock tried to decide whether a silent holder had died, and grew to a quarter of the script. Since 0.7.0 a waiter times out, names the holder, and leaves it to a person to clear the lock.

Status first

uvm status prints the node's architecture, the uv version, and every directory uv will use: state root, cache, tools, interpreters, records. Staff run it first, and it saves a round trip on every ticket.

In fairness, uv's docs warned us.

Back to the MEP

Upgrading the Anvil MEP to uv-manager

worker_init today

export PATH="$HOME/.local/bin:$PATH"if ! command -v uv &> /dev/null; then  curl -LsSf https://astral.sh/uv/install.sh | shfi

worker_init on uv-manager planned

export UVM_ROOT="$SCRATCH/.uv"export PATH="/apps/external/uv/main/bin:$PATH"export UVM_REPAIR=1

Every submitted function gets an environment that is placed, matched to the node, and repaired in the job.

The agentic era

Supercharging MCPs with uv-manager

Globus-MCP · shipped

compute_submit(    endpoint_id="<uuid>",    function_source="def analyze(n): ...",    requirements="numpy",)

Cluster MCP · proposed

python_submit(code, requirements) a Cluster MCP tool: any code, under the site's Slurm
your agent harness
the user's choice
tool server
Globus-MCP, Cluster MCP, with the site's context
site scheduler
Slurm, or a Globus Compute endpoint in front of it
uv-manager
placed, per architecture, repaired in the job
uv

At one site, users running coding agents were 19.5% of users and submitted 55.8% of jobs (Harvard FASRC, July 2026; preprint).

In production

The default uv on five Purdue clusters

$ module load uv$ uvm statusuv-manager:            0.8.0architecture:          x86_64state root:            /anvil/scratch/glentner/.uv  (from UVM_ROOT)resolved version:      uv 0.13.6 (x86_64-unknown-linux-gnu)UV_CACHE_DIR:          /anvil/scratch/glentner/.uv/x86_64/cacheUV_TOOL_DIR:           /anvil/scratch/glentner/.uv/x86_64/toolsUV_PYTHON_INSTALL_DIR: /anvil/scratch/glentner/.uv/x86_64/pythontrampoline dirs:       /home/glentner/.local/share/uvm/bin                       /anvil/scratch/glentner/.uv/bin

uv-manager 0.8.0 is the default uv module on Anvil, Gautschi, Gilbreth, Negishi and Scholar, with UVM_REPAIR set for batch jobs.

QR code for the RCAC blog post on uv-manager

Blog post

QR code for the uv entry in the RCAC application catalog

Application catalog

docs.rcac.purdue.edu

uv-manager · HUST'26

Run uvx on any node, after any purge.

uv-manager places the environment, matches it to the node, and repairs it in the job, for a person at a terminal or an agent behind an MCP server.

Geoffrey Lentner · Purdue University
Lev Gorenstein · Globus – University of Chicago

Globus
QR code for github.com/PurdueRCAC/uv-manager

github.com/PurdueRCAC/uv-manager

MIT license

Purdue University, Rosen Center for Advanced ComputingPurdue University, Rosen Center for Advanced Computing
SC26 Chicago, hpc unites.