What the agent does
The issue comes in as a resource. resources names the repository and the Linear issue URL, which Rush reads through your Linear connection and places in the prompt, so the description, the comments and the acceptance criteria are in front of the agent before it opens a file.
In a Symfony application like symfony/demo, composer.json asks for PHP 8.4 and pins Symfony to 8.1.* in extra.symfony.require, with phpunit/phpunit 11.5, phpstan/phpstan 2 with the Doctrine and Symfony extensions, friendsofphp/php-cs-fixer 3.92, dama/doctrine-test-bundle and doctrine/doctrine-fixtures-bundle in require-dev. phpunit.dist.xml sets failOnDeprecation, failOnNotice and failOnWarning to true and loads the DAMA extension, so every test runs in a transaction that is rolled back. .env.test points DATABASE_URL at data/database_test.sqlite, and that file is committed, as is data/database.sqlite for dev.
The public blog is src/Controller/BlogController.php under #[Route('/blog')]: index serves blog_index, the paginated /page/{page} and the /rss.xml feed, postShow serves /posts/{slug:post}, commentNew takes POST /comment/{postSlug}/new, and search answers /search. Admin is src/Controller/Admin/BlogController.php under #[Route('/admin/post')] with #[IsGranted(User::ROLE_ADMIN)] on the class, and edit and delete guarded again with #[IsGranted('edit', subject: 'post')] and 'delete', which a voter decides. Queries sit in src/Repository/PostRepository.php, findLatest returning a Paginator and findBySearchQuery, and src/Entity/Post.php carries the constraints: a non-blank title, a summary of at most 255 characters, content of at least 10, at most four tags.
A ticket about listing or search lands in BlogController and PostRepository; a ticket about who may edit lands in the voter. Tests go in tests/Controller/BlogControllerTest.php, where testIndex counts Paginator::PAGE_SIZE articles on /en/blog/, testRss checks the feed, testNewComment posts a comment and testAjaxSearch queries the search route, or in tests/Controller/Admin/BlogControllerTest.php, which logs a user in with loginUser and covers new, edit and delete. The agent runs composer install, vendor/bin/phpunit, vendor/bin/phpstan analyze at the configured level 6 and the console lints from lint.yaml, lint:twig, lint:yaml, lint:container and doctrine:schema:validate --skip-sync, and opens the pull request.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "codex",
"input": "Implement the Linear issue given as a resource in this Symfony codebase. Read the issue and its comments first. Make the change in the blog controller, admin controller, repository, entity or voter the issue concerns, and keep the diff small. Add PHPUnit tests in tests/Controller next to the existing ones. Run `composer install`, `vendor/bin/phpunit`, `vendor/bin/phpstan analyze --no-progress`, `bin/console lint:twig templates`, `bin/console lint:container` and `bin/console doctrine:schema:validate --skip-sync`, and fix what fails. Open a pull request whose description links the issue, explains the change and quotes the test result. Do not merge.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
},
{
"url": "https://linear.app/your-org/issue/ENG-42"
}
]
}- 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
A pull request on its own branch comes back. Its description links the issue, names the controller method, repository query or voter it changed, and quotes the PHPUnit summary with the PHPStan and lint results beside it. The diff holds the change and its test.
The pull request URL is on the session and in the run.completed webhook, and your handler posts it as a comment on the issue, because the sandbox has no Linear tools. The transcript keeps the install, each command and every file the agent read, so a reviewer can see that a change under #[IsGranted] was made after reading the voter and Admin/BlogControllerTest.php.
Before merging, a reviewer checks the translations: routes carry a locale, testIndex hits /en/blog/, and a new string needs an entry under translations/ or the lint:translations step in CI flags it.
Run it on every Linear issue labeled agent
Create an automation with a linear trigger on linear.issue.labeled, the hand-off label in config.labels and the team keys in config.teams. Each labeled issue starts one run with the issue as its context and this prompt; a label added by an app integration never fires it.
A linear.issue.state_changed trigger with config.states mapping a review state to plan and a later one to exec puts a read-only run first: it posts a plan as a comment on the issue and changes nothing, and the exec run opens the pull request once someone has read it.
The use case this job follows, with its automation and trigger: Turn a Linear label into a PR.
Why run it on Rush?
The demo is small, but its CI is strict: PHPUnit fails on any deprecation, notice or warning, PHPStan runs at level 6 over bin, config, public, src and tests, and composer audit runs with the lints. On Rush all of that runs in a sandbox, in parallel with other tickets, and the output stays with the run instead of in a terminal that was closed.
The pull request quotes PHPUnit and PHPStan, and the transcript shows which controller and voter the agent read before touching an #[IsGranted] attribute, which is what a reviewer wants to see on an access change. Nobody's local SQLite file is touched: the sandbox has its own copy of data/database_test.sqlite.
Frequently asked questions
How does the agent read the issue?
From a resources entry with the Linear issue URL, read through your Linear connection and placed in the prompt before the run starts. Connect Linear once with POST /me/connections {"type": "app", "name": "linear"} and finish the sign-in it returns.
Can it comment on the issue?
Not from the sandbox: a Linear sign-in cannot yet be named in connections. The pull request URL comes back on the session and in the webhook, and your handler posts it.
Does it need a database server?
No. .env.test uses SQLite at data/database_test.sqlite, committed with the repository, and the DAMA bundle rolls each test's transaction back, so the file is unchanged after the suite.
Will it respect the deprecation policy?
The suite fails on deprecations, notices and warnings by configuration, so a change that triggers one fails the run and the agent fixes it before opening the pull request, or says in the description why it could not.