What the agent does
The agent starts from the Linear issue and the repository in its own cloud sandbox, and reads how the app is put together before it adds authentication. In a project generated from vapor/template with Fluent, Package.swift is swift-tools-version:6.3, depends on vapor from 4.121.4, fluent from 4.13.0, a Fluent driver (the template offers Postgres 2.12.0, MySQL 4.8.0 and SQLite 4.9.0) and swift-nio from 2.101.0, and turns on upcoming features such as ExistentialAny, InternalImportsByDefault and MemberImportVisibility, which decide how new files must write their imports and protocol types. Sources/App/routes.swift registers GET /, GET /hello and TodoController; Controllers/TodoController.swift groups todos with GET, POST and DELETE /todos/:todoID; Models/Todo.swift is a Model with a UUID id and a title; Migrations/CreateTodo.swift creates the todos schema; and configure.swift reads DATABASE_HOST, DATABASE_PORT, DATABASE_USERNAME, DATABASE_PASSWORD and DATABASE_NAME from the environment, adds the migration and calls routes(app). Nothing in the app knows what a user is.
The agent adds the vapor/jwt package to Package.swift and a JWT product to the app target, then builds the pieces in the repository's own shape. Models/User.swift is a Fluent model with a unique email and a passwordHash, with Migrations/CreateUser.swift beside CreateTodo and DTOs/UserDTO.swift beside TodoDTO. Controllers/AuthController.swift adds POST /auth/register, which hashes the password with req.password.async.hash, and POST /auth/login, which verifies it and returns a token signed from a SessionToken payload carrying the user id as sub and an exp a configurable number of hours out; its verify method calls exp.verifyNotExpired(). In configure.swift the agent adds the signing key from a JWT_SECRET environment variable with app.jwt.keys.add(hmac:digestAlgorithm:) and fails startup when it is missing outside the testing environment, so a deployment never runs unsigned. TodoController keeps its routes but its group gains SessionToken.authenticator() and guardMiddleware(), so GET /todos without a bearer token answers 401 and with one reads the user from req.auth.require(SessionToken.self).
Tests follow Tests/AppTests/AppTests.swift, which already uses Swift Testing: a @Suite("App Tests with DB", .serialized) with a withApp helper that calls Application.make(.testing), configure, autoMigrate, runs the test and autoReverts, and @Test functions that drive app.testing().test(.GET, "todos") and decode [TodoDTO]. The agent adds tests in the same file or a sibling: register then login returns a token; GET /todos without a header is 401; with the header it is 200 and lists the todos created on app.db; a token signed with another key is 401; and an expired payload is refused. The withApp helper sets a test signing key through the environment so configure takes the same path it takes in production.
Verification is what .github/workflows/test-template.yml does for the template itself: generate the project with the toolbox for every Fluent and Leaf combination in swift:6.3-noble, with mysql and psql services, and run swift test --package-path template-test with DATABASE_HOST pointing at the service. For a generated app the agent runs swift build and swift test in the same image against the database Compose defines, and then docker compose build, since the template's Dockerfile builds a static release binary with swift build -c release --static-swift-stdlib and a new package dependency has to survive that path too.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Add JWT authentication to this Vapor app as the attached Linear issue describes. Read Package.swift, Sources/App/configure.swift, routes.swift, entrypoint.swift, Controllers/TodoController.swift, Models/Todo.swift, DTOs/TodoDTO.swift, Migrations/CreateTodo.swift, Tests/AppTests/AppTests.swift, Dockerfile and docker-compose.yml. Add the vapor/jwt package, a User model with a unique email and a password hash plus its migration and DTO, an AuthController with register and login routes where login returns a token signed from a payload with sub and exp, a signing key read from JWT_SECRET in configure.swift that fails startup when missing outside testing, and the token authenticator with guardMiddleware on the routes the issue names. Write Swift Testing tests in the existing suite style covering register and login, 401 without a token, 200 with one, a token under a wrong key and an expired payload. Run swift build, swift test against the Compose database in the swift:6.3-noble image, and docker compose build. Open a pull request that links the issue, lists the protected routes and shows the test output.",
"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 Package.swift and Package.resolved, adds Models/User.swift, Migrations/CreateUser.swift, DTOs/UserDTO.swift and Controllers/AuthController.swift, changes configure.swift and TodoController.swift, and extends the tests under Tests/AppTests/. The description links the Linear issue, lists the routes now behind the bearer check and the two that are not, names the JWT_SECRET variable the deployment must set, and quotes the swift test summary.
The transcript at GET /o/{org}/sessions/{id}/messages keeps the package resolution, the build, every test run with its output and the Docker build, and GET /o/{org}/p/{project}/sessions/{id}/changes lists each changed file with its diff. A test that fails ends the run needs_review rather than as a pull request that claims to be green.
Before merging, a reviewer checks the token lifetime against the issue, confirms JWT_SECRET is set in each environment's secrets before the deploy that includes this change, and decides whether register should stay open or be restricted. A follow-up on the same session can add refresh tokens or move the key to a rotated key set.
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 the backend team. When someone labels an issue such as "todos API needs auth before the mobile app ships", the trigger puts the issue after the prompt and the run scopes itself to the routes 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, with a request_id made from the issue id so a retried call returns the run already started instead of opening a second pull request.
The use case this job follows, with its automation and trigger: Turn a Linear label into a PR.
Why run it on Rush?
Authentication added without tests is authentication that fails open somewhere: a route left outside the group, a key that is empty in production, an expiry that is never checked. In its own sandbox the agent builds the app in the same swift:6.3-noble image the template's workflow uses, runs the suite against a real database, and keeps the output, so the pull request shows each protected route answering 401 and 200 rather than asserting that it does.
A Swift build takes minutes and a laptop, and this one happens on neither. Several issues can run at once, each in its own sandbox with its own database, and the transcript keeps every command, so a reviewer reads what ran instead of trusting a summary.
Frequently asked questions
Which routes get protected?
The ones the Linear issue names. The agent applies SessionToken.authenticator() and guardMiddleware() to those route groups and leaves GET /, GET /hello, register and login open, and the pull request lists both sets so a reviewer can see nothing was missed.
Where does the signing key come from?
From a JWT_SECRET environment variable read in configure.swift, the same way the template reads DATABASE_HOST and the others. Outside the .testing environment a missing value stops startup with a clear error, so an unsigned deployment cannot happen quietly; the tests set a key through the environment so configure takes the same path.
Does it work with the template's Swift 6 settings?
Yes, and the build is the proof. Package.swift enables ExistentialAny, InternalImportsByDefault and MemberImportVisibility, so the new files write any on protocol types and import what they use explicitly. swift build and swift test in the swift:6.3-noble image fail on any slip, and the pull request opens only when they pass.
Will the Docker image still build?
The template's Dockerfile resolves packages, then builds a static release binary with jemalloc. A new dependency changes that path, so the agent runs docker compose build after the tests and includes the result; a build that fails ends the run needs_review.