███████████ ████ ███████████
▒▒███▒▒▒▒▒███ ▒▒███ ▒▒███▒▒▒▒▒███
▒███ ▒███ ██████ ████████ █████ ▒███ ▒███ ▒███ ██████ █████ █████
▒██████████ ▒▒▒▒▒███ ▒▒███▒▒███ ███▒▒ ▒███ ▒██████████ ███▒▒███▒▒███ ▒▒███
▒███▒▒▒▒▒▒ ███████ ▒███ ▒▒▒ ▒▒█████ ▒███ ▒███▒▒▒▒▒███▒███ ▒███ ▒▒▒█████▒
▒███ ███▒▒███ ▒███ ▒▒▒▒███ ▒███ ▒███ ▒███▒███ ▒███ ███▒▒▒███
█████ ▒▒████████ █████ ██████ █████ ███████████ ▒▒██████ █████ █████
▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒
Your autopilot for HPC job orchestration. ParslBox manages multi-application workflows (LAMMPS, VASP, ORCA, Python, Julia, custom apps) across PBS and SLURM clusters with resource-aware scheduling, dependency tracking, and fault tolerance.
ParslBox provides a CLI (pbx), a Python API, and an MCP server for AI-agent integration.
Check out a quick demo!
- Job database: Persistent SQLite tracking for job paths, resource requirements, dependencies, and status across sessions. Multiple isolated databases via
PBX_DB_PATH - Job lifecycle: add, update, filter, run with dependency support (parent IDs, parent tags)
- Built-in apps: LAMMPS, VASP, ORCA, Python, Julia — plus pluggable custom apps
- Resource-aware execution:
- Pack multiple sub-node jobs onto shared nodes (GPU or CPU)
- Run multi-node MPI jobs with exclusive node allocation
- CPU-GPU affinity-aware placement
- MPI backends: MPICH, OpenMPI, srun
- Scheduler support: PBS (
pbx qsub) and SLURM (pbx sbatch) with configurable--sched-opts - Pre-configured HPC systems: Polaris, Aurora (GPU & Tile modes), Sophia, Crux, LCRC Swing, LCRC Improv, Pinnacles-CenvalArc, Perlmutter (GPU & CPU), plus per-node launcher variants (
aurora-tile'sMpiExecLauncher,perlmutter-gpu-srun'sSrunLauncher) for large-scale (>10k worker) runs - Dynamic job discovery:
--dynamic(default) re-queries the DB for runnable jobs on every dispatch pass. New jobs matching the same--apps/--tagsfilters, and jobs the user flips back toReady/Restartfrom another terminal (viapbx update --status … <ids>), are picked up automatically — jobs re-dispatched viaRestartgo through the app'srestart()hook just like any otherRestartjob. Because each run atomically claims only what it dispatches, multiplepbx runallocations can safely share one DB in dynamic mode. Use--staticfor collect-once behavior (claim the runnable set once up front; no re-query) - Self-respawn chain:
pbx qsub --respawn Nproduces a self-perpetuating submission chain that auto-resubmits at every walltime boundary. Running jobs preempted at walltime are markedRestart; the next link calls each app'srestart()hook lazily as eachRestartjob is dispatched, so apps can decide how to resume. Seedocs/pbx-run-details.md - Fault tolerance: Node health tracking, quarantine, and auto-recovery
- Script-driven status reporting: Python/Julia scripts report success/failure via
report_status()utility - Python API and MCP server for programmatic and AI-agent integration
- Rich CLI output with tables and colors
Requirements:
- Python >= 3.11, < 3.14
- Parsl >= 2025.9.8
Clone the repo first:
git clone https://github.com/fbhuiyan2/parslbox.git
cd parslboxThen pick a Python environment manager below. Poetry (recommended) installs into the active environment and uses the committed poetry.lock for reproducible dependency resolution. pip is offered as an alternative.
conda create -n parslbox python=3.11.9
conda activate parslbox
pip install poetry
poetry install # core dependencies
# poetry install --extras "simulation" # + ase, pymatgen
# poetry install --extras "agentic" # + uvicorn, mcp, pydantic
# poetry install --extras "simulation agentic" # both
# poetry install --all-extrasOr with pip
pip install . # core
# pip install ".[simulation]"
# pip install ".[agentic]"
# pip install ".[simulation,agentic]"
# pip install ".[all]"# First confirm a suitable Python is on PATH:
# which python # or `which python3.11`
# python --version # should be >= 3.11, < 3.14
python3.11 -m venv .venv
source .venv/bin/activate
pip install poetry
poetry install # (same extras options as above)Or with pip
pip install . # (same extras options as above)uv venv -p 3.11.9 .uvenv
source .uvenv/bin/activate
uv pip install poetry
poetry install # (same extras options as above)Or with uv pip
uv pip install . # (same extras options as above)Run pbx config to interactively create a configuration file. Edit the generated config to set correct executable paths and environment setup before running jobs.
Add jobs:
# Add a single LAMMPS job (requires system config name)
pbx add /path/to/sim --app lammps-kk --config polaris --ngpus 2 --tag run1
# Add all subdirectories in current folder as VASP jobs
pbx add all --app vasp --config polaris --tag ManyVaspCalc
# Add a Python script job with arguments and environment file
pbx add /path/to/analysis --app python --config polaris --input plot.py \
--args "--mode gpu --output-prefix test" --envfile env_setup.sh
# Add with explicit resources and dependencies
pbx add /path/to/calc --app vasp --config polaris --nnodes 1 --nocc 0.5 \
--parents "10 11" --tag stage2
# or wait for all jobs with a tag to finish
pbx add /path/to/calc2 --app vasp --config polaris --parent-tag stage1Submit via PBS or SLURM:
# Short flags
pbx qsub -c sophia -N myrun -q gpu --select 2 -T 90 -A myproject -a lammps-kk -t production
pbx sbatch -c perlmutter-gpu -N myrun -q regular --select 2 -T 90 -A myproject -a lammps-kk -t production
# Long flags
pbx qsub --config sophia --job-name myrun --queue gpu --select 2 --walltime 90 --project myproject --apps lammps-kk --tags production
pbx sbatch --config perlmutter-gpu --job-name myrun --queue regular --select 2 --walltime 90 --project myproject --apps lammps-kk --tags production
# Glob tags: use `*` to match a substring. Quote to prevent shell expansion.
pbx qsub -c sophia -N myrun -q gpu --select 2 -T 90 -A myproject -a lammps-kk -t '*nomix,prod-run'
# Self-respawn chain: auto-resubmit at every walltime boundary, up to 3 times
pbx qsub -c sophia -N sweep -q gpu --select 4 -T 4h -A myproject -a lammps-kk -t sweep \
--respawn 3New to pbx? See Example usage below — job_test/create_test_jobs.py generates a batch of tiny throwaway jobs so you can exercise the whole pipeline end-to-end in a few minutes without preparing real workloads.
Built-in: lammps-kk, vasp, orca, python, julia. Custom apps can be registered via config.yaml. Full details, per-app notes, status reporting protocol, and custom-app templates: docs/apps.md.
| System | Scheduler | GPUs/Node | GPU Type | Cores/Node | MPI Backend |
|---|---|---|---|---|---|
| polaris | PBS | 4 | NVIDIA A100 | 64 | MPICH |
| aurora-gpu | PBS | 6 | Intel Max 1550 | 208 | MPICH |
| aurora-tile | PBS | 12 (6x2 tiles) | Intel Max 1550 | 208 | MPICH |
| sophia | PBS | 8 | NVIDIA A100 (DGX) | 128 | OpenMPI |
| crux | PBS | 0 (CPU-only) | — | 256 | MPICH |
| lcrc-swing | PBS | 8 | NVIDIA A100 | 64 | OpenMPI |
| lcrc-improv | PBS | 0 (CPU-only) | — | 128 | OpenMPI |
| pinnacles-cenvalarc | SLURM | 0 or 2 (auto-detected) | NVIDIA L40S / H200 NVL | 64 | srun |
| perlmutter-gpu | SLURM | 4 | NVIDIA A100 | 128 | srun |
| perlmutter-cpu | SLURM | 0 (CPU-only) | — | 128 | srun |
| perlmutter-gpu-srun | SLURM | 4 | NVIDIA A100 | 128 | srun |
Each system defines its own MPI defaults, scheduler templates, and resource detection methods. New systems can be added by creating a config class inheriting from BaseSystemConfig.
Launcher placement: most configs use SimpleLauncher, which concentrates all workers on the head node. For very large runs (above ~10k workers) head-node RAM becomes the scaling ceiling, so some configs instead place one Parsl manager per compute node (workers distributed across nodes): perlmutter-gpu-srun is a SrunLauncher variant of perlmutter-gpu for this purpose, and aurora-tile uses MpiExecLauncher per-node placement by default.
ParslBox provides a Python API for scripts and AI agents. The API delegates to core logic inside the command modules, ensuring CLI and API behavior stay in sync.
Main methods:
add_jobs(paths, app, config, ...)update_jobs(job_ids, ...)list_jobs(...)— returns all jobs when called with no filtersfilter_jobs(...)— returns job IDs matching filters (status, app, tag, path, input)get_job(job_id),get_jobs_by_ids(ids)remove_job,remove_jobs,remove_all_jobsqsub(...)— submit via PBSsbatch(...)— submit via SLURMrun(...)— execute jobs directlyqdel(jobid, grace=30)/scancel(jobid, grace=30)— graceful cancel of a running PBX batch job (SIGTERM → waitgraceseconds → hard kill)
Exceptions: ParslBoxError, ValidationError, JobNotFoundError
Full per-method reference with examples: docs/api-details.md.
ParslBox ships an MCP server and two agent skills (parslbox-cli, parslbox-api) that drop into any agentic harness — Claude Code, OpenCode, or anything that speaks MCP. The MCP server exposes job add/submit/query/cancel as callable tools; the skills teach the agent the pbx CLI and the Python API. Install the MCP dependencies with pip install ".[agentic]".
See docs/agentic_usage.md for full setup — adding the MCP server and skills to Claude Code and OpenCode — plus best practices for driving parslbox from an agent.
Full per-command reference with examples lives in docs/commands.md.
pbx reads three shell environment variables to locate its config, database, and pace job submissions. These are set in your shell (via export) — not inside config.yaml. pbx config prints copy-paste-ready export lines at the end of its output when relevant.
| Variable | Purpose | Default when unset |
|---|---|---|
PBX_CONFIG_PATH |
Location of config.yaml |
~/.parslbox/config.yaml |
PBX_DB_PATH |
Location of the SQLite job database | ~/.parslbox/job_database_pbx.db |
PBX_RUN_DELAY |
Seconds to sleep between consecutive job submissions | 0.2 — bump up on systems like Perlmutter where rapid srun invocations can overload slurmctld |
If you echo $PBX_DB_PATH in a fresh shell and get an empty line, that's the intended "not set" state — pbx falls back to the defaults above. Nothing is wrong.
# In your current shell (temporary — must re-export in each new shell):
export PBX_CONFIG_PATH=/scratch/mycfgs # dir → <dir>/config.yaml
export PBX_DB_PATH=/scratch/mydbs # dir → <dir>/job_database_pbx.db
export PBX_RUN_DELAY=0.5For persistence across shells, add the same lines to your shell profile (~/.bashrc, ~/.zshrc, etc.).
pbx normalizes the value at read time:
export PBX_DB_PATH=$(pwd) # current dir → <cwd>/job_database_pbx.db
export PBX_DB_PATH=/path/to/project/dir # any dir → <dir>/job_database_pbx.db
export PBX_DB_PATH=/path/to/project/mydb.db # explicit .db file name (must end in .db)PBX_CONFIG_PATH similarly accepts either a directory (pbx appends config.yaml) or a full path to a .yml/.yaml file.
Note: these are shell env vars — they are not read from, or set inside,
config.yaml.
All three env vars are automatically forwarded into the pbx qsub/pbx sbatch submission scripts, so the batch job sees the same config/DB paths as your login shell.
The default run directory (~/.parslbox/runs/<timestamp>/) is not env-var-controlled. Override per-run via the --run-dir /custom/path CLI flag on pbx qsub / pbx sbatch / pbx run.
- Single-node GPU jobs: Assign specific GPU IDs (e.g., 0,1) so multiple jobs can share a node
- CPU-only jobs: Use
--noccfor fractional node occupancy (e.g., 0.25); multiple jobs co-reside up to 1.0 - Multi-node jobs: Exclusive free nodes with MPI hostlist generation
- Non-MPI apps: Resource launcher constrains execution to assigned node/resources
- Dependency-aware scheduling: Jobs dispatch once their parents finish and resources free up (parents satisfy on
Done/Warning) - Node health tracking: Quarantine a node only after multiple distinct jobs fail on it (a single job's crash — even a multi-node one — never quarantines a node), auto-recover when healthy
ParslBox generates MPI launch commands with CPU binding flags appropriate for each job type (subnode, fullnode, multinode) and scheduler:
- PBS systems (mpiexec/MPICH, mpirun/OpenMPI): Configurable via
cpu_bind_methodin thempi:config section. Options:none,rankfile,list,depth. Therankfileandlistmethods provide GPU-affinity-aware core assignments. - SLURM systems (srun): Native SLURM resource binding. Full/multi-node GPU:
--gpus-per-node+--gpu-bind=map_gpu|mask_gpuwith--cpu-bind=cores|threads. Sub-node GPU:--gpu-bind=map_gpu:{pbx_gpu_ids}+--cpu-bind=mask_cpu:{hex}. All srun jobs include--overlap(required when running underSrunLauncher-based configs likeperlmutter-gpu-srun; also enables sub-node packing) and-N {nodes}for explicit node control.
Details: parslbox/resource_manager/README.md
Statuses:
- Ready → Submitted → Running → Done | Failed | Killed
- Restart → Resubmitted → Running → … — the restart path.
Submitted/Resubmittedare the claimed states: apbx runatomically flips a job toSubmitted(fromReady) orResubmitted(fromRestart), stamping its batch id (sched_job_id) so multiple concurrent runs sharing one DB never double-claim. - Warning — if an app returns an invalid/unknown status (satisfies dependencies like
Done) - Killed — when walltime is exceeded in a no-respawn run, or whenever the user runs
pbx qdel/pbx scancel. At shutdown the orchestrator reconciles its own jobs per state:Running → Killed(orRestart/Failedin a--respawnrun), and claimed-but-not-yet-running jobs go back to the pool (Submitted → Ready,Resubmitted → Restart). If the signal handler can't finish its DB writes in time, a post-kill reconciliation step inpbx qdel/scancelapplies the same per-state rules, scoped to the killed batch'ssched_job_id. - Restart — set by either the user (
pbx update --status Restart) or by the orchestrator in a--respawnchain at walltime. Either way, the nextpbx runcalls the app'srestart()hook lazily per-job as eachRestartrow is dispatched (patch fields and re-run / re-run as-is / mark Failed), right after resources are assigned.
Full state-transition table and end-to-end chain walkthrough: docs/pbx-run-details.md.
The fastest way to verify a fresh install and see the whole pipeline in action:
cd job_test
python create_test_jobs.py <config_name> --python hello_affinity.py 3 --tag firstrunThis creates 3 tiny Python jobs (using hello_affinity.py, which just prints per-rank CPU/GPU affinity) under job_test/tests/python/ and registers them with pbx. Then submit them:
pbx ls -t firstrun # confirm the jobs got registered
pbx qsub -c <config_name> -N firstrun -q <queue> --select 1 -T 15 -A <project> -t firstruncreate_test_jobs.py supports LAMMPS, Python, Julia, and VASP job creation with varied resources and dependencies — see its --help for the full menu. Other useful files in job_test/:
hello_affinity.py/hello_affinity_julia.jl— affinity-printing scripts; drop-in--inputtargets for thepython/juliaappstest_dynamic_jobs/— self-spawning workload for exercising the--dynamicjob-discovery flow
Longer, production-style orchestrations with their own READMEs and analysis scripts:
examples/strong_scaling/— LAMMPS strong scaling orchestration with automated analysis and publication-ready plotsexamples/weak_scaling/— LAMMPS weak scaling orchestration with intelligent system replicationexamples/chemgraph_parslbox_example/— MCP server integration with ChemGraph AI agent
Issues and PRs welcome at:
Author and developer: Fakhrul Hasan Bhuiyan
Copyright Argonne UChicago LLC, 2026. All rights reserved.