SRE agent
The rest of this section is about producing signals. This page is about the thing that consumes them: an alert firing at 03:00 normally reaches a human who then starts from nothing — reading the alert, opening dashboards, correlating a deploy, remembering whether this happened before.
RunLore does that first pass. It receives the Alertmanager webhook, investigates against the live cluster, posts a finding to Slack, and — when the investigation taught it something — opens a pull request against a knowledge-base repository so the next occurrence starts from the answer rather than from nothing.
rbac.allowActions: false and the
actions config block is omitted entirely, which means mode off. RunLore
has no patch rights on Flux resources and no ability to change cluster state.
Its only writes anywhere are pull requests and issues on its own knowledge-base
repository. It investigates and reports; a human still decides and acts.What happens to an alert
- A
VMRulefires and Alertmanager routes it to therunlorereceiver. Onlyseverity: criticalis matched (triggers.incidents.match), with a 30-minute dedup window on the alert fingerprint. FluxReady=Falseconditions are a second, independent source. - The webhook is authenticated. RunLore fails closed: with a model
configured, the alert webhook must carry a bearer token, because alert
labels and annotations flow into the model prompt and therefore bill it.
The token comes from an External Secret and Alertmanager posts it via
authorization.credentials_file. - Recall runs first. The knowledge base is searched for a matching past
investigation. On a high-confidence match the loop short-circuits — no
full investigation, no model spend. This is the payoff of everything else
on this page, and it is opt-in (
instant_recall.enabled: true). - Otherwise it investigates, correlating Kubernetes object state, the
GitOps change history, metrics from VictoriaMetrics and logs from
VictoriaLogs — multi-source root cause, not just “what does
kubectl getsay”. - It posts the finding to Slack in
#alerts, with 👍/👎 feedback buttons. - It drafts a knowledge-base PR, unless the verdict was
no_action— those are suppressed after pilot data showed 72% of drafted PRs over six days were closed as noise.
A trigger answered conclusively less than 30 minutes ago is not re-investigated
(recurrence_cooldown). That deliberately defers to humans: an inconclusive
verdict always retries, and a standing 👎 on the previous answer re-arms
investigation immediately.
The knowledge base
The catalog is a separate repository, Smana/runlore-kb,
git-synced into the pod every 5 minutes at /var/lib/runlore/catalog — as a
writable mirror, so the loop is closed in both directions: recall reads
it, curation pushes pull requests to it through a GitHub App.
Alongside it sits an outcome ledger (outcomes.jsonl), which is what
turns accumulation into something closer to learning. A recall that never
resolves is rejected by the decay gate rather than being recommended forever,
and a 👎 in Slack feeds the same ledger.
Recall confidence is gated by an LLM reranker on a calibrated 0–1 match score rather than a raw BM25 magnitude — which is what removed the need to hand-tune scoring thresholds per corpus on this cluster.
The model
GLM 5.2, reached over Z.ai’s OpenAI-compatible API (provider: openai plus a
base_url — any provider name other than anthropic or gemini falls back
to the OpenAI wire protocol). The key arrives as GLM_API_KEY from an
External Secret; no credential is ever inlined.
How it is deployed
| Chart | deploy/helm/runlore from the runlore GitRepository |
| Source | flux/sources/gitrepo-runlore.yaml, pinned to tag v0.13.0 |
| Image | 0.13.0 — signed, multi-arch, SBOM-attested |
| Workload | StatefulSet, replicaCount: 2, leader election |
| Storage | one gp3 RWO 1Gi PVC per replica, via volumeClaimTemplates |
| Identity | EKS Pod Identity xplane-runlore bound to the runlore ServiceAccount |
| Kustomization | observability |
GitRepository ref, and RunLore parses its config with
KnownFields(true) — so a chart newer than the binary emits config keys the
binary rejects and serve fails closed on startup. That is not a partial
upgrade, it is an outage: a v0.11.0 chart against a 0.9.2 binary crashlooped
both replicas on field sweeps not found in type config.Curate for as long as
the drift lasted. Note also that the image tag carries no leading v
(0.13.0, not v0.13.0) — GoReleaser publishes it that way, and a v prefix
lands the StatefulSet in ImagePullBackOff.The GitRepository lives under flux/sources/ rather than beside the
component under observability/, for the reason described in
Repository Structure:
a source placed under an app-owned directory inherits that directory’s
sharding.fluxcd.io/key label, and the default shard’s controllers then
cannot see the HelmChart derived from it — surfacing as “source not found”
on some unrelated HelmRelease, far from the actual cause.
Network posture
- One public route, one path. The
HTTPRouteexposes onlyPOST /slack/interactionsonrunlore.<domain>, so Slack can deliver button clicks; every request is HMAC-verified against the signing secret with a ±5 minute replay window./webhook/alertmanager,/metricsand/actions/*deliberately have no route and the gateway answers 404. - Ingress is scoped to the
observabilitynamespace — vmagent scrapes/metrics, Alertmanager and VMAlert post the incident webhook. Left empty, the chart emits nofrom:at all and every pod in the cluster could post to the webhook. - Two extra egress ports. The chart’s default egress opens only 443 and 6443, but VictoriaMetrics listens on 8428 and VictoriaLogs on 9428 — without those, the agent’s metrics and logs tool calls hang until they time out and every investigation runs half-blind against its own deadline.
- The EKS Pod Identity endpoint (
169.254.170.23:80) is allowed as thehostentity, which is required on Cilium — see Policies.
Its own observability
RunLore is scraped like anything else (vmServiceScrape, 30s, /metrics) and
carries a Grafana dashboard plus 12 VMRule alerts on its own health:
RunloreAgentDown, RunloreNoActiveLeader, RunloreMultipleLeaders,
RunloreInvestigationsDropped, RunloreInvestigationThrottlingSustained,
RunlorePipelineStalled, RunloreInvestigationErrors,
RunloreToolErrorRateHigh, RunloreModelErrorRateHigh,
RunloreModelLatencyHigh, RunloreSlowResolution and
RunloreInvestigationCostHigh — the last of which is the one worth having:
an agent that investigates in a loop is a cost surface, not only a reliability
one. See Dashboards & Alerts.
Known gaps
- Network signals are deferred. The
network:source needs a Hubble or flow-log provider choice, so investigations correlate Kubernetes state, GitOps history, metrics and logs — but not connectivity. - Recall thresholds are corpus-dependent. The reranker removed the need to
tune BM25 scores by hand, but
outcome_priorandoutcome_floorare still set from observedrecall_score/recall_rejectionsbehaviour rather than derived.