# Turn an Asana Task into a Pull Request for a Laravel App

Yes. An agent reads the Asana task, does the work in your Laravel repo with PHPUnit tests, opens a PR and comments the link back on the task.

[Run this on Rush](https://cloud.getrush.ai/run/asana-task-to-pr/laravel/), in a Project that has your repository.

## What the agent does

The agent starts with the task, not the code. Through the Asana connection it reads the task you name: its title, description, subtasks and comments, and any custom fields your project uses for acceptance criteria. It writes down what done means for this task before opening a file, so a vague ask ("playlists should be sortable") becomes a concrete list it can check against at the end.

Then it reads the repository the way the project expects. In a Laravel app like koel/koel, `composer.json` requires PHP 8.3 or later and `laravel/framework` `^13.0`, and the repository ships `AGENTS.md` and `CLAUDE.md` with Laravel Boost guidelines: follow sibling files, do not add dependencies without approval, prefer feature tests over ad hoc scripts. The agent follows them. It finds the code the task touches by following the route to its controller under `app/Http/Controllers/API/`, then the service in `app/Services/` and the model in `app/Models/`, and reads the matching tests in `tests/Feature/` (koel has 78 of them) to copy their setup.

It does the work with tests in the same shape the suite already uses. `phpunit.xml.dist` runs three suites (`feature`, `unit`, `integration`) against SQLite `:memory:` with `QUEUE_DRIVER` set to `sync`, so the agent can run them in its sandbox with no database server: `composer install`, `php artisan key:generate`, then `composer test`, which is `php artisan test --parallel`. Before pushing it also runs the checks CI runs: `composer lint` (`mago lint`), `composer cs` (`mago format --check`) and `composer analyze` (PHPStan with `phpstan.neon.dist`). When the task touches the Vue frontend in `resources/assets`, it installs with pnpm 10.32.1 from the `packageManager` field and runs `pnpm test` and `pnpm typecheck` as well.

When the work is done and the suite is green, it opens the pull request, then goes back to Asana and adds a comment on the task with the pull request link and a one-line summary of what changed. If the task asked for something it could not do, such as a decision the description leaves open, it says so in that comment and in the pull request instead of guessing.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Read the Asana task https://app.asana.com/0/<project-id>/<task-id> through the Asana connection, including its subtasks and comments, and list its acceptance criteria. Do the work it describes in this Laravel repository, following AGENTS.md and the conventions of sibling files. Add or update PHPUnit feature tests in tests/Feature for the behaviour it changes. Run composer test, composer lint, composer cs and composer analyze, and pnpm test if you changed anything under resources/assets, and fix what fails. Open a pull request whose description links the task and lists each acceptance criterion with the test that covers it. Then add a comment on the Asana task with the pull request link and a one-line summary. If something in the task is unclear, ask in the comment instead of guessing.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-laravel-app"
    }
  ]
}
```

1. **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.
2. **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.
3. **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 a new branch, and a comment on the Asana task that links to it. In a repository shaped like koel the diff usually spans a controller or request class under `app/Http/`, the service that holds the logic under `app/Services/`, a migration in `database/migrations/` when the task changes the schema, and a feature test under `tests/Feature/` that drives the endpoint the way a client would. The description links the task and maps each acceptance criterion to the test that covers it.

The repository's own CI is the first check: koel's workflows run the backend suite on SQLite, MariaDB and PostgreSQL, plus the lint and frontend jobs, so a migration that only works on SQLite shows up red on the pull request. The session's trajectory keeps each command the agent ran with its output, including `composer test` and `composer analyze`, so the reviewer can see what was run before the push.

Before merging, a reviewer checks the pull request against the task rather than against the agent's summary: that each criterion is really covered, that no dependency was added (the Boost guidelines ask for approval first), and that a new migration is reversible. The Asana comment tells whoever filed the task where to look, without anyone copying links by hand.

## Run it on every Asana task moved to Ready

Register an Asana webhook on the project and have your server send this request when a task moves into the section your team uses for agent work, with the task's URL in `input` and the repository in `resources`. Each task gets its own run, its own sandbox and its own branch, so three tasks moved on Friday evening are three pull requests on Monday morning, not one tangled branch.

Set a webhook URL on the API key and Rush POSTs `run.completed` or `run.failed` to your server when each run's turn finishes, with its `session_url`. A key runs at most `max_concurrent` sessions at once (4 by default) and answers `429` past that, so a server that moves a whole backlog at once should queue its requests.

## Why run it on Rush?

Doing an Asana task on a laptop ties it to the laptop: a PHP 8.3 toolchain, `composer install`, the parallel PHPUnit run, PHPStan at 1 GB of memory, and a working copy that stays on the feature branch until the work is done. On Rush the agent does all of it in its own sandbox with its own clone, so the task keeps moving after you close the lid, and your local branch and database are never touched.

The result carries its own evidence. The pull request runs the repository's CI, the trajectory keeps every command and its output, and the comment on the task links the two, so the person who filed the task can follow it without asking. Because each task is its own run, a team can hand off several at once and review each pull request on its own.

## Frequently asked questions

### How does the run reach Asana?

Connect Asana once with `POST /me/connections`; it is in Rush's MCP catalog and signs in with OAuth. Then add its name or id to `connections` in the request. A key run gets only the connections it names, so without it the agent cannot read the task or post the comment.

### Will the tests need MySQL or Redis?

Not for a suite like koel's. `phpunit.xml.dist` sets `DB_CONNECTION` to `sqlite`, `DB_DATABASE` to `:memory:`, the queue to `sync` and the cache to `array`, so the agent runs `composer test` in its sandbox without starting a database. Database-specific behaviour is still caught by your own CI running the MariaDB and PostgreSQL jobs on the pull request.

### Does it follow my project's AGENTS.md?

Yes. It reads `AGENTS.md` and `CLAUDE.md` before changing code, the same way it would on your machine. In koel those are Laravel Boost guidelines, such as reusing existing components and not changing dependencies without approval, and the pull request description says where the work had to make a judgment call.

### What if the Asana task is vague?

The request tells the agent to ask instead of guessing. It posts its question as a comment on the task and stops, and you can answer by sending a follow-up to the same session with `POST /o/{org}/sessions/{id}/messages`, which wakes the parked sandbox where it left off.
