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

Yes. Your product sends the customer's spec to the API, and an agent builds the feature in their Gin repo, adds Go tests and opens a pull request.

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

## What the agent does

Your product receives a feature request from a customer, turns 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 code before it writes any. In a Gin app like `gothinkster/golang-gin-realworld-example-app` it finds Go 1.21 in `go.mod`, Gin 1.10 and GORM 1.25 on SQLite, and one package per domain: `articles/`, `users/` and `common/`, each with `models.go`, `routers.go`, `serializers.go` and a `unit_test.go`. The repository's own `AGENTS.md` describes that layout, and the agent follows it.

The spec here is cursor pagination on `GET /api/articles` without breaking offset pagination. The agent traces the request: `ArticleList` in `articles/routers.go` reads `tag`, `author`, `favorited`, `limit` and `offset` from the query string and hands them to `FindManyArticle` in `articles/models.go`, which parses `limit` with a default of 20 and `offset` with a default of 0, then branches four ways. The tag, author and favorited branches order by `updated_at desc`; the unfiltered branch calls `Offset` and `Limit` with no `Order` at all, so its pages have no stable order. A cursor needs one, so the agent orders by a unique key in every branch, `created_at` with `id` as the tie-breaker, and says so in the pull request.

It adds the cursor as an extra parameter rather than a second handler. When a request carries `cursor`, `FindManyArticle` filters to rows after the encoded `created_at` and `id` and ignores `offset`; when it does not, the existing offset path runs unchanged. The response keeps `articles` and `articlesCount` exactly as `ArticleList` writes them today and adds a `nextCursor` field when there are more rows, built in the router so the serializers in `articles/serializers.go` stay as they are. A cursor that does not decode answers through the same `common.NewError` shape the handler already uses for an invalid parameter.

Then it proves the change the way the repository's CI does. `.github/workflows/ci.yml` runs `scripts/gofmt.sh` and then `scripts/coverage.sh`, which walks every package and runs `go test` with coverage against the SQLite test database at `./data/gorm_test.db`. The agent adds cases to `articles/unit_test.go` next to the existing `/api/articles?limit=5&offset=0` test: walking all pages by cursor returns every article once with no repeats, a filtered list by tag pages the same way, an invalid cursor is refused, and an offset request still returns what it did before. It runs both scripts in the sandbox and fixes whatever they report.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "A customer asked for this feature: add cursor pagination to GET /api/articles while keeping offset pagination working. Return a nextCursor with each page, keep articles and articlesCount as they are, and make the unfiltered list ordered so pages never repeat. Follow the project's existing patterns in articles/routers.go, articles/models.go and articles/unit_test.go, add tests that cover cursor and offset paging, run scripts/gofmt.sh and scripts/coverage.sh, 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 touches `articles/models.go`, where `FindManyArticle` gains the cursor filter and a stable order in every branch, `articles/routers.go`, where `ArticleList` reads `cursor` and writes `nextCursor`, and `articles/unit_test.go` with the new cases. The description is the three bullets the prompt asks for: what the cursor is, how offset requests still behave, and the ordering change on the unfiltered list.

The checks are the repository's own: the CI workflow runs the gofmt check and the coverage script on Go 1.21, 1.22 and 1.23. A reviewer reads the diff for the parts a customer would notice: that a client sending only `limit` and `offset` gets the same JSON keys as before, that the cursor is opaque rather than a raw row id, and that the new order is acceptable for the feed the customer's frontend shows.

Your product reads the result from the session or the webhook: the pull request link, the agent's final answer and the cost of the run. The session also keeps every command the agent ran with its output, so when the customer asks how the feature was tested, the answer is the record of the `go test` runs rather than a summary.

## Run it on every customer feature request

Each request a customer files in your product becomes one call to `POST /o/{org}/sessions` with their repository in `resources` and their spec in `input`. Runs do not share a sandbox, so ten customers asking for ten features on the same afternoon get ten branches and ten pull requests, up to the key's limit on runs in progress.

Give the API key a webhook URL and your backend hears back when each run finishes: the `run.completed` payload carries the session URL and the result, so you can post the pull request link to the customer's ticket without polling. Send an `Idempotency-Key` with the customer's request id so a retried call does not start a second run.

## Why run it on Rush?

An agency or a developer-tools product that builds customer features on its own machines has to check out each customer's code, keep a Go toolchain that matches their `go.mod`, and keep their SQLite test files and branches apart. On Rush each request gets its own sandbox with the repository cloned through the GitHub App, the tests run there, and nothing from one customer's run is visible in another's.

The proof travels with the work. Every run keeps the commands it ran and their output, the pull request carries the customer's own CI, and the session read gives your product the link, the answer and the cost in one place. That is what lets you hand a customer a pull request from an agent and stand behind it.

## Frequently asked questions

### Will existing clients that use limit and offset break?

They should not, and the prompt says so. The offset path in `FindManyArticle` stays the default when no `cursor` is sent, and the response keeps `articles` and `articlesCount`. The new tests include an offset request so a reviewer can see that behaviour pinned.

### Why does the order of the unfiltered list change?

In this repository the unfiltered branch of `FindManyArticle` calls `Offset` and `Limit` with no `Order`, so SQLite may return rows in any order. Cursor pages need a stable order to avoid repeats and gaps, so the agent adds one and names the change in the pull request for the reviewer to accept or adjust.

### Does the run need a database server?

No. The app uses GORM's SQLite driver, and `common/database.go` points tests at `./data/gorm_test.db` unless `TEST_DB_PATH` is set. The agent runs `scripts/coverage.sh` in the sandbox against that file.

### How does my product get the customer's repository into the run?

The customer installs the Rush GitHub App on the repository once; installing an App needs a person on github.com. After that, `GET /o/{org}/github` lists the repositories your key can reach, and you put the one you want in `resources`.

### Can I pass a longer spec, with mockups or an API contract?

Yes. `input` can be a list of blocks: text for the spec and `document` or `image` blocks for the files. The agent finds each file under `/workspace/.rush/attachments/` and its prompt lists them.
