What the agent does
The agent reads the pull request's diff and lists what changed behind each endpoint. In a Laravel app like koel the API controllers live in app/Http/Controllers/API/: SongController.php, PlaylistController.php, PlaylistSongController.php, PlaylistFolderController.php and MovePlaylistSongsController.php among them, with routes in routes/ and the real work in services such as App\Services\Playlist\PlaylistFolderService. For each changed controller action, form request or service method, it works out the HTTP call and the user that would reach the new code and checks the existing tests for one that makes it.
Then it reads how koel tests those features. phpunit.xml.dist defines three suites, feature, unit and integration, under tests/, and runs them on an in-memory SQLite database (DB_CONNECTION=sqlite, DB_DATABASE=:memory:) with array caches, a sync queue and empty AI and Spotify keys. Feature tests such as tests/Feature/PlaylistTest.php, PlaylistSongTest.php, SongTest.php and SongVisibilityTest.php extend Tests\TestCase, mark methods with PHPUnit's #[Test] attribute, create users and playlists with helpers from tests/Helpers.php (create_user, create_playlist, create_playlists) and model factories like Song::factory()->createMany(2), and call the API with getAs and postAs as a given user.
The agent writes the new tests into those files in that style, as feature tests because koel's own AGENTS.md says most tests should be feature tests and that every change must be programmatically tested. Each test covers one path, including the failure and edge paths the same file asks for: a playlist owner can reorder songs, a collaborator who is not the owner cannot delete the playlist, a private song stays hidden from another user. It asserts the JSON shape with the resource's JSON_STRUCTURE constant, as PlaylistTest does with PlaylistResource::JSON_STRUCTURE, and the database state with assertDatabaseHas where the action writes rows.
It runs the affected files first with php artisan test --compact tests/Feature/PlaylistTest.php, as koel's AGENTS.md recommends, then the whole feature suite, on the PHP version composer.json requires (>=8.3) with laravel/framework ^13.0. When a new test fails because the controller is wrong, the agent does not change the controller, because the prompt forbids application changes; it leaves the test out and describes the failing request in its comment.
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 song and playlist controllers. Read the diff against the base branch and list the new or changed behavior in app/Http/Controllers/API (the song and playlist controllers), their form requests and the services they call that no existing test covers. Write PHPUnit feature tests for each item in the matching file under tests/Feature (PlaylistTest.php, PlaylistSongTest.php, SongTest.php and so on), following the existing style: #[Test] methods, create_user and create_playlist from tests/Helpers.php, model factories, getAs and postAs, and the resource JSON_STRUCTURE constants. Cover failure paths as well as the happy path. Run php artisan test --compact on the files you changed, then the feature suite, until the new tests pass. Do not change application code: if a test fails because the code is wrong, leave it out and describe the failure. Push the tests to this pull request's branch and post a comment listing each test and the behavior it covers.",
"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 a commit on the pull request's branch with new #[Test] methods in the existing files under tests/Feature/, and a new test class only when the pull request added a controller with no test file yet. Every test creates its own user and data through the helpers and factories, calls the endpoint as that user, and asserts the status, the JSON structure and the rows the action wrote. Nothing under app/, routes/ or database/ changes.
The agent's comment on the pull request lists each test method, the behavior it covers and the php artisan test commands it ran. When a test exposed a defect, such as a playlist endpoint that lets a non-owner change it, the comment quotes the failing assertion and names the controller method instead of pushing a red test. Your CI runs the suites again on the pushed commit, and that run is the result a reviewer relies on.
Before merging, a reviewer checks that authorization tests use the right kind of user for what they claim (owner, collaborator, admin or a stranger), that tests involving time or ULIDs freeze them as PlaylistTest does with Ulid::freeze(), and that no test depends on media files outside tests/songs/.
Run it on every pull request opened
Give the Project bound to your Laravel repository an automation with this prompt and a GitHub trigger on pull_request.opened, filtered with branch_patterns to the branches you merge into. Each pull request opened by someone with write access then gets a run of its own; the prompt it receives includes the pull request number, URL, title, author and base branch. Pull requests from forks never fire it, and neither do the ones the Rush GitHub App opens itself.
If you would rather choose which pull requests get tests, handle GitHub's pull_request webhook in your own app and send this request with the pull request's URL in resources only when the diff touches app/Http/Controllers/API. The delivery id as Idempotency-Key keeps a retried webhook from starting a second run.
The use case this job follows, with its automation and trigger: Write the missing tests.
Why run it on Rush?
Writing feature tests for another developer's branch means switching your local koel install to that branch, reinstalling Composer and pnpm dependencies, and rerunning the suite while your own work waits. On Rush every pull request gets its own sandbox with a clone at its branch and PHP ready to run php artisan test, so nothing in your local environment changes and several pull requests are covered at once.
What comes back can be verified line by line. The trajectory keeps each artisan test command with its output, so a reviewer can see a test fail for a wrong assumption and pass after the fix, and the repository's own CI runs the pushed tests again. The tests sit in the pull request next to the change they describe, where the reviewer is already reading.
Frequently asked questions
Will it write Pest tests?
Not in koel, which uses PHPUnit classes and says in its AGENTS.md to convert any Pest test to PHPUnit. The agent follows the framework the repository uses; in an app whose tests are Pest files, it writes Pest tests with the same helpers.
Does it need MySQL to run the tests?
No. koel's phpunit.xml.dist runs every suite on an in-memory SQLite database, so php artisan test needs no database server. If your app's tests use MySQL or Postgres, the agent starts that database in its sandbox and runs your migrations first.
Does it test the Vue frontend too?
The prompt keeps the run on the PHP controllers and tests/Feature/. koel's frontend has its own test setup; ask for those tests in a separate run when a pull request changes the client.
How many tests does it add per pull request?
As many behaviors as the diff adds or changes and no more. A small fix might get one test for the bug and one for the path next to it; a new endpoint gets its happy path, its authorization failures and its validation errors.