What the agent does
The agent clones the repository into its own cloud sandbox, installs Elixir and Postgres, and brings the app up. In an Elixir app like hexpm, the Phoenix app behind hex.pm, it reads mix.exs (Elixir ~> 1.20, ecto_sql, postgrex, oban ~> 2.23) and the ecto.setup alias, which resets the database and runs priv/repo/seeds.exs. The schema is loaded from priv/repo/structure.sql, so the agent starts from exactly the tables, materialized views and indexes production has, including package_downloads and release_downloads, which are materialized views.
The repository's seeds are a starting set, not a load test, so the agent writes a seeding script that uses the app's own schemas to create tens of thousands of packages with many releases each, dependencies between them so package_dependants has rows, and download counts, and then refreshes the materialized views. It sets Postgres log_min_duration_statement = 50, enables pg_stat_statements, and exercises the main pages: the package list sorted by name, recent and downloads, a search, a package page and its dependants.
For each slow query it finds the Ecto code that builds it. In hexpm most package queries are composed in lib/hexpm/repository/package.ex: the downloads-sorted list pages ids through downloads_join and Hexpm.Utils.paginate in a subquery before joining back to Package, dependants use dependant_ids_query and dependency_exists_query over PackageDependant, ordered by downloads DESC NULLS LAST, and search goes through like and fragment clauses over name and meta. The agent reads the schema's indexes beside each one, such as packages_name_trgm (a gin_trgm_ops index), package_dependants_dependency_id_dependant_repository_id_index with INCLUDE (package_id) and package_downloads_view_downloads_package_id_idx, and explains why the planner used or skipped them.
It runs EXPLAIN (ANALYZE, BUFFERS) on each slow query, then tries a fix on the seeded data: an index created in the sandbox, a rewritten Ecto query, a preload changed to a join or the reverse, or a narrower select. It measures each change on the same data and keeps both times. It also counts repeated queries per page to catch preloads issued per row. The repository is left as it was.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Load this app in the sandbox with Postgres, the schema from priv/repo/structure.sql and a realistic amount of seed data (tens of thousands of packages with releases, dependencies and download counts; refresh the materialized views). Set log_min_duration_statement to 50 ms, exercise its main pages, and write slow-queries.md: each query over 50 ms, the Ecto code that builds it with file and line, its EXPLAIN ANALYZE, the indexes the planner used or skipped, and the index or rewrite you tried with the time before and after on the same data. Do not open a pull request.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- 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 slow-queries.md as an artifact on the session, which the session read lists with a download link. It starts with the seeded row counts and the pages exercised, then gives each slow query its own section: the SQL as Postgres logged it, the Ecto function that composes it with file and line, the plan, the indexes involved, and the change that was tried with its measured time before and after.
Queries that are fast alone but repeated per row are grouped separately, with the page and the preload that causes them. The session's transcript keeps the seeding script, every mix and psql command, the log excerpts and every plan, so any number in the report can be reproduced by rerunning the same step.
Before taking a change, a reviewer checks the seed data's shape against production, weighs a new index against write cost on tables the publish path writes, and checks that a rewritten query returns the same rows. A follow-up message to the same session can then ask the agent to add the migration and the query change and open a pull request.
Run it on every schema migration merged
Query plans change when the schema does, so run it when a migration lands. A CI job that sees a new file under priv/repo/migrations can send this request, and the report says whether any page got slower before the change ships.
Set a webhook URL on the API key and Rush POSTs run.completed when the turn finishes, with the session_url. Your handler takes slow-queries.md from the session's artifacts and posts it on the merged pull request for the author to read.
The use case this job follows, with its automation and trigger: Investigate an incident.
Why run it on Rush?
Profiling Ecto queries needs the real schema, a lot of data and a running app. Doing that on a laptop means a local Postgres with a large seed, refreshed materialized views and changed logging settings that then have to be undone. In a Rush sandbox the agent sets all of it up on its own machine and discards it when the run ends.
The report carries a plan and a measured before and after for every finding, and the transcript keeps the commands that produced them. Separate apps or separate parts of one app can be profiled at the same time, one run each, up to the key's concurrent run limit.
Frequently asked questions
Does it use my production data?
No. It loads your schema from priv/repo/structure.sql and seeds its own data in the sandbox. If you want it to start from your real slow queries, attach a pg_stat_statements export to the request.
Does it understand Ecto composition?
Yes. It follows the query from the controller through the context functions that add where, join, order_by and preload clauses, so the report points at the function that adds the slow part, not only at the final SQL.
What about materialized views?
The agent refreshes them after seeding, because a list sorted by downloads reads package_downloads, and an empty view would hide the cost. The report says when each was refreshed.
Will it change my code?
Not in this run. It measures changes in the sandbox and reports them. A follow-up message to the same session can ask for the migration and the query change in a pull request.