What the agent does
The agent starts from the Linear issue and the repository in its own cloud sandbox, and reads how pagination is done before it adds a second kind. In a project laid out like HackSoftware/Django-Styleguide-Example, styleguide_example/api/pagination.py holds a get_paginated_response(*, pagination_class, serializer_class, queryset, request, view) helper and a LimitOffsetPagination subclass with default_limit = 10, max_limit = 50 and a get_paginated_response that returns limit, offset, count, next, previous and results in that order. styleguide_example/users/apis.py uses it in UserListApi, which nests a Pagination class with default_limit = 1, validates id, is_admin and email through a FilterSerializer, and hands user_list(filters=...) to the helper. styleguide_example/users/selectors.py builds that queryset as BaseUser.objects.all() filtered by BaseUserFilter, with no ordering at all, which is the first thing a cursor needs.
It reads the model next. BaseUser in styleguide_example/users/models.py extends BaseModel from styleguide_example/common/models.py, so it carries created_at with db_index=True and default=timezone.now, plus updated_at, a unique email, is_active, is_admin and a jwt_key. A cursor over created_at alone can repeat or skip rows when two users share a timestamp, so the agent orders by ("-created_at", "-id") and says in the pull request why both fields are there. It then writes a CursorPagination subclass in api/pagination.py on top of rest_framework.pagination.CursorPagination, with a page_size, a max_page_size and that ordering, and gives it the same shape of get_paginated_response the limit-offset class has, minus count, which a cursor cannot give without a second query. The get_paginated_response helper already takes any pagination_class, so UserListApi switches by changing its nested Pagination base class and nothing else in the view.
The tests follow the one that already exists. styleguide_example/api/tests/pagination/test_get_paginated_response.py builds an ExampleListApi with APIRequestFactory, creates users through user_create, and asserts the exact OrderedDict of the first page, including the next link http://testserver/some/path?limit=1&offset=1. The agent adds a sibling test for the cursor class that creates three users, requests the first page, follows the cursor query parameter it gets back, checks that the second page carries the remaining rows and a previous link, and checks that a user created between the two requests does not shift the page. It runs the suite the way .github/workflows/django.yml does: pytest with DJANGO_SETTINGS_MODULE = config.django.test from pytest.ini, where config/django/test.py swaps in MD5PasswordHasher and an in-memory cache, against the postgres service the workflow starts.
Before opening the pull request it runs the other two checks from that workflow, ruff check . under the 120-column pyproject.toml configuration and mypy --config mypy.ini styleguide_example/, which loads mypy_django_plugin and mypy_drf_plugin, and it updates the "Example List API" section of README.md, which shows the limit/offset response shape for /api/users/, so the documented shape matches the endpoint the issue changed. Endpoints the issue does not name keep LimitOffsetPagination.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Add cursor pagination to the endpoints the attached Linear issue names. Read styleguide_example/api/pagination.py, styleguide_example/users/apis.py, styleguide_example/users/selectors.py, styleguide_example/users/models.py, styleguide_example/common/models.py and the existing test in styleguide_example/api/tests/pagination/. Add a CursorPagination subclass beside LimitOffsetPagination with an explicit ordering on created_at and id so the cursor is stable, and a get_paginated_response that returns next, previous and results. Switch only the views the issue names by changing their nested Pagination base class; leave the other endpoints on limit-offset. Write tests with APIRequestFactory that walk forward and back through three users and that show a row inserted between requests does not shift the page. Run ruff check ., mypy --config mypy.ini styleguide_example/ and pytest with the settings in pytest.ini against Postgres, update the Example List API section of README.md to the new response shape, and open a pull request that links the issue and explains the ordering choice.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
},
{
"url": "https://linear.app/your-org/issue/ENG-123"
}
]
}- 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 that changes styleguide_example/api/pagination.py to add the cursor class, styleguide_example/users/apis.py to use it in the views the issue names, a new test module under styleguide_example/api/tests/pagination/, and the response example in README.md. The description links the Linear issue, shows a first-page response with its next cursor link, and gives the ordering and the reason for the tie-breaker on id.
The transcript at GET /o/{org}/sessions/{id}/messages holds the ruff, mypy and pytest runs with their full output, and GET /o/{org}/p/{project}/sessions/{id}/changes lists each changed file with its diff. Checks that do not pass end the run needs_review rather than as a pull request that claims to be green.
Before merging, a reviewer checks that the ordering matches an index (created_at is indexed on BaseModel; the pair with id uses the primary key), that no endpoint outside the issue changed, and that clients which read count from the old response have a plan for its absence. A follow-up on the same session can switch a second endpoint once the first is in.
Run it on every Linear issue labelled agent
In the console, add an automation to the Project with this prompt and a linear trigger on linear.issue.labeled, filtered to the agent label and to the team whose issues name endpoints. When someone labels an issue such as "users list should page by cursor, offset pages drift under inserts", the trigger puts the issue after the prompt and the run scopes itself to the endpoints it names. Only label changes made by a person in your Rush org fire the trigger; changes made by an app integration never do, and the automation starts at most max_runs_per_hour runs in an hour.
From your own server, the same body goes to POST /o/{org}/sessions with the repository in resources and the issue's URL beside it, read through your Linear connection, plus a request_id made from the issue id so a retried call does not open a second pull request for the same issue.
The use case this job follows, with its automation and trigger: Turn a Linear label into a PR.
Why run it on Rush?
Pagination changes look small and break quietly: a cursor over a non-unique column skips rows, and the only way to see it is to insert data between requests and read both pages. In its own sandbox the agent runs the real test database the workflow uses, writes that scenario as a test, and runs ruff, mypy and pytest exactly as django.yml does before it opens anything.
The record stays with the result. Every command and its output is in the transcript, the diff is readable from the session, and several issues can run at once, each in a separate sandbox, so a cursor change on one endpoint never waits on an unrelated one.
Frequently asked questions
Does it switch every list endpoint?
No. It changes the views the Linear issue names and leaves the rest on LimitOffsetPagination. The get_paginated_response helper takes the pagination class as an argument, so the two kinds coexist in api/pagination.py and each view picks one through its nested Pagination class.
Why order by created_at and id rather than created_at alone?
BaseModel.created_at defaults to timezone.now, so two users created in one request can share a value, and a cursor that compares only that field can repeat or drop one of them at a page boundary. Adding the primary key as a tie-breaker makes the order total. The pull request says this in its description.
What happens to the count field?
A cursor response has no count; computing one would mean the extra query that cursor pagination exists to avoid. The new class returns next, previous and results, the README example is updated to match, and the pull request calls out the removed field so API clients can be checked before merge.
Does it need Postgres to run the tests?
The workflow's build job runs pytest against a postgres service, so the agent starts the same database in the sandbox and uses the DATABASE_URL the project reads through django-environ. The test settings in config/django/test.py keep the rest in memory.