# Add Horizontal Pod Autoscalers to Kubernetes Manifests from a Linear Issue

Yes. An agent reads the labelled Linear issue, adds a Kustomize component with one HPA per stateless Deployment, renders and validates it, and opens a PR.

[Run this on Rush](https://cloud.getrush.ai/run/add-horizontal-pod-autoscaler/kubernetes/), in a Project that has your repository.

## What the agent does

The agent reads how the manifests are organised before it writes one. In a repository shaped like GoogleCloudPlatform/microservices-demo, `kustomize/base/` holds one file per service, eleven in all, from `adservice.yaml` to `shippingservice.yaml`, listed in `kustomize/base/kustomization.yaml`, and `kustomize/kustomization.yaml` composes the base with a commented list of optional components. `kustomize/components/` already has fifteen of them, from `network-policies` and `memorystore` to `without-loadgenerator`, each a `kind: Component` on `kustomize.config.k8s.io/v1alpha1`, and `kustomize/README.md` describes every one in a list the agent extends. `kustomize/tests/` holds three combinations such as `memorystore-with-all-components`, and `.github/workflows/kustomize-build-ci.yaml` runs `kubectl kustomize .` on the base and `kustomize build` on each test folder for every pull request that touches `kustomize/`.

The Deployments are ready for autoscaling. `kustomize/base/frontend.yaml` requests 100m of CPU and 64Mi of memory with limits of 200m and 128Mi, `adservice.yaml` requests 200m and 180Mi, and every other service sets requests too, which is what a CPU-utilisation target needs; none sets `replicas`, so the Deployments default to one. `cartservice.yaml` also carries the `redis-cart` Deployment, which holds the cart state in memory and must not scale, and `loadgenerator.yaml` exists to produce load rather than serve it. A duplicate copy of every file lives in `kubernetes-manifests/`, and the agent leaves it alone because the issue asks for an opt-in component.

The change is one component. `kustomize/components/horizontal-pod-autoscaling/` gets a `kustomization.yaml` of `kind: Component` and one `hpa-<service>.yaml` per stateless Deployment, each an `autoscaling/v2` `HorizontalPodAutoscaler` whose `scaleTargetRef` names the Deployment, with the `minReplicas`, `maxReplicas` and CPU utilisation target the issue sets, and no entry for `redis-cart` or `loadgenerator`. A `README.md` in the component follows the pattern of the `network-policies` one and states that the metrics server must be present and that `replicas` must not be pinned elsewhere, and the component is added to the list in `kustomize/README.md` and as a commented line in `kustomize/kustomization.yaml` in the same place the others sit.

Validation is done the way the CI does it, plus a schema check. The agent runs `kubectl kustomize kustomize/` with the component enabled and reads the rendered output for one `HorizontalPodAutoscaler` per intended Deployment and none for the two excluded, then runs `kustomize build` on each folder under `kustomize/tests/` after adding the component to those kustomizations so the existing combinations keep building with it. It runs `kubeconform` against the rendered YAML so the `autoscaling/v2` objects are checked against the API schema without a cluster, and it pushes only when every build and the schema check pass.

## Start it with one API call

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

```json
{
  "agent": "claude",
  "input": "A Linear issue labeled agent says: \"Add an opt-in Kustomize component with HorizontalPodAutoscalers for every stateless service: minimum 1 replica, maximum 5, target 70% CPU. Do not autoscale redis-cart or the load generator.\" Read kustomize/kustomization.yaml, kustomize/base/kustomization.yaml, every Deployment under kustomize/base, kustomize/components/network-policies, kustomize/README.md, the folders under kustomize/tests and .github/workflows/kustomize-build-ci.yaml first. Create kustomize/components/horizontal-pod-autoscaling with one autoscaling/v2 HPA per stateless Deployment and a README, add it to the README list and as a commented line in kustomize/kustomization.yaml, add it to the test combinations, render with kubectl kustomize, build every test folder, validate the rendered YAML with kubeconform, and open a pull request that links the issue and lists which Deployments are scaled and which are not.",
  "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 its own branch. The diff adds `kustomize/components/horizontal-pod-autoscaling/` with its `kustomization.yaml`, one `hpa-<service>.yaml` per stateless Deployment and a `README.md`, adds the component to `kustomize/README.md` and as a commented line in `kustomize/kustomization.yaml`, and adds it to the kustomizations under `kustomize/tests/`. `kustomize/base/` and `kubernetes-manifests/` are untouched. The description quotes the issue, lists the scaled Deployments and the two left out with the reason for each, and shows the rendered HPA for `frontend` as an example.

The checks are the proof. The `kustomize-build-ci` workflow runs on the pull request because it touches `kustomize/`, rendering the base and building every test folder, now with the component in them. The session's trajectory at `GET /o/{org}/sessions/{id}/messages` keeps the `kubectl kustomize` output, each `kustomize build` and the `kubeconform` result, so a reviewer reads the rendered objects rather than trusting a description.

Before merging, a reviewer checks that no scaled Deployment has `replicas` set in an overlay they use, since a pinned count fights the autoscaler, that the cluster runs a metrics server so the CPU target has a source, and that the limits in the base leave headroom for the target utilisation. Nothing is applied to any cluster, and nothing reaches main until the pull request merges.

## Run it on every Linear issue labelled agent

Sign in to Linear once with `POST /me/connections {"type": "app", "name": "linear"}` and finish the authorize URL in the console. Then add an automation to the Project with this prompt and a `linear` trigger on `linear.issue.labeled`, naming the label in `config.labels`. The trigger adds the issue after the prompt and fills in no placeholder, so the agent reads the replica bounds and the target from the issue itself. The automation's `max_runs_per_hour`, between 1 and 30, caps how many labelled issues it takes on in an hour, each in its own sandbox with its own pull request.

From your own server instead, a handler that receives Linear's webhook for the label sends `POST /o/{org}/sessions` with the repository and `https://linear.app/<workspace>/issue/ENG-42` in `resources`, which puts the issue text in the prompt through your Linear connection, with a `request_id` derived from the issue id so a redelivered webhook returns the run it already started. Give that key a `webhook_url` and Rush POSTs `run.completed` or `run.failed` when the turn ends, with `data.session_url` to read for the pull request link.

## Why run it on Rush?

Manifest changes are judged on what they render, and rendering every combination under `kustomize/tests/` plus a schema check is a loop nobody enjoys on a laptop with a live kubeconfig one mistyped flag away. On Rush the agent works in its own sandbox with no cluster credentials, runs `kubectl kustomize`, `kustomize build` and `kubeconform` there, and pushes only when the output has one autoscaler per intended Deployment and the schema check is clean.

The pull request carries its own evidence: the component files, the README, the rendered example in the description, and the repository's `kustomize-build-ci` workflow running on it. The trajectory keeps every render and its output. A second issue, for a different target or a different set of services, is a second sandbox and a second pull request in parallel.

## Frequently asked questions

### Why a component rather than editing the base?

Because that is how this repository handles every optional change: `kustomize/components/` has fifteen of them and `kustomize/kustomization.yaml` lists each as a commented line to enable. A component keeps the base deployable on a cluster without a metrics server and lets `kustomize/tests/` build the combinations the CI checks. The issue asks for opt-in, and the pull request delivers exactly that.

### Which services are not autoscaled?

`redis-cart`, the in-memory cart store defined in `cartservice.yaml`, because scaling it would split the cart data across pods, and `loadgenerator`, which generates traffic rather than serving it. The description names both and the reason. Every other Deployment under `kustomize/base/` gets an autoscaler.

### Does the cluster need anything for this to work?

A metrics server, so the `autoscaling/v2` controller can read CPU utilisation, and CPU requests on the containers, which every Deployment under `kustomize/base/` already sets. The component's README states both. Without metrics the autoscalers sit at `minReplicas` and report that they cannot read the metric.

### Is anything applied to a cluster?

No. The sandbox has no kubeconfig and the prompt asks only for rendering and validation. `kubectl kustomize` and `kustomize build` work from the files alone, and `kubeconform` checks the rendered YAML against published schemas offline. The pull request is the only output.

### What about the kubernetes-manifests directory?

It is a plain copy of the base for `kubectl apply -f` users and has no component mechanism, so the agent leaves it alone and says so in the description. If you want autoscalers there too, a follow-up issue can add the same objects as plain files.
