Delay batch ML/AI jobs to the cleanest grid window
Project description
cleanshift
Delay batch ML/AI jobs to the cleanest grid window.
cleanshift wraps your long-running job — fine-tuning, batch inference, embedding generation — and schedules it during the lowest-carbon hour within a time window you specify. After execution it prints a savings receipt showing estimated CO₂ avoided vs. running immediately.
Install
pip install cleanshift
Quick start — no API key needed
import time
from cleanshift import carbon_window, MockProvider
provider = MockProvider()
with carbon_window(provider=provider, max_delay_hours=6) as window:
time.sleep(1) # your job here
print(window.receipt.model_dump_json(indent=2))
MockProvider generates a synthetic sinusoidal intensity curve (peak at 3 PM UTC, trough at 3 AM UTC) so you can develop and test without signing up for anything.
Real API example
import os, time
from cleanshift import carbon_window, ElectricityMapProvider
provider = ElectricityMapProvider(
api_key=os.environ["ELECTRICITY_MAP_API_KEY"],
zone="US-CAL-CISO",
)
with carbon_window(provider=provider, max_delay_hours=6) as window:
run_fine_tuning()
print(window.receipt.model_dump_json(indent=2))
Decorator form
from cleanshift import carbon_window, ElectricityMapProvider, Policy
@carbon_window(
provider=provider,
max_delay_hours=12,
policy=Policy.THRESHOLD,
max_intensity_gco2_per_kwh=150,
)
def nightly_finetune():
...
nightly_finetune()
print(nightly_finetune.last_receipt.model_dump_json(indent=2))
Pure scheduling query
from cleanshift import find_cleanest_window, MockProvider
best = find_cleanest_window(
MockProvider(),
duration_hours=2,
max_delay_hours=24,
)
print(f"Recommended start: {best.isoformat()}")
Policies
| Policy | Behaviour |
|---|---|
min_carbon (default) |
Sleep until the lowest-intensity hour in the allowable window |
threshold |
Run as soon as intensity drops below max_intensity_gco2_per_kwh; falls back to the cleanest available slot if the threshold is never met |
now_or_never |
Run immediately if intensity is clean; raise JobSkipped otherwise |
halt_if_dirty |
Start immediately; pause execution if intensity spikes above max_intensity_gco2_per_kwh mid-run; resume automatically when it drops back (Unix only) |
halt_if_dirty notes
halt_if_dirty requires max_intensity_gco2_per_kwh and uses SIGSTOP/SIGCONT to pause the process, so it is not supported on Windows. The intensity is re-checked every poll_interval_seconds (default 5 minutes).
Receipt schema
class CarbonReceipt(BaseModel):
scheduled_at: AwareDatetime # when carbon_window was entered
ran_at: AwareDatetime # when execution actually started
delay_seconds: int
duration_seconds: int
avg_intensity_gco2_per_kwh: float # mean of readings at start + end of run
counterfactual_avg_intensity: float # intensity at scheduling time
estimated_savings_pct: float # computed field (positive = savings)
region: str
provider: str
avg_intensity_gco2_per_kwh is the arithmetic mean of readings taken at the start and end of the run — a point estimate, not a true integral. If your job is long enough that accuracy matters, prefer a provider with dense historical data.
Providers
| Class | Source | Sign-up |
|---|---|---|
MockProvider |
Synthetic curve | None |
ElectricityMapProvider |
electricitymaps.com | Free tier available |
WattTimeProvider |
watttime.org | Free tier available |
Adding a provider
Implement GridIntensityProvider:
from cleanshift.providers.base import GridIntensityProvider, IntensityPoint
class MyProvider(GridIntensityProvider):
@property
def provider_name(self) -> str: return "myprovider"
@property
def region(self) -> str: return "MY-REGION"
async def current(self) -> IntensityPoint: ...
async def forecast(self, from_dt, to_dt) -> list[IntensityPoint]: ...
CLI
# Dry-run with mock data (no API key)
cleanshift schedule --duration 2h --max-delay 24h --region US-CAL-CISO --dry-run
# With ElectricityMap
cleanshift schedule --duration 2h --max-delay 24h \
--provider electricitymap --api-key $KEY --zone US-CAL-CISO --dry-run
Development
pip install -e ".[dev]"
pytest
python examples/synthetic_demo.py
License
MIT
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 cleanshift-0.1.0.tar.gz.
File metadata
- Download URL: cleanshift-0.1.0.tar.gz
- Upload date:
- Size: 14.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c35d02ff81ff5e03150f83a176cf9517db2452d8f2eeda070a3359927745ed40
|
|
| MD5 |
9b7bb1d1d24f496e7a829a8881f72b84
|
|
| BLAKE2b-256 |
806d9f64140a02312c3ce6a011914fec89aecd4ce419ba1e4a97e8a997d29771
|
Provenance
The following attestation bundles were made for cleanshift-0.1.0.tar.gz:
Publisher:
publish.yml on ChanelYJ19/cleanshift
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cleanshift-0.1.0.tar.gz -
Subject digest:
c35d02ff81ff5e03150f83a176cf9517db2452d8f2eeda070a3359927745ed40 - Sigstore transparency entry: 1565196580
- Sigstore integration time:
-
Permalink:
ChanelYJ19/cleanshift@5508a8fec64f98a4788c280829c1dbbf9e13c886 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ChanelYJ19
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5508a8fec64f98a4788c280829c1dbbf9e13c886 -
Trigger Event:
release
-
Statement type:
File details
Details for the file cleanshift-0.1.0-py3-none-any.whl.
File metadata
- Download URL: cleanshift-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.8 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 |
cad3f528cb297bd01d714c6cfae5624bdaf31cf522ce038da8e84a7276d161c5
|
|
| MD5 |
849c26b62249900d31c4032390690037
|
|
| BLAKE2b-256 |
5c9dcc2ce47e8ea6a042475432ca675fa1308b51897c443c500227461b14c7ec
|
Provenance
The following attestation bundles were made for cleanshift-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on ChanelYJ19/cleanshift
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cleanshift-0.1.0-py3-none-any.whl -
Subject digest:
cad3f528cb297bd01d714c6cfae5624bdaf31cf522ce038da8e84a7276d161c5 - Sigstore transparency entry: 1565196696
- Sigstore integration time:
-
Permalink:
ChanelYJ19/cleanshift@5508a8fec64f98a4788c280829c1dbbf9e13c886 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ChanelYJ19
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5508a8fec64f98a4788c280829c1dbbf9e13c886 -
Trigger Event:
release
-
Statement type: