What the agent does
The agent clones the repository into its own cloud sandbox and reads how requests are authenticated, because DRF decides between anonymous and user throttles from request.user. In a DRF API like HackSoftware/Django-Styleguide-Example, requirements/base.txt pins djangorestframework==3.16.1 on Django 5.2, and the REST_FRAMEWORK dictionary in config/django/base.py sets an exception handler and a filter backend but DEFAULT_AUTHENTICATION_CLASSES is an empty list. Authentication is opted into per view through ApiAuthMixin in styleguide_example/api/mixins.py, which adds session, session-as-header and JWT authentication with IsAuthenticated.
With that in mind the agent adds DEFAULT_THROTTLE_CLASSES with AnonRateThrottle and UserRateThrottle, and DEFAULT_THROTTLE_RATES of 20/minute for anon and 1000/day for user, to the existing REST_FRAMEWORK dictionary rather than a new settings module. Views without ApiAuthMixin count against the anonymous rate by client IP, and views with it count per user.
The login endpoints get the stricter scope. styleguide_example/authentication/urls.py routes session/login/ to UserSessionLoginApi and jwt/login/ to UserJwtLoginApi, which extends drf-jwt's ObtainJSONWebTokenView. The agent sets throttle_classes = [ScopedRateThrottle] and throttle_scope = "login" on both and adds a login rate to the settings. Throttle counts live in Django's cache; config/django/test.py already sets LocMemCache, and the base settings configure no shared cache, which the agent notes for production.
Tests sit beside the existing ones in styleguide_example/authentication/tests/apis/, which use Django's TestCase with DRF's APIClient and reverse("api:authentication:session:login"). The agent adds tests that clear the cache in setUp, override the rates with override_settings so the suite stays fast, send one more request than the limit and assert 429 with a Retry-After header, and check that a different scope is unaffected. Then it installs requirements/local.txt, starts PostgreSQL in the sandbox because DATABASES reads DATABASE_URL with a Postgres default, and runs pytest.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "codex",
"input": "A Linear issue labeled agent says: \"Add Throttling to a Django REST Framework API. Add DRF throttling: 20 requests per minute for anonymous users and 1,000 per day for authenticated ones, with a stricter scope on the login endpoint.\" Do the work in this repository, add tests for it, run pytest, and open a pull request that links back to the issue.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
},
{
"url": "https://linear.app/your-workspace/issue/ENG-42"
}
]
}- 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. In an API shaped like the styleguide example, the diff changes config/django/base.py (the throttle classes and rates inside the existing REST_FRAMEWORK dictionary), styleguide_example/authentication/apis.py (the login scope on the session and JWT login views), and adds a throttling test module under styleguide_example/authentication/tests/apis/. The description quotes the issue, links back to it and states the chosen login rate.
A reviewer checks the numbers against the issue, then the two things throttling depends on that the code does not show: which cache backend production uses, since a per-process cache gives each worker its own count, and whether the API sits behind a proxy, since AnonRateThrottle identifies clients by address and DRF's NUM_PROXIES setting decides which address that is. The pull request names both.
The checks are the repository's own: in the example, the GitHub workflow runs ruff check, mypy and pytest. The session keeps every command the agent ran and its output, including the pytest run against the sandbox PostgreSQL.
Run it on every Linear issue labelled agent
In the console, an automation on the Project with a Linear trigger on linear.issue.labeled, filtered to the agent label, starts one run for each issue that gains the label. The issue is the run's context and the run ends in a pull request that links back to it.
Your own server can start the same run with this request and the issue's URL in resources. With a webhook URL set on the API key, Rush POSTs run.completed when the turn finishes, and the session_url in it reads the pull request.
The use case this job follows, with its automation and trigger: Turn a Linear label into a PR.
Why run it on Rush?
Throttling tests need a database, the full dependency set and a cache, and they are easy to get subtly wrong when the cache carries counts between tests. On Rush each run gets its own sandbox with PostgreSQL started inside it, runs the real pytest suite, and keeps every command and its output, so the reviewer sees the 429s the tests actually produced.
Security-flavoured issues like this one tend to arrive in batches after an audit. Each labelled issue becomes its own run in its own sandbox and its own pull request, so throttling, password rules and header changes can be reviewed and merged separately instead of as one large branch.
Frequently asked questions
My views set authentication per view, not globally. Does throttling still work?
Yes, and the agent reads that first. DRF picks the anonymous or user throttle from request.user after authentication, so a view that uses ApiAuthMixin counts per user and a view without it counts per IP. The tests cover one of each.
What does the login scope do that the anonymous rate does not?
Login views are anonymous by nature, so without a scope they would share the general 20 per minute. ScopedRateThrottle with throttle_scope = "login" gives the session and JWT login views their own lower rate, counted separately from every other anonymous request.
Will the tests be slow because of the per-day rate?
No. The tests override DEFAULT_THROTTLE_RATES with small numbers through override_settings and clear the cache before each case, so they send only a handful of requests. The production rates stay in settings exactly as the issue states them.
Does it change how errors look?
The project routes errors through its own EXCEPTION_HANDLER. The agent checks that a throttled request still comes out with status 429 and Retry-After through that handler, and adds an assertion on the response body shape if the handler reformats it.