+ It works out why a workload is failing, then opens a pull request.
Yet another LLM SRE operator. It watches a Kubernetes cluster, groups the symptoms of one failure together, finds the manifest in your GitOps repository that produced the object, and proposes a minimal patch with the evidence it rests on. It never changes the cluster.
41 workloads / 14 namespaces
32 incidents, 9 advised
1 pull request
An operator that edits your cluster to fix it creates two problems. The change is invisible to the repository that is supposed to describe the cluster, and the next reconcile undoes it. YALSO does not do that. Every fix it proposes arrives the way any other change does: as a pull request that a human reads and merges.
The consequence is that a wrong answer costs a review, not an outage. That trade is the whole design, and the rest of this page is what follows from it.
--patch '{"spec":…}'
→ reverted by reconcile
→ absent from git history
Nine steps, each producing something the next one reads. The order is the operator's, not a diagram of it.
Citation
Every statement in the analysis must cite a piece of evidence that was actually collected, and every hunk must cite evidence that reaches the field it changes. A finding that cites nothing is not a finding.
Verification
A second model call is shown the analysis and the same evidence and asked whether it holds. Agreement is required, and it is unanimous across every judgement in the reply: one disagreement refuses the analysis.
Minimality
The patch is bounded in hunks and in changed lines, and it may not repeat a change to a field it has already touched. A fix that rewrites a file is not a fix.
A language model will produce a confident answer whether or not it has grounds for one. These exist because that is the failure mode. A refusal is recorded on the incident with its reason, so a gate that fires is visible rather than silent.
UnparseableAnalysis
RulesOverCeiling
PromptOverCeiling
WriteAccessRefused
HelmSourceUnsupported
SourceNotFound
The reason to run this at all is that its blast radius is small, so each boundary below names the mechanism rather than an intention.
| Boundary | How it is enforced |
|---|---|
| Change any object in the cluster except its own records | The ClusterRole grants no write verb outside its own API group, and the operator's own client refuses one regardless. A bug that tried would be stopped twice. |
| Read a credential it was not pointed at | Secrets are read from the operator's own namespace, by name and key, named in a custom resource. The Helm chart never creates a Secret and refuses a values file that looks like it carries one. |
| Send a credential to the model | Manifests, pod status, events and container logs are redacted before they
leave: credential-shaped values, the data of every Secret, and
credential-shaped environment variables. Redaction is pattern based and is
not a guarantee, which is why the model endpoint should be one you control. |
| Open a pull request against a repository you did not list | A resolved repository URL outside the configured list is refused outright. |
helm install yalso ./charts/yalso \ --namespace yalso-system --create-namespace \ --set api.enabled=true
Then apply a YalsoConfig named default naming your model
endpoint and the repositories it may propose against, and read the status conditions.
The chart refuses a values file that oversteps: a key shaped like a credential, a key
that belongs in a custom resource, and a replica count are each rejected at render time
with the reason.
helm test yalso -n yalso-system --logs
the read API over the cluster network
A chart test that passes proves the
operator answers, not that it has
anything to say yet.
Two values decide whether anything outside the cluster can read what the operator
found, and both are off. api.enabled serves the panel and the read API
without a credential, and ingress.enabled publishes them through Traefik.
Turn either on deliberately, and keep ingress.allowedIPs set.
| Key | Default | Description |
|---|---|---|
| Image | ||
| image.repository | ghcr.io/lukaszraczylo/yalso | Image repository. |
| image.tag | "" | Image tag. Empty uses the chart's appVersion. |
| image.pullPolicy | IfNotPresent | Image pull policy. |
| image.pullSecrets | [] | Pull Secrets that already exist in the release namespace. A private image needs one. The chart creates none. |
| imagePullSecrets | [] | Same, at the top level, for a values file written against the common chart convention. Both are honoured. |
| tests.image | busybox:1.37 | Image helm test runs. It needs an HTTP client and nothing else. Point it at a mirror on an air-gapped cluster. |
| Naming | ||
| nameOverride | "" | Override the chart name. |
| fullnameOverride | "" | Override the generated resource name. |
| Permissions | ||
| serviceAccount.create | true | Create the ServiceAccount. |
| serviceAccount.name | "" | Name to use. Empty uses the release fullname. |
| serviceAccount.annotations | {} | ServiceAccount annotations, for workload identity. |
| rbac.create | true | Install the ClusterRole and its binding. Turn it off only when the permissions are managed elsewhere. |
| rbac.allowClusterMutation | false | Add write verbs on foreign workloads to the ClusterRole. The operator's own client refuses them regardless, so this grants permission the code still declines to use. |
| leaderElect | false | Enable leader election. Also renders a Role granting the lease permission, which the ClusterRole deliberately omits. |
| The read API and panel | ||
| api.enabled | false | Serve the read API and the panel. Nothing served is authenticated, so what can reach the port is the whole of its protection. Turn it on together with a NetworkPolicy. |
| api.port | 8082 | Port the API and panel listen on. |
| api.service.enabled | true | Create a Service. This is what a browser or another tool connects through. |
| api.service.type | ClusterIP | Service type. |
| api.service.port | 8082 | Service port. |
| api.service.annotations | {} | Service annotations. |
| Ingress (Traefik) | ||
| ingress.enabled | false | Publish the panel and API through a Traefik IngressRoute. Requires api.enabled, because there is nothing to route to otherwise. |
| ingress.host | "" | Hostname to route. The only value with no sensible default. |
| ingress.entryPoints | websecure | Traefik entry points. |
| ingress.tls.secretName | "" | An existing certificate Secret in the release namespace. Empty falls back to Traefik's default certificate. |
| ingress.middlewares | [] | Middlewares, applied in order. Each needs the namespace it lives in, because a Traefik middleware is namespaced. |
| ingress.allowedIPs | 10.0.0.0/22 172.16.0.0/22 | Restrict the route by client address. The panel is unauthenticated, so this is the only thing between it and whoever resolves the host. An empty list removes the restriction. |
| ingress.annotations | {} | IngressRoute annotations. |
| Metrics and health | ||
| metrics.enabled | true | Serve Prometheus metrics. |
| metrics.port | 8080 | Metrics port. |
| metrics.service.enabled | true | Create a Service, which a scraper such as a ServiceMonitor needs. |
| metrics.service.type | ClusterIP | Service type. |
| metrics.service.port | 8080 | Service port. |
| metrics.service.annotations | {} | Service annotations. |
| healthProbe.port | 8081 | Health and readiness port. |
| Runtime | ||
| workspace.sizeLimit | 1Gi | Size of the clone directory. The root filesystem is read only, so this is the one writable mount. Exceeding it evicts the pod, so raise it for a large monorepo. |
| resources | see values.yaml | Requests and limits. No CPU limit by default: analysis is bursty, and throttling it lengthens an incident rather than protecting the node. |
| podSecurityContext | see values.yaml | Runs as user 65532, non-root, with the RuntimeDefault seccomp profile. |
| securityContext | see values.yaml | Read-only root filesystem, no privilege escalation, all capabilities dropped. |
| terminationGracePeriodSeconds | 30 | Time an in-flight analysis has to stop cleanly. |
| Scheduling | ||
| podAnnotations | {} | Pod annotations. |
| podLabels | {} | Pod labels. |
| nodeSelector | {} | Node selector. |
| tolerations | [] | Tolerations. |
| affinity | {} | Affinity. |
| priorityClassName | "" | Priority class. |
These fail the render with the reason rather than being ignored.
| Kind | What it holds |
|---|---|
| YalsoConfig | The whole configuration: model endpoints, detectors, repositories, gates, retention, notifications. One object named default. |
| YalsoIssue | One symptom, with its fingerprint, first and last sighting, and how many times it has been seen. |
| YalsoIncident | One correlated group and every decision made about it: the analysis, the gates, the patch, the pull request and its outcome. |
| YalsoRuleSet | Instructions you write that shape the analysis. They are quoted in the pull request, so a reader can tell which rules produced a proposal. |
| YalsoLesson | What the operator concluded from what humans did with its pull requests. Applied only when you approve it. |
The Kubernetes API already serves the records themselves to anything holding a kubeconfig. This serves the layer above them, so every consumer is not deriving it again and drifting apart.
| Path | Answers |
|---|---|
| / | the panel: a timeline of every incident, its analysis, and how incidents relate |
| /api/v1/snapshot | every record in one consistent response |
| /api/v1/summary | the counts |
| /api/v1/issues | symptoms, filterable by namespace, detector and phase |
| /api/v1/incidents | correlated groups; the filters match any member |
| /api/v1/groups | how incidents relate to one another |
| /openapi.json | the specification, held to the routes by a test |
It stays off unless you give it an address. Put a NetworkPolicy in front of it.
It is a retention window, not a history
Resolved issues and finished incidents are pruned on the schedule the configuration sets. Nothing can answer a question about a record that has already expired, and the API says so in its own description.
It can only patch what a manifest declares
A rendered Helm chart committed to a repository is patchable, because the object is a file. A chart rendered at deploy time is not: a patch to rendered output does not survive the next render, so the operator declines it and explains instead.
→ rendered at deploy time
SourceNotFound
→ no manifest declares it
Because the reasoning came from a language model, and a reviewer who cannot check it is being asked to trust it. Every proposal carries the evidence each statement cites, the gates that ran, the hashes of the evidence and the rules that produced it, and a section headed Not claimed.
that anything was applied
that either gate can tell whether
the fix is the right one
github.com/lukaszraczylo/yalso — MIT licensed. Written in Go on controller-runtime, distributed as a multi-architecture image and a Helm chart.