All jobs

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 RushIn 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 with this body:

{
  "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.

The use case this job follows, with its automation and trigger: Turn a Linear label into a PR.

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.