What the agent does
The agent clones the repository into its own sandbox and works out which packages the project actually asks for, because a requirements.txt is usually a pip freeze that mixes direct dependencies with everything they pulled in. In a Flask app like miguelgrinberg/microblog, requirements.txt pins 56 packages at exact versions, from Flask==3.0.0, Flask-SQLAlchemy==3.1.1 and SQLAlchemy==2.0.23 down to greenlet, MarkupSafe and urllib3. The agent reads the imports in app/ and microblog.py (flask_babel, flask_login, flask_mail, flask_migrate, flask_moment, flask_wtf, elasticsearch, langdetect, redis, rq, jwt, requests) and the places that need a package without importing it: Email() in app/auth/forms.py needs email-validator, app/cli.py shells out to pybabel from Babel, config.py accepts a Postgres DATABASE_URL that psycopg2-binary serves, and boot.sh and the Procfile run gunicorn and rq worker.
From that it writes pyproject.toml with only the direct dependencies under [project] dependencies, each kept at the version the old file pinned so the migration changes the tool and not the libraries. Packages that only a developer uses go in a dev dependency group: httpie and aiosmtpd (the local mail server the tutorial runs) are in requirements.txt but nothing in app/ imports them. Then uv lock resolves the rest, and the agent compares the locked versions of the transitive packages with the old pins and lists any that moved. It sets requires-python from what the code and its pins can run on, and adds a .python-version so uv and the image agree.
Then the files that installed with pip. The Dockerfile today starts FROM python:slim, runs pip install -r requirements.txt and then pip install gunicorn pymysql cryptography on top, so the image carries two packages, pymysql and cryptography, that the requirements never mention, for a MySQL DATABASE_URL. The agent moves those into pyproject.toml (or an optional extra, and says which), copies uv into the image, runs uv sync --locked --no-dev before copying the app so the dependency layer caches, and puts .venv/bin on PATH so boot.sh's flask db upgrade and gunicorn and the build-time flask translate compile still find their commands. deployment/supervisor/microblog.conf and microblog-tasks.conf call /home/ubuntu/microblog/venv/bin/gunicorn and .../venv/bin/rq; uv creates .venv by default, so the agent updates those paths or sets UV_PROJECT_ENVIRONMENT and explains the choice.
Last, it proves it. The repository has no CI workflow, so the agent says that rather than inventing one, and runs the checks itself: uv sync, uv run python -m unittest tests.py (the four tests in UserModelCase run against an in-memory SQLite database from TestConfig), uv run flask db upgrade on a fresh database, and docker build followed by a container start that reaches gunicorn. It deletes requirements.txt only once all of that passes, or keeps a generated one with uv export if the README or a deploy script still reads it.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Migrate this Flask project from pip and requirements.txt to uv. First find the direct dependencies: read the imports, the CLI commands the code shells out to, the WSGI server and worker the Procfile and boot scripts run, and any package the Dockerfile installs outside requirements.txt. Write a pyproject.toml with only those under dependencies, at the versions requirements.txt pins today, and developer-only tools in a dev dependency group. Run uv lock and list any transitive package whose version changed from the old pins. Add .python-version, update the Dockerfile to install with uv sync --locked --no-dev in a cached layer, and update any CI workflow, Procfile, boot script or process manager config that calls pip or a venv path. Run the tests with uv run, run the database migrations on a fresh database, build the image and start it. Remove requirements.txt only if nothing still reads it. Open a pull request with the dependency list and why each is direct, the version changes, and the test, migration and docker output.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
- Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
- Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.
What comes back
What comes back is a pull request on its own branch with a new pyproject.toml, uv.lock and .python-version, an updated Dockerfile, the venv paths fixed in deployment/supervisor/, and requirements.txt removed or regenerated. The description lists each direct dependency with the reason it is direct (the import, the command or the config that needs it), the developer-only packages moved to the dev group, the packages the Dockerfile used to install on the side, and every transitive package whose locked version differs from the old pin.
The proof is the transcript, which keeps every command with its output: uv lock, uv sync, the unittest run, flask db upgrade against a new database, the docker build and the container's first log lines showing gunicorn listening. If your repository runs CI on pull requests, those checks run against the new setup too; microblog has none, so the agent's own runs are the record.
Before merging, a reviewer checks that no package the app needs at runtime landed in the dev group, that the Dockerfile copies uv.lock and installs with --locked so the image matches the lock, and that every place that starts the app (the Procfile, boot.sh, supervisor, a deploy script) points at the new environment.
Run it on every Python repository still on requirements.txt
A team moving all its Python services to uv can start one run per repository: a POST /o/{org}/sessions with the repository in resources and this prompt as input, with an Idempotency-Key per repository so a retried request returns the run already started.
Up to the key's max_concurrent sessions run at once (4 by default), each in its own sandbox with its own Python and its own Docker. A webhook on the API key posts run.completed or run.failed with the session_url when each turn finishes, so a script can collect the pull requests as they arrive.
The use case this job follows, with its automation and trigger: Hand off from your terminal.
Why run it on Rush?
The risky part of a uv migration is not writing pyproject.toml, it is proving nothing else changed: the same versions, the same commands on PATH in the image, the same entry points for the web process and the worker. That takes a clean environment, a fresh database for the migrations and a Docker build, and a laptop that already has the old virtualenv activated hides exactly the mistakes you want to catch. A Rush sandbox starts with none of that installed.
The pull request carries the evidence a reviewer needs: why each package is direct, which transitive versions moved, and the output of the tests, the migration and the container start, each kept in the transcript with the command that produced it. If a reviewer wants pymysql as an optional extra instead of a dependency, one follow-up message to the same session changes it and runs everything again.
Frequently asked questions
Will it upgrade my dependencies?
No. The prompt keeps every direct dependency at the version requirements.txt pins today, so the migration changes how packages are installed, not which ones. Transitive packages are resolved by uv lock, and any whose version differs from the old pin is listed in the pull request.
How does it tell direct dependencies from transitive ones?
It reads what the code imports, what it runs as a command (pybabel, gunicorn, rq), and what the config needs without importing, such as a database driver for DATABASE_URL or email-validator for a WTForms Email() field. Everything else in the freeze is left to the lock.
What happens to my Dockerfile?
It installs uv, copies pyproject.toml and uv.lock first, runs uv sync --locked --no-dev so that layer is cached between code changes, then copies the app. The virtualenv's bin goes on PATH, so the existing entry point and boot script keep working unchanged.
Can I keep a requirements.txt for a platform that needs one?
Yes. If the README, a deploy script or a hosting platform still reads requirements.txt, the agent generates it from the lock with uv export and says which file depends on it, so the two cannot drift.