All jobs

Write k6 Load Tests for a FastAPI API

Yes. An agent writes k6 scripts for your FastAPI app's main flows, ramps them against the service in its sandbox and reports p95 latency and the breaking rate.

Run this on RushIn a Project that has your repository.

What the agent does

The agent clones the project into its own cloud sandbox and reads how the backend runs in production, because a load test against the wrong server command measures the wrong thing. In fastapi/full-stack-fastapi-template, backend/pyproject.toml asks for Python >=3.14, fastapi[standard]>=0.141.1, sqlmodel, psycopg[binary] and pwdlib[argon2,bcrypt], with uv.lock at the root. backend/Dockerfile ends with CMD ["fastapi", "run", "--workers", "4"], and compose.yml wires the backend to Postgres through DATABASE_URL and a health check on /api/v1/utils/health-check/. The agent installs Postgres in the sandbox, sets the values the root .env names (POSTGRES_PASSWORD, SECRET_KEY, FIRST_SUPERUSER), runs backend/scripts/prestart.sh (alembic upgrade head, then app/initial_data.py), and starts the backend with the same fastapi run --workers 4.

It picks three flows from backend/app/api/routes. Sign-in is POST /api/v1/login/access-token, which takes an OAuth2PasswordRequestForm, so the k6 script sends username and password as a form body rather than JSON, and core/security.py verifies the password with Argon2 first and bcrypt second through PasswordHash. Reading is GET /api/v1/items/, which runs a count query and then an offset and limit page ordered by created_at. Writing is POST /api/v1/items/. Users come from POST /api/v1/users/signup, with unique emails generated in the script.

Two details in the code shape how the agent reads the results. Every route handler is a plain def, not async def, so FastAPI runs each one in a thread pool. And backend/app/core/db.py calls create_engine(str(settings.DATABASE_URL), pool_pre_ping=True) with no pool_size, so each worker uses SQLAlchemy's default connection pool. When reads slow down before CPU is busy, waiting for a pooled connection is a likely reason, and the report says what it saw rather than claiming a cause.

Each flow gets a script in load/ with a ramping-arrival-rate scenario, checks on status codes and a threshold of http_req_failed: ["rate<0.01"]. The agent runs each one against the four-worker backend, reads k6's summary per stage, and records the rate where errors first pass 1% and the p95 of http_req_duration just before it.

Start it with one API call

POST https://api.prix.dev/o/{org}/sessions with this body:

{
  "agent": "claude",
  "input": "Write k6 load tests for this FastAPI backend. Read backend/pyproject.toml, backend/Dockerfile, compose.yml, .env and the routes under backend/app/api/routes. Start the backend in the sandbox the way the Dockerfile does (fastapi run --workers 4) with its own Postgres: set the variables .env names and run backend/scripts/prestart.sh first. Write one k6 script in load/ for each of the three most important flows, including sign-in through /api/v1/login/access-token as a form body, using a ramping-arrival-rate scenario, checks on status codes and a threshold on http_req_failed of 1%. Sign up unique users in the scripts. Ramp each until the error rate passes 1% and record the p95 latency and the request rate where errors start. Open a pull request with the scripts, a load/README.md and a table per flow of request rate, p95 latency and error rate at each stage.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
  1. 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.
  2. 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.
  3. 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 with load/login.js, load/read-items.js, load/create-item.js and load/README.md, which says how to start the backend with a database, how to set the target with an environment variable, and which superuser or sign-up path the scripts use. The description has one table per flow with each stage's request rate, p95 latency and error rate, and the breaking stage marked.

Every number comes from the sandbox: four Uvicorn workers and a Postgres on the same machine. The pull request says so and treats the numbers as a baseline for later changes, such as setting pool_size or making a handler async, rather than a capacity figure for production. The transcript keeps each k6 run with its full output.

Before merging, a reviewer checks that the scripts never hard-code FIRST_SUPERUSER_PASSWORD from a real environment, that the threshold matches the team's own idea of failure, and that the item flows reflect how the frontend calls the API, page sizes included.

Run it on every change to the database layer

Once the scripts exist, a repository webhook on your server can send a follow-up request when a pull request touches backend/app/core/db.py, crud.py or a migration, asking the agent to rerun the scripts on that branch and compare with the baseline table. Put the branch in resources as https://github.com/your-org/your-app/tree/<branch>.

With a webhook URL on the API key, Rush posts run.completed when the turn ends, with the final answer in result up to 16 KiB. Your handler can comment the comparison on the pull request that triggered it.

The use case this job follows, with its automation and trigger: Write the missing tests.

Why run it on Rush?

A FastAPI load test needs the real server command, the real database and nothing else on the machine. In a Rush sandbox the agent installs Python and uv, Postgres and k6 on one machine of its own, runs migrations from scratch, and starts the four workers the Dockerfile asks for, so the ramp measures this app and not a laptop's background work.

The proof is kept: each script run's k6 summary is in the transcript, and the table in the pull request is built from those outputs. Different services, or the same service on two branches, can run at once in separate sandboxes without sharing a database.

Frequently asked questions

Why run four workers instead of uvicorn --reload?

The Dockerfile runs fastapi run --workers 4, so that is what production serves. A single reloading development server would break at a much lower rate and say little about the deployed app.

Does sign-in need a JSON body?

No. /api/v1/login/access-token reads an OAuth2PasswordRequestForm, so k6 must send username and password form-encoded. A JSON body gets a 422 on every request, which would look like the API breaking at any rate.

Will it change my application code?

No. The prompt adds scripts and a README only. Where the results suggest a change, such as a larger connection pool, the report names it as a follow-up for you to decide.

Can it test the React frontend too?

Load testing here targets the API. For the frontend, the template already has Playwright tests in its compose setup; ask for a separate run if you want browser flows measured.