What the agent does
The agent checks out the pull request's branch and reads its diff against the base branch before it writes anything. In an ASP.NET Core solution like dotnet/eShop the diff usually lands in src/Ordering.API (the minimal API endpoints in Apis/OrdersApi.cs, the commands and handlers under Application/), in src/Ordering.Domain (the Order and Buyer aggregates) or in src/Basket.API/Grpc/BasketService.cs. For each changed method it looks for a test that already exercises the new branch or the new rule, and lists the behavior that has none.
Then it reads how the solution already tests that code, because eShop uses two frameworks side by side. global.json pins SDK 10.0.302, sets the test runner to Microsoft.Testing.Platform and pins MSTest.Sdk 4.0.2. The unit test projects, tests/Basket.UnitTests and tests/Ordering.UnitTests, are MSTest.Sdk projects that substitute dependencies with NSubstitute 5.3: BasketServiceTests.cs builds a BasketService over Substitute.For<IBasketRepository>() and a TestServerCallContext. The functional projects, tests/Ordering.FunctionalTests and tests/Basket.FunctionalTests, are Aspire app hosts that use xunit.v3.mtp-v2, a WebApplicationFactory<Program> in OrderingApiFixture, and a Redis container in RedisFixture. The agent writes each new test in the project and the framework that already covers its neighbor, so a domain rule gets an MSTest [TestMethod] next to OrderAggregateTest.cs and a new HTTP behavior gets an xUnit [Fact] in OrderingApiTests.cs.
It reuses what the projects already provide instead of building new scaffolding: the Builders.cs helpers in Ordering.UnitTests for orders and addresses, the Helpers folder in Basket.UnitTests for gRPC call contexts, and the x-requestid header pattern the functional tests already send to the cancel and ship endpoints. Each test names the behavior it pins, such as rejecting a cancel with an empty request id, rather than the method it calls.
Before pushing, it runs dotnet test on the projects it touched and reads the output. A new test that fails because the application code is wrong is not rewritten until it passes: the prompt forbids changing application code, so the agent reports the failure as a finding instead. The functional tests start containers through Aspire, and the repository's own tests/README.md says they need Docker running; if the sandbox cannot start them, the agent says which tests it could not run instead of pushing them as passing.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "This pull request changes the ordering and basket APIs. Read the diff against the base branch and list the new or changed behavior in src/Ordering.API, src/Ordering.Domain and src/Basket.API that no existing test covers. Write tests for each item in the test project that already covers its neighbor: MSTest with NSubstitute in tests/Ordering.UnitTests and tests/Basket.UnitTests, xUnit with the existing fixtures in tests/Ordering.FunctionalTests and tests/Basket.FunctionalTests. Reuse Builders.cs and the existing helpers. Run dotnet test on each project you changed until the new tests pass. Do not change application code: if a test fails because the code is wrong, keep the test out of the commit and describe the failure. Push the tests to this pull request's branch and post a comment listing each test, the behavior it covers and the projects you ran.",
"resources": [
{
"url": "https://github.com/your-org/your-app/pull/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 one more commit on the pull request's own branch, not a second pull request. In a solution shaped like eShop it touches only files under tests/: new [TestMethod] cases in Ordering.UnitTests/Domain or Application, new cases in BasketServiceTests.cs, or new [Fact] methods in OrderingApiTests.cs, plus any small builder it had to extend in Builders.cs. Nothing under src/ changes, so the diff the author asked to be reviewed stays the same.
The pull request also gets a comment from the agent that lists each test by name, the behavior it covers, the file it lives in and the dotnet test projects it ran. When a test exposed a real bug, the comment describes the failing assertion and the line in the application code, and that test is left out of the commit. The repository's own CI then runs on the new commit, so the checks on the pull request are the proof the tests pass outside the sandbox too.
Before merging, a reviewer reads the new tests the way they read any test: does each assertion check the behavior the author meant, or only what the code happens to do today? They also check that substitutes stand in only for the repository, event bus or identity boundaries, and that no test depends on ordering or a fixed clock that will break later.
Run it on every pull request opened
In the Project bound to the repository, add an automation with this prompt and a GitHub trigger on pull_request.opened. Each new pull request then starts its own run, and the run's prompt carries the event: the repository and pull request number, its URL, title, author and base branch. Set branch_patterns to main so only pull requests into your default branch fire it, and max_runs_per_hour to cap a busy day. A pull request fires only when its author can write to the repository; pull requests from forks never fire, and neither do pull requests the Rush GitHub App opened itself.
If you would rather drive it from your own server, point a GitHub webhook for pull_request at it and, on opened, send this request with the pull request's URL in resources, which clones that pull request's branch. Use the delivery id as the Idempotency-Key header so a redelivered webhook returns the run it already started.
The use case this job follows, with its automation and trigger: Write the missing tests.
Why run it on Rush?
On a laptop this job means checking out someone else's branch, restoring a .NET 10 solution with a dozen projects, and leaving Docker running for the Aspire functional tests while you write and rerun them. On Rush each pull request gets its own sandbox with its own clone at that branch, so the agent can run dotnet test as many times as it needs without touching your working tree, and two pull requests opened in the same hour do not wait for each other.
The result is a commit you can check, not a claim that coverage went up. The trajectory keeps every dotnet test command and its output, so a reviewer can see which projects ran, which tests failed on the way and why the agent left one out. The repository's CI runs again on the pushed commit, and the tests sit in the same diff the reviewer is already reading.
Frequently asked questions
Will it write MSTest or xUnit tests?
Both, matching the project it adds to. eShop's unit test projects use MSTest.Sdk, and its functional test projects use xunit.v3.mtp-v2, so a test in Ordering.UnitTests is a [TestMethod] and a test in Ordering.FunctionalTests is a [Fact]. It does not add a third framework or move existing tests.
Can it change my application code to make a test pass?
The prompt tells it not to, and the pushed commit only touches files under tests/. When a new test fails because the code under review is wrong, it leaves that test out and describes the failure in its comment, which is usually the most useful thing the run finds.
Do the Aspire functional tests run in the sandbox?
They need Docker for the Postgres and Redis containers Aspire starts, as eShop's tests/README.md says. When the containers cannot start, the agent runs the unit test projects, says which functional tests it wrote but could not run, and leaves the final word to your CI.
What happens when the author pushes more commits?
The trigger in this page fires on pull_request.opened only. To cover later pushes as well, add pull_request.synchronize to the same trigger; each push then starts a run that reads the new commits and adds tests for what they changed.