# Add a Feature to a Phoenix App from a Customer Spec

Yes. Your product sends the spec to the API, and an agent builds the feature in the customer's Phoenix LiveView app, tests it and opens a pull request.

[Run this on Rush](https://cloud.getrush.ai/run/add-feature-per-customer/phoenix/), in a Project that has your repository.

## What the agent does

Your product takes a customer's feature request, writes it into one prompt, and starts a run on that customer's repository. The agent clones it into its own cloud sandbox and reads the app first. In a Phoenix app like `fly-apps/live_beats` it finds Phoenix 1.7 and LiveView in `mix.exs`, Ecto on Postgres, and a `test` alias that runs `ecto.create --quiet` and `ecto.migrate --quiet` before `mix test`. `config/test.exs` expects Postgres on localhost with the `postgres` user and the `Ecto.Adapters.SQL.Sandbox` pool, so the agent starts Postgres in the sandbox before anything else.

The spec is a play count for each song on the profile page that updates live. The agent follows playback to its source: `LiveBeats.MediaLibrary.play_song/1` in `lib/live_beats/media_library.ex` updates the song's `status` and `played_at` inside an `Ecto.Multi`, then broadcasts `%Events.Play{}` on the owner's PubSub topic. It reads the `cond` there closely, because one branch resumes a paused song and keeps its old start time. A resume is not a new play, so the count goes up only when a stopped song starts.

It extends what is there. A migration under `priv/repo/migrations/` adds a `play_count` integer with a default of 0 to `songs`, the `Song` schema in `lib/live_beats/media_library/song.ex` gains the field, and `play_song/1` adds the increment to the same `Multi` that sets `status: :playing`, the way the import path already keeps `songs_count` on users. The `Events.Play` broadcast then carries the updated song, so `handle_info` for that event in `lib/live_beats_web/live/profile_live.ex` re-inserts it into the `@streams.songs` stream and the new column in the songs table shows the count to every viewer of the profile.

Tests go where the project keeps them. `test/live_beats/media_library_test.exs` gets cases for a first play, a pause then resume that does not add a play, and a second play after a stop; `test/live_beats_web/live/profile_live_test.exs`, which already logs in a user with `user_fixture` and opens the profile with `live/2`, gets a case that plays a song and asserts the rendered count changes. The agent runs `mix test` and `mix format --check-formatted`, reads every failure and fixes it before it opens the pull request.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "A customer asked for this feature: show each song's total play count on the profile page, updated live. Count a play only when a stopped song starts, not when a paused song resumes. Follow the project's existing patterns in lib/live_beats/media_library.ex, the Song schema, priv/repo/migrations and the profile LiveView, start Postgres in the sandbox, add tests in test/live_beats and test/live_beats_web/live, run mix test, and open a pull request that explains the change in three bullets.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-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 the customer's repository. Its diff has one new migration adding `play_count` to `songs`, the field on the `Song` schema, the increment inside `play_song/1`'s transaction, a column in the profile's songs table, and the new test cases. The description is three bullets: what is counted, when it is counted, and how the profile page learns about it.

This repository ships no CI workflow, so the proof is the run itself: the session keeps the `mix test` output from the sandbox with Postgres running. A reviewer checks the migration is reversible, that the increment sits in the same `Repo.transaction` as the status change so a failed play does not count, and that the count appears only where the customer asked for it.

Your product gets the pull request link, the agent's final answer and the run's cost from the session read or the webhook, and can post them back to the customer's request. The transcript has every command and file the agent touched, which answers a customer's question about how the change was made without anyone reconstructing it.

## Run it on every customer feature request

Each feature request in your product becomes one `POST /o/{org}/sessions` with the customer's repository in `resources` and their words in `input`. Your backend keeps the mapping from its request id to the returned `execution_id`, and sends that request id as the `Idempotency-Key` so a retry returns the same run.

A webhook on the API key closes the loop: when a run finishes, Rush posts `run.completed` with the result and the session URL to your endpoint, and your product updates the customer's request with the pull request. A follow-up from the customer, such as hiding the count on private profiles, goes to the same session as a message and wakes the parked sandbox where it stopped.

## Why run it on Rush?

Building Phoenix features for many customers on one machine means a Postgres for each one, Elixir and Erlang versions that match each `mix.lock`, and dependencies like Bumblebee and EXLA in this app that take time to compile. Each Rush run gets its own sandbox: it starts its own Postgres, compiles that customer's dependencies, and leaves nothing behind for the next customer's run to trip over.

The customer gets proof, not a claim. The pull request shows the change, and the session record shows the `mix test` run that passed against a real database. Runs for different customers start side by side, each on its own branch, so a queue of requests does not wait on one developer's laptop.

## Frequently asked questions

### Why does resuming a paused song not count as a play?

In this app `play_song/1` treats a paused song differently: it keeps the elapsed time and shifts `played_at` back. That is the same listen continuing, so the prompt asks for the count to rise only when a stopped song starts. A customer who wants every press counted can say so in the spec.

### Where does the Postgres for the tests come from?

The sandbox starts one. `config/test.exs` expects Postgres on localhost with the `postgres` user, and the `test` alias in `mix.exs` creates and migrates the test database before `mix test` runs, so the agent only has to start the server.

### How does the count update for other people viewing the profile?

Through the PubSub broadcast the app already sends. `play_song/1` broadcasts `Events.Play` on the profile owner's topic, and every connected profile LiveView handles it, so carrying the updated song in that event is enough to refresh the row.

### The repository has no CI. What checks the pull request?

The run does. The agent runs `mix test` in the sandbox and the session keeps the output. If the customer's repository has a workflow, its checks run on the pull request as well.
