What the agent does
In a Flask app like miguelgrinberg/microblog, the models are already partly typed: app/models.py declares columns with SQLAlchemy 2's so.Mapped and so.mapped_column, such as username: so.Mapped[str] and last_seen: so.Mapped[Optional[datetime]]. The functions around them are not. set_password(self, password), avatar(self, size), get_token(self, expires_in=3600), the routes in app/main/routes.py and app/auth/routes.py, the API handlers in app/api, and helpers like translate(text, source_language, dest_language) in app/translate.py have no annotations. The agent adds a mypy section with strict = true, runs it, and works from the error list.
Some errors come from missing stubs, not from the code. requests needs types-requests, while packages such as Flask-Mail 0.9.1, rq and langdetect ship no type information, so the agent adds a per-module ignore_missing_imports entry for each one it cannot type and lists them, instead of a blanket setting that would hide new ones.
Others are design points strict mode forces into the open. create_app in app/init.py sets app.elasticsearch, app.redis and app.task_queue as new attributes on the Flask object, and app/search.py reads current_app.elasticsearch, which mypy rejects because Flask has no such attribute. The agent fixes this with a typed accessor or a Flask subclass that declares the three attributes, not with a cast at each call site. User.check_token calls token_expiration.replace(...) on a value typed Optional[datetime], and verify_reset_password_token returns None from its except branch and a user otherwise; the agent makes both return types explicit and handles the None case.
When mypy finds a real bug, the agent fixes it and lists it separately from annotation work, since it changes behaviour on purpose. Everything else must leave behaviour as it was, so after mypy reports zero errors it runs the four tests in tests.py with python -m unittest, starts the app with flask run against SQLite, and requests a few pages to confirm nothing broke at import time.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "codex",
"input": "Add type hints to every function and method in this Flask project and configure mypy in strict mode (strict = true in pyproject.toml or mypy.ini). Install stub packages where they exist and add a per-module ignore_missing_imports entry only for packages with no types, listing each one. Fix errors with real types, not casts or type: ignore. For attributes added to the Flask app object, declare them properly instead of casting at each use. Fix real bugs mypy finds and list them separately. Run the tests with python -m unittest, start the app with flask run and load a few pages, and open a pull request with mypy's final 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
The pull request adds the mypy configuration, the stub packages in requirements.txt, and annotations across app/: models, routes, forms, the API blueprint, email, search, translate and tasks. Its description includes mypy's final output with zero errors, the list of modules allowed to have missing imports and why, the design change for the extra app attributes, and a short, separate list of real bugs mypy surfaced with the fix for each.
A reviewer reads that bug list first, since those are the only intended behaviour changes, then spot-checks the annotations on view functions, where return types like a response or a redirect are easy to get subtly wrong. The rest is annotation and should not change behaviour, which the passing tests and the pages loaded in the sandbox support. The session transcript has every mypy run, so a reviewer can see how the error count fell.
Run it on every untyped module
This is a job to hand off and come back to. Start it from your own server or script with POST /o/{org}/sessions, close the laptop, and the webhook on your key posts run.completed with the session_url when the pull request is ready. If the run stops to ask something, answer with POST /o/{org}/sessions/{id}/messages and it continues in the same sandbox.
For a large codebase, start one run per package or blueprint instead of one for everything, so each pull request stays small enough to review, and add the mypy check to CI once the last one merges.
The use case this job follows, with its automation and trigger: Hand off from your terminal.
Why run it on Rush?
Getting a whole Flask app through mypy strict is many small rounds: run mypy, fix a dozen errors, install a stub, run it again. On your laptop that occupies an evening; in a Rush sandbox the agent does the rounds on its own copy of the repository while you work on something else, and every mypy and unittest run is kept with its output.
The sandbox also lets the agent check behaviour, not just types: it starts the app, loads pages and runs the tests, so the claim that nothing changed rests on commands that actually ran. You pick it up later as one pull request with the full record behind it.
Frequently asked questions
Will adding type hints change how my app behaves?
Annotations themselves do not change runtime behaviour. The only intended changes are real bugs mypy finds, which the pull request lists separately. The agent also runs the tests and starts the app to catch anything that breaks at import time.
What about libraries with no type stubs?
The agent installs stub packages where they exist, such as types-requests. For packages with no types, it adds a per-module ignore_missing_imports entry and lists each one, so a new untyped import still fails mypy.
Can I start this and check on it tomorrow?
Yes. The run works without you, and a sandbox that finishes a turn is parked with its disk and memory for up to 7 idle days. A follow-up message wakes it where it left off, for example to ask for a change after review.
Will it use type: ignore to get to zero errors?
The prompt rules out casts and type: ignore. When an error cannot be fixed with a real type, the agent explains it in the pull request rather than suppressing it.