What the agent does
The agent clones the API into its own cloud sandbox and reads how it starts before it writes a load script. In a Fastify app like fastify/demo, package.json pins fastify ^5.10.0, runs TypeScript through tsx, uses knex with mysql2 and postgrator migrations, and has scripts db:create, db:migrate, db:seed, build (tsc), start and test, where test seeds the database and runs node:test under c8. src/server.ts listens on PORT or 3000, sets connectionTimeout 120000, requestTimeout 60000, keepAliveTimeout 10000 and headersTimeout 15000, and keeps the logger silent unless LOG_LEVEL is set or stdout is a TTY. .github/workflows/ci.yml runs a mysql:8.4 service with test_db, test_user and test_password, so the agent starts the same MySQL in the sandbox, writes a .env from .env.example, runs npm run db:migrate and npm run db:seed, and starts the server.
Then it reads what stands between a load generator and the routes. src/routes/api/autohooks.ts rejects any /api request without a session except /api/auth/login, and src/routes/api/auth/index.ts sets request.session.user and saves the session on a correct password, so each virtual user has to log in once and carry the cookie named by COOKIE_NAME. scripts/seed-database.ts creates basic, moderator and admin users at example.com, and test/helper.ts logs in with the password Password123$. src/plugins/external/rate-limit.ts limits every route to RATE_LIMIT_MAX requests per minute, 100 by default and 4 in CI, src/routes/api/users/index.ts caps PUT /api/users/update-password at 3 per minute, and the not-found handler in src/app.ts is limited to 3 per 500 ms. A load test that ignores those measures the limiter, not the API, so the agent raises RATE_LIMIT_MAX in the sandbox .env and says so in the report.
The flows come from src/routes/api/tasks/index.ts: GET /api/tasks with page, limit up to 100, status and order from QueryTaskPaginationSchema, GET /api/tasks/:id, POST /api/tasks with a name, PATCH and DELETE /api/tasks/:id, POST /api/tasks/:id/assign, POST /api/tasks/:id/upload for a JPEG or PNG, and GET /api/tasks/download/csv, which streams a gzipped CSV through csv-stringify. The agent writes one script per flow under load/, using autocannon's programmatic API from TypeScript so the scripts run with tsx like the rest of the repository: sign-in, the paginated list, task creation, and the CSV download, each with the session cookie from a login made in a setup step, a request body where the schema requires one, and a check that every response is 2xx.
Each script runs with a fixed number of connections for a fixed duration, then again with more connections until non-2xx responses or timeouts appear, and the agent records autocannon's latency percentiles, requests per second and error counts for every step. It adds a load script to package.json, checks that npm run lint passes on the new files under the repository's neostandard ESLint config, and confirms npm run test still passes, since the load scripts live outside test/ and must not be picked up by the glob that runs node:test.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Write load tests for this Fastify API. Read package.json, src/server.ts, src/app.ts, src/plugins/external/rate-limit.ts, src/plugins/external/env.ts, src/routes/api/autohooks.ts, the routes under src/routes/api and scripts/seed-database.ts. Start MySQL in the sandbox as .github/workflows/ci.yml does, write a .env from .env.example with RATE_LIMIT_MAX raised so the limiter does not shape the results, run npm run db:migrate and npm run db:seed, and start the server. Write autocannon scripts under load/ in TypeScript, run with tsx, for sign-in, the paginated task list, task creation and the CSV download; log in once per script with a seeded user and send the session cookie on every request. Run each script at a fixed connection count, then raise the count until non-2xx responses or timeouts appear, and record latency percentiles, requests per second and errors at each step. Add a load script to package.json, make sure npm run lint and npm run test still pass, and open a pull request with the scripts and a results table. State the RATE_LIMIT_MAX you used and that the numbers come from the sandbox.",
"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
What comes back is a pull request on its own branch adding a load/ directory with one autocannon script per flow and a shared login helper, a load script in package.json, and autocannon as a dev dependency in package.json and package-lock.json. The description holds a table per flow with the connection count, latency percentiles, requests per second and error count at each step, the point at which errors first appeared, and the RATE_LIMIT_MAX the sandbox ran with, so the figures are read as sandbox figures rather than a capacity promise.
The transcript at GET /o/{org}/sessions/{id}/messages keeps the MySQL start, the migration and seed, the server start and every autocannon run with its full output, and the session read names the repository and the pull request. A reviewer can see which run produced each row of the table.
Before merging, a reviewer checks that the scripts never run under npm test, that the login helper uses a seeded account rather than creating users, and that the CSV flow drains the gzipped stream so it measures the whole response. The next step is usually a follow-up on the same session that runs the scripts against a staging host instead of the sandbox server.
Run it on every change to the database layer
The scripts are most useful after a change to src/plugins/external/knex.ts, a migration or a repository under src/plugins/app. Add an automation to the Project with a github trigger on pull_request.opened and pull_request.synchronize and a prompt that runs the load script on the pull request's branch and compares the table with the one on the default branch; the trigger clones that branch and adds the pull request event after the prompt. An automation starts at most max_runs_per_hour runs in an hour.
From your own server, the same run starts with resources set to https://github.com/your-org/your-app/tree/<branch> and a request_id made from the commit SHA. Give the key a webhook URL and Rush POSTs run.completed with data.session_url when the turn ends, so a handler can post the comparison table on the pull request.
The use case this job follows, with its automation and trigger: Write the missing tests.
Why run it on Rush?
A load test on a laptop measures the laptop: the MySQL container, the load generator and the server share one machine, and a sleeping browser tab moves the percentiles. In its own sandbox the agent starts the same MySQL image CI uses, seeds it the same way, and runs the generator against a server with nothing else competing, then writes down exactly which configuration produced each number.
The record is the point. Every autocannon run is in the transcript with its full output, and the pull request table cites them, so a later run on the same scripts has a baseline whose conditions are known. Several branches can be measured at once, each in its own sandbox against its own database.
Frequently asked questions
Why raise RATE_LIMIT_MAX for the run?
Because src/plugins/external/rate-limit.ts applies RATE_LIMIT_MAX requests per minute to every route, with a default of 100, and a load generator would hit that within the first second and measure 429 responses instead of the API. The agent raises it in the sandbox .env only, says so in the pull request, and leaves .env.example and the plugin as they were.
How do the scripts authenticate?
Each script posts to /api/auth/login once with a seeded user, which scripts/seed-database.ts creates and test/helper.ts logs in as, reads the session cookie named by COOKIE_NAME, and sends it on every autocannon request. No users are created during the run.
Will the load scripts run as part of npm test?
No. test runs node:test over ./test/**/*.ts through glob, and the load scripts live under load/, so neither the glob nor c8 sees them. They run only through the new load script, and the agent confirms npm run test still passes before opening the pull request.
Can it run against staging instead of the sandbox server?
Yes. The scripts take the base URL and the credentials from environment variables, so a follow-up on the same session can point them at a staging host, provided that host's rate limit is set for it. The sandbox run stays as the baseline in the pull request.