What the agent does
The run starts with the Linear issue in its prompt and the repository cloned into its own sandbox. In a FastAPI app like fastapi/full-stack-fastapi-template, the agent reads backend/pyproject.toml and finds FastAPI, SQLModel, Alembic and psycopg, Python pinned to 3.14 in .python-version, and uv as the package manager with a root uv.lock. The endpoint the issue names is read_items in backend/app/api/routes/items.py: it takes skip: int = 0 and limit: int = 100, orders by Item.created_at descending, and for a user who is not a superuser filters on Item.owner_id before calling .offset(skip).limit(limit). It returns ItemsPublic, which backend/app/models.py defines as data: list[ItemPublic] and count: int.
Offset pagination on that query has two known faults: rows shift between pages when items are created or deleted, and a large skip makes Postgres read and discard every earlier row. The agent replaces it with a keyset cursor on (created_at, id), because created_at alone is not unique and the migration fe56fa70289e_add_created_at_to_user_and_item.py adds it as a nullable column. The cursor is an opaque, URL-safe base64 string of those two values; the next page is the rows strictly after it in created_at DESC, id DESC order, and the endpoint asks for limit + 1 rows so it knows whether to return a next_cursor. Rows with a null created_at sort last and are handled in the comparison, rather than being dropped from every page.
Keeping old clients working for one release means skip still works when no cursor is sent, and ItemsPublic gains next_cursor: str | None without losing count. The React frontend calls ItemsService.readItems({ query: { skip: 0, limit: 100 } }) in frontend/src/routes/_layout/items.tsx through a client generated from the OpenAPI schema by scripts/generate-client.sh, so the agent regenerates frontend/src/client and leaves the call working. If the issue asks for a composite index on item (owner_id, created_at, id), it adds an Alembic revision under backend/app/alembic/versions/ and runs alembic upgrade head; otherwise it notes the index as a follow-up in the pull request.
Then it proves the change the way the repository's own CI does. .github/workflows/test-backend.yml starts db and mailpit with docker compose up -d --wait, runs uv run bash scripts/prestart.sh (migrations and initial data) and then scripts/tests-start.sh, which wraps coverage run -m pytest tests/. The agent starts Postgres 18 in the sandbox, the version compose.yml pins for db, runs the same steps, and adds tests to backend/tests/api/routes/test_items.py next to the existing test_create_item and test_read_item: walking every page with the cursor returns each item once, a cursor from one user cannot read another user's items, a malformed cursor is a 422, and a request with skip and no cursor returns what it did before.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Do the work the Linear issue describes: replace skip and limit on GET /api/v1/items/ in backend/app/api/routes/items.py with an opaque cursor over (created_at, id) and add next_cursor to ItemsPublic in backend/app/models.py, keeping skip working when no cursor is sent so old clients keep working for one release. Handle rows with a null created_at. Regenerate the frontend client with scripts/generate-client.sh. Add tests to backend/tests/api/routes/test_items.py for walking all pages, owner isolation, a malformed cursor and the old skip path. Start Postgres from compose.yml, run uv run bash scripts/prestart.sh and uv run pytest in backend, and open a pull request that links back to the issue with the test output.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
},
{
"url": "https://linear.app/your-workspace/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 links the Linear issue. Its diff touches backend/app/api/routes/items.py (the cursor query and the skip fallback), backend/app/models.py (next_cursor on ItemsPublic), the regenerated files under frontend/src/client, the new tests in backend/tests/api/routes/test_items.py, and an Alembic revision when the issue asked for an index. The pull request description explains the cursor format, the sort order and how long skip stays supported, and quotes the pytest output from the sandbox.
The repository's own checks run on the pull request as they do for any contributor: Test Backend repeats the pytest run against its own Postgres, and the Playwright and pre-commit workflows cover the frontend and formatting. The session keeps the transcript of every command the agent ran, so a reviewer can open the migration and test steps and read their output instead of trusting a summary.
Before merging, a reviewer checks that the cursor cannot be forged into another owner's rows (the owner filter applies before the cursor comparison), that a deleted item between pages does not break the next request, and that the deprecation plan for skip matches what the API's other clients need. If something is off, a follow-up message to the same session wakes the sandbox where it stopped and the agent pushes a fix to the same branch.
Run it on every Linear issue labelled agent
In a Rush Project for the repository, add an automation with a linear trigger on linear.issue.labeled and the label agent. Each issue that gets the label starts one run with the issue as its context, so triage in Linear is the only step a person takes. Changes made by an app integration never fire it.
From your own server, the same thing is one POST /o/{org}/sessions per issue with the issue URL in resources, read through your Linear connection. Send an Idempotency-Key built from the issue id so a retried webhook does not start a second run, and set a webhook URL on the key to hear run.completed with the session_url when the pull request is open.
The use case this job follows, with its automation and trigger: Turn a Linear label into a PR.
Why run it on Rush?
This change is only finished when it has run against a real Postgres: a keyset query that looks right can still skip a row on a tie in created_at or leak rows across owners, and only a test against the database shows it. In a Rush sandbox the agent starts Postgres 18, the version the template's compose.yml pins, runs the Alembic migrations and the full pytest suite, and none of it touches the database or the uv environment on your laptop.
Each run is its own sandbox, so several labelled issues can be worked at once, up to the key's concurrent run limit, each on its own branch. The result is a pull request with the repository's checks on it and the complete record of the commands behind it, which is what lets a reviewer approve a change no person on the team started by hand.
Frequently asked questions
Will existing clients that send skip and limit break?
No, not in this change. When a request sends no cursor, the endpoint keeps the offset path and the same response fields, and next_cursor is added beside data and count. The pull request says how long skip is kept, so you can plan its removal for a later release.
Why a cursor over created_at and id rather than created_at alone?
Two items can share a created_at, and a cursor on a non-unique column either repeats or skips rows at the tie. Adding id as the second sort key makes every position unique. In this template created_at is also nullable, so the query has to place null rows explicitly.
Does the agent need my Postgres or my secrets?
No. The tests run against a Postgres the agent starts inside the sandbox, the version the repository's compose.yml names, with the throwaway values the template's settings expect. Nothing reaches your staging or production database.
What if the Linear issue is vague?
The issue text becomes the prompt, so a two-line issue gets a narrower change than a detailed one. When the description leaves the contract open (cursor format, page size, how long to keep skip), the agent states the choice it made in the pull request, and you can correct it with a follow-up message to the same session.
Does it update the generated frontend client?
Yes. The template generates frontend/src/client from the backend's OpenAPI schema with scripts/generate-client.sh, so the agent runs it after changing the endpoint and commits the regenerated files, and the existing readItems call in items.tsx keeps compiling.