What the agent does
The agent reads the Linear issue and its comments, then maps the ask onto the app's folders before it writes anything. In an app like antfu-collective/vitesse, a feature is spread across src/pages/ (file-based routes, with the layout chosen in each page's <route lang="yaml"> block), src/layouts/ (default.vue, home.vue, 404.vue), src/components/ (auto-registered), src/composables/ and src/stores/ (auto-imported through unplugin-auto-import as vite.config.ts configures), and src/modules/, where i18n.ts, pinia.ts, pwa.ts and nprogress.ts each export an install that src/main.ts applies to the ViteSSG app. A request for a new screen is a new file under src/pages/; a request for shared state is the Pinia store in src/stores/user.ts; a request for new copy is keys in locales/en.yml, since templates render text through t().
It follows the conventions the files already show. Styling is UnoCSS attributes on elements, as TheInput.vue sets p="x-4 y-2" and border="~ rounded gray-200 dark:gray-700", and icons are classes such as i-carbon-campsite. Pages set their title with useHead and translate it with t(), as index.vue and hi/[name].vue do. A new component is written as <script setup lang="ts"> with defineProps, like TheCounter.vue, and becomes usable in any template without an import once src/components.d.ts is regenerated. The dark toggle is toggleDark from src/composables/dark.ts, and the language switch is toggleLocales in TheFooter.vue, which cycles availableLocales from src/modules/i18n.ts. Anything the issue asks for that touches window or document must guard for server rendering, because vite-ssg build renders every page at build time.
The test goes where the behaviour can be seen. A store or composable change gets a Vitest case in test/ with a fresh Pinia, matched by test/**/*.test.ts in a jsdom environment. A component gets a mount test in the style of test/component.test.ts, which passes props, reads wrapper.text(), triggers clicks on .inc and .dec and keeps a snapshot in test/__snapshots__/. A new page or navigation gets a case in cypress/e2e/basic.spec.ts, which visits http://localhost:3333, asserts exact URLs, types into #input and clicks [data-test-id="about"]; a new interactive element gets a data-test-id for that reason. The agent writes the smallest test that proves the issue's acceptance criteria and names the layer in the pull request.
It runs what .github/workflows/ci.yml runs, with pnpm@10.30.2 from packageManager and pnpm install --frozen-lockfile: pnpm run lint, pnpm run typecheck (vue-tsc --noEmit), pnpm run test:unit --run and pnpm run build, and for a page change npx vite preview --port 3333 with cypress run. It commits any regenerated src/components.d.ts, src/auto-imports.d.ts or src/route-map.d.ts, because typecheck on a clean checkout fails without them. Then it opens a pull request whose description links the Linear issue, lists the files by folder and quotes each result. The run does not comment on the Linear issue; your handler posts the link back.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "codex",
"input": "Implement the Linear issue given as a resource in this Vue 3 app built with Vite, file-based routes in src/pages, layouts in src/layouts, auto-imported components, composables and a Pinia store, vue-i18n locales in locales/*.yml and static generation through vite-ssg. Read the issue and its comments first. Put the change in the folder the convention names, follow the existing style (script setup with TypeScript, UnoCSS attributes, text through t() with keys in locales/en.yml, a data-test-id on new interactive elements), and guard any window or document access for server rendering. Add the smallest test that proves the acceptance criteria: a Vitest case in test/ with mount from @vue/test-utils and a fresh Pinia, or a case in cypress/e2e/basic.spec.ts for a new page or navigation. Use pnpm 10.30.2 and pnpm install --frozen-lockfile, then run pnpm run lint, pnpm run typecheck, pnpm run test:unit --run and pnpm run build; for a page change also serve npx vite preview --port 3333 and run cypress run. Commit regenerated .d.ts files. Open a pull request whose description links the issue, lists the files by folder and quotes each result. Do not merge.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
},
{
"url": "https://linear.app/your-org/issue/ENG-42"
}
]
}- 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 with the Linear issue linked in its description. The diff is grouped by folder: a page or layout, a component with its regenerated entry in src/components.d.ts, a store or composable, keys in locales/en.yml, and one test in test/ or cypress/e2e/. The description quotes pnpm run lint, pnpm run typecheck, pnpm run test:unit --run and pnpm run build, and for a page change the cypress run summary with its browser.
A reviewer checks that the folders agree: a new page has its layout block and its useHead title, a new string has a key rather than a literal, a new control has a data-test-id, and nothing reads window during the SSG build. They check the test fails without the change and that the snapshot in test/__snapshots__/ changed only if a template did. The push runs ci.yml on GitHub, so lint, typecheck, both test rows and test-e2e confirm what the sandbox ran.
The session holds the issue text the run read and every command. GET /o/{org}/sessions/{id} returns the pull request once the turn has finished; your handler reads it there or from the run.completed webhook and comments the link on the issue.
Run it on every Linear issue labelled agent
Create an automation in the Project for this repository with this prompt and a linear trigger on linear.issue.labeled, with the hand-off label in config.labels and your team keys in config.teams. Each labelled issue starts one run with the issue as its context on its own branch; a label added by an app integration never fires it. Leave the example issue URL out of the automation's prompt, since the trigger supplies the issue.
For a two-step flow, add a linear.issue.state_changed trigger whose config.states maps a review state to plan and a later state to exec. The plan run reads the pages, store and locales the issue touches, changes nothing and posts its plan as a comment on the issue; the exec run opens the pull request once someone has read it.
The use case this job follows, with its automation and trigger: Turn a Linear label into a PR.
Why run it on Rush?
A small Vue feature in this template touches a page, a locale file, a generated declaration and often a store, and proving it means pnpm 10.30.2, vue-tsc, Vitest, an SSG build and a browser running Cypress against vite preview. A Rush run does that in its own sandbox with a browser, from a clean clone, and keeps the output of every command, so the pull request can quote the checks instead of promising them.
The change lands in the right folder because the agent reads the conventions in vite.config.ts, src/main.ts and the existing pages before editing. Several labelled issues run at once, each on its own branch with its own build, and each ends as a linked pull request whose own four CI jobs confirm it, or as a comment on the issue saying what was unclear.
Frequently asked questions
How does the run read the Linear issue?
Through the https://linear.app/<workspace>/issue/<ID> entry in resources, which puts the issue in the prompt through your Linear connection, or through the linear trigger, which appends the issue after the prompt. An issue your Linear connection cannot reach answers 422 RESOURCE_NOT_ACCESSIBLE before a sandbox starts.
Will it add a new route?
By adding a file under src/pages/, which the Vue Router plugin in vite.config.ts turns into a route and records in src/route-map.d.ts. A dynamic segment is a bracketed filename like hi/[name].vue, and the page chooses its layout in a <route lang="yaml"> block.
Does new text go straight into the template?
No. Templates render text through t() with keys in locales/en.yml, loaded by src/modules/i18n.ts. The agent adds the key there, and to another locale file only when the issue names that language; the rest fall back.
Does the run comment the pull request link on the Linear issue?
No. The run reads the issue and the pull request links it, but posting back is your handler's job: read the pull request from the session or the run.completed webhook and comment it through your own Linear integration.
What if the issue asks for something that needs a browser API?
vite-ssg build renders every page on the server, so the agent guards window and document behind isClient or typeof document !== 'undefined', as src/modules/i18n.ts and src/modules/pinia.ts already do, and the build step in CI is what proves it.