What the agent does
The agent works from the issue text and the repository as they are, not from a template. In a repository shaped like ktorio/ktor-samples it opens kweet/build.gradle.kts and finds the io.ktor.plugin at 3.6.0 with Kotlin 2.4.20, a Gradle 9.2.1 wrapper, jvmToolchain(17), and dependencies on ktor-server-sessions, ktor-server-resources and FreeMarker, with tests on ktor-server-test-host-jvm and mockk. It reads kweet/src/main/kotlin/io/ktor/samples/kweet/KweetApplication.kt, where mainWithDependencies installs DefaultHeaders, CallLogging, ConditionalHeaders, PartialContent, Resources, FreeMarker and Sessions, and it reads the route files to see which handlers change state: PostNew.kt posts new kweets at /post-new and Delete.kt posts deletes at /kweet/{id}/delete, while Index.kt, ViewKweet.kt and UserPage.kt only read.
The change is two plugins and a routing wrapper. In KweetApplication.kt the agent installs CORS with one configured origin via allowHost rather than anyHost, and installs RateLimit with a named limiter set to 60 requests per minute. Inside the routing block it applies that named limiter only to the posting routes, the post<PostNew> handler and the delete handler, and leaves every read route unlimited. If the CORS or RateLimit artifacts are not already on the compile classpath it adds them to the dependencies block of build.gradle.kts, and nothing else in the file moves.
For tests it extends kweet/src/test/kotlin/KweetApplicationTest.kt, which already builds the app with testApplication and a relaxed mockk DAOFacade, so no database is needed. It adds cases that match the issue: a request carrying the configured Origin header gets Access-Control-Allow-Origin back and a request from another origin does not, and posting to /post-new more than 60 times within the window answers 429 while a GET on / stays unlimited. Then it runs ./gradlew test in the kweet directory with the sample's own wrapper until the suite passes.
Only then does it push a branch and open a pull request that names the Linear issue in its description. The repository's own CI does the rest: .github/workflows/build-all.yml runs ./build-all.sh on every pull request to main on JDK 24, so the new test cases run there too, not only in the sandbox.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "A Linear issue labeled agent says: \"Add CORS and Rate Limiting to a Ktor Server. In the kweet sample, install the CORS plugin for one configured origin and the RateLimit plugin at 60 requests per minute on the posting routes.\" Do the work in this repository, add tests for it, run ./gradlew test in the sample, and open a pull request that links back to the issue.",
"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. The diff touches kweet/src/main/kotlin/io/ktor/samples/kweet/KweetApplication.kt for the two install blocks and the rate-limited routing wrapper, PostNew.kt and Delete.kt only where the wrapper requires it, kweet/build.gradle.kts when a plugin artifact was missing, and kweet/src/test/kotlin/KweetApplicationTest.kt with the new CORS and rate-limit cases. The description quotes the issue, names the configured origin and the 60-per-minute limit, and links back to the Linear issue so the tracker closes the loop.
The pull request's checks are the proof. The build-all workflow the repository already has runs ./build-all.sh on the pull request, and the session's trajectory, read at GET /o/{org}/sessions/{id}/messages, keeps every ./gradlew test invocation with its output, so a reviewer can see the suite pass rather than take the description's word for it.
Before merging, a reviewer checks three things: that the allowed origin is the real frontend rather than a placeholder, that the named limiter wraps only the posting routes and not the whole site, and that a rate-limited form post answers 429 where the handlers otherwise redirect on bad input. Everything the agent did is in the diff and the trajectory; nothing reaches main until the pull request merges.
Run it on every Linear issue labeled agent
Sign in to Linear once with POST /me/connections {"type": "app", "name": "linear"} and let the console finish the authorize URL. Your server then watches Linear for the label (a Linear webhook on issue.labeled with the agent label) and starts one run per labeled issue, naming the issue URL in resources: https://linear.app/<workspace>/issue/ENG-42 puts the issue text in the prompt, read through your Linear connection, and resources names the repository the agent works in. The prompt tells the agent to do what the issue says and open a pull request that links back.
Set a webhook URL on the API key and Rush POSTs run.completed or run.failed when each run's turn finishes, with the session URL, so your server can post the pull request link as a comment on the issue. A key runs at most max_concurrent sessions at once (4 by default) and answers 429 past that, so a burst of labeled issues should queue. When resources names a repository, the Rush GitHub App must be installed on it, which is the one step the API cannot do for you.
The use case this job follows, with its automation and trigger: Turn a Linear label into a PR.
Why run it on Rush?
Picking up a labeled issue on a laptop means checking out the repository, warming a Gradle daemon and running ./gradlew test yourself before you even start, and the work stalls the moment you close the machine. On Rush the run happens in its own cloud sandbox with its own clone: the agent edits KweetApplication.kt, runs the sample's Gradle 9.2.1 wrapper against the JDK 17 toolchain the build file declares, and pushes only after the suite is green, while your working tree stays untouched.
Labeling issues also rarely stops at one. Ten labeled issues means ten sandboxes and ten independent pull requests at once, each within the key's max_concurrent limit, instead of ten serial checkouts on one machine. And the result is checkable rather than claimed: the pull request carries the diff and the issue link, the repository's build-all workflow runs on it, and the trajectory keeps every Gradle command and its output, so the proof of the change is the change itself.
Frequently asked questions
How does the run read the Linear issue?
You sign in to Linear once from the console, which creates a linear app connection on your account. After that, naming an issue URL like https://linear.app/<workspace>/issue/ENG-42 in resources puts the issue text in the prompt, read through that connection. The agent never sees your Linear credentials, and the run itself carries only agent, input and resources.
What if the issue does not say which origin or limit to use?
This issue names both: one configured origin for CORS and 60 requests per minute for the limiter. If your issue leaves one out, the agent picks a conservative default, states it in the pull request description, and links back so you can correct it in review before merging.
Will rate limiting slow down the whole site?
No. The limiter is a named RateLimit configuration applied inside the routing block only to the posting routes, the new-kweet and delete handlers in PostNew.kt and Delete.kt. Read routes such as /, /kweet/{id} and /user/{user} stay outside the wrapper, and the diff shows exactly which routes are wrapped.
Which Java and Gradle versions does it test with?
The sample's own toolchain: the Gradle 9.2.1 wrapper checked into kweet/gradle/wrapper and the jvmToolchain(17) line in kweet/build.gradle.kts. The agent runs ./gradlew test from the kweet directory, the same versions the repository's build-all.yml workflow builds with on pull requests.
How do I know the tests actually ran?
Two ways. The session's trajectory at GET /o/{org}/sessions/{id}/messages keeps every ./gradlew test invocation with its full output, and the pull request triggers the repository's existing build-all workflow, which runs ./build-all.sh on JDK 24. If the suite could not run, the run ends run.failed and the webhook says so instead of reporting a pull request.