What the agent does
The agent starts from the Linear issue, which says which content editors need to export, then reads how the admin is built. In a site like wagtail/bakerydemo, requirements/base.txt pins wagtail>=8.0,<8.1 on Django>=6.0,<6.1, runtime.txt says Python 3.12.1, and the admin is customised in wagtail_hooks.py files rather than with @register_snippet on models. bakerydemo/breads/wagtail_hooks.py registers a BreadMenuGroup holding a BreadPageListingViewSet for BreadPage, with a BreadPageFilterSet on origin, bread_type and ingredients and a custom origin column in place of parent, plus BreadIngredientSnippetViewSet, BreadTypeSnippetViewSet and a CountryModelViewSet built on ModelViewSet. None of them exports anything.
One viewset in the same codebase already does, and it is the pattern to copy. bakerydemo/base/wagtail_hooks.py gives PersonViewSet a list_export = ("first_name", "last_name", "job_title"), which is all a SnippetViewSet or ModelViewSet needs for Wagtail to add the download buttons to its index view and answer ?export=csv and ?export=xlsx. So the agent adds list_export to the three bread viewsets: name and live for BreadIngredient, which carries DraftStateMixin, title for BreadType, and title and sort_order for Country, keeping the existing list_display, filters and inspect_view_enabled settings as they are.
BreadPage is a page, not a snippet, and the issue wants one file of all breads with their origin, type and ingredients, so the agent adds a report. It writes a BreadPageReportView on Wagtail's PageReportView in bakerydemo/breads/views.py, whose queryset is live BreadPage objects with select_related on origin and bread_type and prefetch_related on ingredients, with list_export naming title, origin, bread_type, a method that joins the ordered ingredient names, and url, and export_headings for the human labels. It registers the view with register_admin_urls and adds it to the Reports menu with register_reports_menu_item in breads/wagtail_hooks.py, which is the documented pair of hooks for a custom report, so the download buttons and the export query parameter come from the same mixin the snippet listings use.
Tests follow bakerydemo/base/tests/test_home_page.py: a WagtailPageTestCase that creates a superuser in setUp, logs in with the test client and asserts on responses. The new breads/tests module creates a Country, a BreadType, two BreadIngredient rows and a BreadPage under a BreadsIndexPage, then requests the ingredient listing and the report with ?export=csv, asserting a text/csv content type, the heading row and one data row with the joined ingredients, and once more with ?export=xlsx for the content type. It runs what .github/workflows/ci.yml runs with DJANGO_SETTINGS_MODULE=bakerydemo.settings.test: ./manage.py check, ./manage.py makemigrations --check --noinput, ./manage.py test, and make lint-server, which is ruff format --check, ruff check, curlylint and djhtml --check, then opens the pull request.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Add CSV and XLSX export to this Wagtail admin as the linked issue asks. Read requirements/base.txt, bakerydemo/breads/models.py, bakerydemo/breads/wagtail_hooks.py and bakerydemo/base/wagtail_hooks.py, where PersonViewSet already uses list_export. Add list_export to BreadIngredientSnippetViewSet, BreadTypeSnippetViewSet and CountryModelViewSet with the fields editors would expect. Add a report view on PageReportView in bakerydemo/breads/views.py listing live BreadPage objects with title, origin, bread type, the ordered ingredient names joined, and url, with list_export and export_headings, and register it with register_admin_urls and register_reports_menu_item. Write tests in the style of bakerydemo/base/tests/test_home_page.py that create the models, log in as a superuser, request a snippet listing and the report with export=csv and export=xlsx, and assert the content type, the headings and a data row. Run ./manage.py check, ./manage.py makemigrations --check --noinput and ./manage.py test with DJANGO_SETTINGS_MODULE=bakerydemo.settings.test, then make lint-server. Open a pull request that links the issue, lists the files and quotes the test and lint output.",
"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 a pull request on its own branch that links the Linear issue. It changes bakerydemo/breads/wagtail_hooks.py, adds bakerydemo/breads/views.py and a bakerydemo/breads/tests module, and touches no model and no migration, which makemigrations --check confirms in the quoted output. The description lists each viewset and the fields it now exports, describes the report's columns and where it appears in the Reports menu, and quotes the manage.py test and make lint-server output from the sandbox.
The session read names the repository and the pull request URL. The transcript at GET /o/{org}/sessions/{id}/messages keeps every command: the install from requirements/development.txt, the Django checks, each test run including any failing first attempt, and the lint. A reviewer can see what the agent ran without checking the branch out.
Before merging, a reviewer loads the Reports menu on a review deploy and downloads the file once, checks that the ingredient join respects the ordered_ingredients order the model defines, and confirms the report queryset uses live() so drafts stay out of the export. If editors also need the export on the BreadPage listing itself, that is a follow-up on the same session.
Run it on every Linear issue labelled export
Add an automation to the Project with this prompt and a linear trigger on linear.issue.labeled with export in its labels. Each labelled issue starts one run in the Project's repository, and the trigger adds the issue after the prompt, so the agent reads which models and columns editors asked for from the issue and the prompt text stays as written. A label added by a Linear app integration never fires it, and the automation starts at most max_runs_per_hour runs in an hour.
From your own server, the same run starts with this request and https://linear.app/<workspace>/issue/<ID> added to resources, read through your Linear connection. Give the key a webhook URL and Rush POSTs run.completed with data.session_url when the turn ends, so a handler can post the pull request link back on the issue.
The use case this job follows, with its automation and trigger: Turn a Linear label into a PR.
Why run it on Rush?
This job is small in code and large in setup: a virtualenv from requirements/development.txt, a database for the test suite, a logged-in admin to click the download button, and three linters that CI runs. The agent does all of it in its own sandbox and loads the report in its browser before opening the pull request, so the first review sees a file that was downloaded, not a diff that should produce one.
The proof is the pull request plus the transcript. The exact manage.py test output, the makemigrations --check result that shows no schema change and the lint run are kept on the session, and several export issues can run at once, each in its own sandbox with its own pull request.
Frequently asked questions
Does this add a migration?
No. list_export on a viewset and a report view are admin configuration, not model fields, so ./manage.py makemigrations --check --noinput passes unchanged and the pull request says so with the command's output.
Why a report for BreadPage instead of exporting the page listing?
The bread pages are listed through a PageListingViewSet, and the issue asks for one file with related data joined in, the origin, type and ingredients. A PageReportView with list_export is the documented way to ship exactly that set of columns under the Reports menu, using the same export mixin the snippet listings use.
Will drafts or unpublished breads be in the file?
Not from the report, whose queryset is filtered with live(). The ingredient listing exports whatever the index shows, and BreadIngredient has DraftStateMixin, so its export includes a live column so editors can tell the two apart.
What does it run before opening the pull request?
The same steps as .github/workflows/ci.yml: ./manage.py check, ./manage.py makemigrations --check --noinput and ./manage.py test with the test settings, then make lint-server, which runs ruff format --check, ruff check, curlylint and djhtml --check. The output of each is quoted in the description.