AegisOps: Ein autonomer SRE-Agent mit vier Speicherschichten
Wie ein LangGraph ReAct-Agent Kubernetes-Vorfälle mit episodischem Speicher von pgvector, autonomiegesteuerten Ebenen, sechzehn Werkzeugen sowie einer vollständigen Audit-Trail löst.
Quellen: I Built an Autonomous SRE Agent That Resolves Production Incidents While I Sleep
Es ist 3:14 Uhr morgens und bei PagerDuty herrscht Hektik. Prometheus hat eine kritische Warnung ausgelöst: Die Fehlerquote des Zahlungsdienstes ist auf 34 Prozent gestiegen, Pods sterben aufgrund von OOMKills ab und das Zeitfenster für den SLA-Verstoß öffnet in sechs Minuten. Die Optionen sind, aus dem Bett zu steigen und sich per SSH in einen Cluster einzuloggen, oder einem Agenten die Sache zu überlassen.
AegisOps ist ein autonomer SRE-Agent, der genau für solche Momente entwickelt wurde. Er diagnostiziert, handelt und löst Produktionsvorfälle ohne menschliches Eingreifen, und er dokumentiert jede getroffene Entscheidung sowie den Grund dafür. Es handelt sich dabei nicht um einen Chatbot, der um kubectl herumgebaut ist. Er verfügt über echtes Gedächtnis, mehrschichtige Autonomiekontrollen sowie die Fähigkeit, aus bereits bearbeiteten Vorfällen zu lernen. Das ursprüngliche Ziel war nie, den Bereitschaftsingenieur zu ersetzen, sondern ihm einen unermüdlichen Erstreaktor zur Verfügung zu stellen, der bereits die Handbücher kennt, sich an die letzten zehn Vorfälle erinnert und vor dem Eingreifen in die Produktion Richtlinien überprüft.
Warum ReAct auf LangGraph?
ReAct (Reasoning plus Acting) eignet sich hervorragend für die Reaktion auf Vorfälle. Eine einfache Kette führt die Schritte blind aus, während ein ReAct-Agent einen Gedanken formuliert, eine Aktion wählt, beobachtet, was passiert ist, und den Prozess wiederholt. Das ist die richtige Vorgehensweise bei Ausfällen, bei denen die wichtigen Fragen lauten: Was ist bekannt, was wird beobachtet, und was sollte als Nächstes versucht werden?
LangGraph bietet eine Zustandsbasierte Graphenausführung. Jeder Knoten hat Zugriff auf den vollständigen AegisState – ein TypedDict, das Nachrichten, Speicherinhalte, Details zum Vorfall, Entscheidungen gemäß Richtlinien, Toolaufrufe sowie Überprüfungsergebnisse enthält. Da der Zustand nach jedem Schritt in PostgreSQL gespeichert wird, kann eine Ausführung pausiert, untersucht und wieder aufgenommen werden.
Der Graph besteht aus vier Knoten, die absichtlich einfach gehalten werden, um eine Überkomplexisierung zu vermeiden:
-
memory_nodewird zuerst verwendet und lädt alles, was der Agent vor Beginn jeder Berechnung benötigt.
agent_nodeist der Berechnungslauf des LLM.toolsist LangGraphs standardmäßigerToolNode, der das ausführt, was das Modell verlangt.resolution_nodebeendet den Vorfall: Er speichert die Informationen, berechnet den MTTR und sendet die Slack-Nachricht.
Vier Ebenen des Speichers
Die meisten Agentensysteme verzichten auf eine Speicherverwaltung oder beschränken sich auf zwei Ebenen. Ein menschlicher SRE setzt bei einem Vorfall vier Arten von Speicher ein, und AegisOps reproduziert alle davon.
- Episodisches Gedächtnis (pgvector, kosinusähnliche Ähnlichkeit) speichert vergangene, gelöste Vorfälle als Embeddings. Wenn ein neuer Vorfall eintritt, werden die drei am ähnlichstenen früheren Fälle abgerufen und zusammen mit ihrer Ursache, den ergriffenen Maßnahmen sowie dem Ergebnis in den Systemprompt eingefügt.
- Semantisches Gedächtnis (RAG über eine Wissensdatenbank in pgvector) enthält in Blöcken gegliederte Handbücher, Architekturdokumente sowie Nachbesprechungen. Das Tool
retrieve_runbookdurchsucht diese semantisch und stellt dem Agenten das institutionelle Wissen des Teams zur Verfügung.
params.yml in Redis geladen und vor jeder Aktion, die etwas ändert, ausgewertet. Man kann es als den trainierten Reflex des Agenten für sichere Abläufe betrachten.Episodisches Gedächtnis in der Praxis
Zur Eingabe ruft memory_node die Funktion retrieve_similar_incidents mit der Symptomenliste des aktuellen Incidents auf. Ein 768-dimensionaler Satzencoder embeddet diese Symptome, und die Kosinus-Distanz von pgvector vergleicht sie mit der Tabelle IncidentMemory. Frühere Incidents, die einen Ähnlichkeitsschwellenwert von 0,72 überschreiten, werden formatiert und im Systemprompt platziert, vor dem ersten Gedanken des Agents.
Nach einer Lösung ruft resolution_node die Funktion save_incident_memory auf, damit der Agent seine eigenen Erinnerungen speichert. Die Embedding-Werte stammen von den Symptomen in Kombination mit der Hypothese zur Ursache, sodass spätere Incidents mit ähnlichen Symptomen diesen gefunden werden.
Eine spezielle Vektordatenbank wie Pinecone oder Weaviate war eine Option. Alles in PostgreSQL zu halten beseitigte eine Infrastrukturabhängigkeit und gewährleistete transaktionale Konsistenz zwischen den Incidents-Records und ihren Embeddings. In diesem Umfang reicht pgvector völlig aus.
Autonomiegrade von L0 bis L3
Das war die schwierigste Gestaltungsfrage: Wie kann man einem Agenten echte Macht über die Produktion geben, ohne eine Katastrophe zu riskieren? Die Antwort ist ein schichtweises Autonomiemodell, bei dem jede verändernde Aktion vor Ausführung durch Richtlinien kontrolliert wird.
Jedes Aktionstool (restart_deployment, scale_deployment, update_deployment_image) beginnt mit einem Aufruf von retrieve_policy. Der Policy-Engine bewertet die Aktion, den Dienst, die Schwere sowie das aktuelle Autonomielevel im Vergleich zu den Regeln in params.yml. Wenn die Antwort „Nein“ lautet, gibt das Tool umgehend eine Ablehnung zurück, und der Agent muss einen anderen Weg finden, in der Regel eine Eskalation.
Das Feld requires_approval verdient Aufmerksamkeit. Selbst auf Ebene L3 benötigen einige zerstörerische Aktionen, wie beispielsweise das Zurücksetzen der Image-Datei in einem kritischen Zahlungsdienst, weiterhin eine ausdrückliche menschliche Genehmigung. Der Agent postet eine Nachricht in Slack und wartet. Dadurch erhält das Bereitschaftsteam die Gewissheit, dass bei den riskantesten Operationen immer ein Mensch eingebunden bleibt.
Die sechzehn Tools
AegisOps stellt sechzehn Tools in vier Kategorien bereit. Sechs davon heben sich hervor:
- get_logs ist der Beweissammler. Er nimmt die letzten 500 Zeilen der Protokolldatei eines Services, behält die angeforderten Ebenen bei und entfernt aus jeder Zeile IP-Adressen, UUIDs sowie reine Zahlen. Zwei Ablehnungen von verschiedenen Hosts gelten daher als ein Muster. Ein Python
Counterordnet die Muster nach Häufigkeit und gibt die am häufigsten vorkommenden zurück. Sowohl Terminal-Leser als auch der Entscheidungszyklus des Agents erhalten dasselbe strukturierte Ergebnis – es wird kein Elasticsearch-Cluster verwendet, sondern lediglich eine Protokolldatei sowie einige Python-Sätze. - get_pod_status fragt die Kubernetes CoreV1Api nach den Pods hinter dem Label
app={deployment}und gibt Informationen zu Neustarts, Bereitschaft sowie dem Zustand der Container zurück. Wenn der Cluster nicht erreichbar ist, wechselt das Tool sanft auf einen Ersatzmodus und schickt den Agenten stattdessen zuget_logs.
kubectl.kubernetes.io/restartedAt startet einen schrittweisen Neustart, wartet anschließend darauf, dass alle Replicas bereit sind, und führt eine Gesundheitsprüfung durch. Ein optionaler Entlastungsschritt hält den Traffic während des Neustarts fern.True an, wenn alle Pods bereit sind und die 5xx-Antworten unter 5 % liegen.Ein Incident, von Anfang bis Ende
So handelte der Agent bei dem Problem OOMKill im Zahlungsdienst um 3:14 Uhr morgens:
[MEMORY] Loading episodic context for payment-service...
[MEMORY] Found 2 similar past incidents (similarity: 0.89, 0.76)
[MEMORY] Loaded 3 procedural policies for payment-service/critical
[THOUGHT] Incident: payment-service OOMKill, 3 pods CrashLooping.
Past incident INC-2801 had identical symptoms — root cause was
a memory leak in the gRPC connection pool after a deploy.
Starting with get_logs to confirm before acting.
[ACTION] Calling tool: get_logs with {'service_name': 'payment-service', 'log_level': 'error'}
→ top_errors: [{"pattern": "out of memory: kill process", "count": 47},
{"pattern": "grpc connection pool exhausted", "count": 23}]
[THOUGHT] OOM confirmed. gRPC pool exhaustion matches INC-2801 pattern.
Retrieve runbook before acting — want the fix procedure.
[ACTION] Calling tool: retrieve_runbook with {'query': 'payment service grpc oom memory leak fix'}
→ [runbooks / payment-service-oom.md] Recommended action: rolling restart
resets connection pool. If recurs within 1h, rollback to previous image.
[THOUGHT] Runbook confirms rolling restart. Checking policy for autonomy L2...
[ACTION] Calling tool: restart_deployment with {'service': 'payment-service',
'deployment': 'payment-worker', 'namespace': 'production',
'severity': 'critical', 'autonomy_level': 'L2'}
→ policy: ALLOWED (L2 ≥ min_level L2 for restart_deployment)
→ draining pods... restarting... polling rollout...
→ health_check: healthy=True, pods_ready=3/3, error_rate=0.008
[THOUGHT] Restart succeeded. Error rate 0.8% — below 5% threshold.
Verifying with check_service_health before closing.
[ACTION] Calling tool: check_service_health with {'service': 'payment-service',
'deployment': 'payment-worker', 'namespace': 'production'}
→ healthy=True, pods_total=3, pods_ready=3, error_rate=0.007
[THOUGHT] Service fully recovered. Writing audit log and resolving.
[ACTION] Calling tool: write_audit_log ...
→ audit_log written: restart_deployment for incident INC-2847
RESOLVED: payment-service recovered after rolling restart. Root cause: gRPC
connection pool OOM — matched pattern from INC-2801. All 3 pods healthy,
error rate 0.7%. MTTR: 187 seconds.
[MEMORY] Saving incident INC-2847 to episodic memory...
[NOTIFY] Sending resolution to Slack channel: #all-aegisops ✅
Die gesamte Ablaufzeit betrug 187 Sekunden – von der Alarmierung bis zur Lösung in etwas mehr als drei Minuten, ohne dass jemand geweckt werden musste. Das ist diezeit die übliche Zeitspanne bei Vorfällen, die denen zuvor ähneln. Der Mitarbeiter begann wie ein erfahrener Ingenieur damit, zu prüfen, was beim letzten Mal funktioniert hatte – das ist die Wirkung episodischer Erinnerung.
Die Spur wurde zur besseren Übersicht rekonstruiert. Ein echter Ablauf auf einer einfachen lokalen Infrastruktur, mit einem einzigen Kind-Cluster und ohne Prometheus, sieht anders aus. Der Vorfall wurde durch einen einfachen PowerShell-Webhook-Aufruf ausgelöst:
Invoke-RestMethod -Uri "http://localhost:8000/webhook/incident" `
-Method Post `
-ContentType "application/json" `
-Body '{
"source": "webhook",
"severity": "critical",
"service": "payment-service",
"symptoms": ["High 500 errors", "DB connection timeouts"],
"autonomy_level": "L2"
}'
Der Agent führte den Vorgang von Anfang bis Ende durch. Er suchte nach ähnlichen Vorfällen und entwickelte die Hypothese, dass ein Anstieg des Datenverkehrs die Kapazität der Pods überlastete – was mit einem früheren Fall in der Speicherung übereinstimmte. Er rief get_pod_status auf und stellte fest, dass 2 von 5 Pods bereit waren; daraufhin skalierte er die Deployment-Anzahl auf 4 Replicas. Mit check_service_health bestätigte er den Status und schloss den Vorgang als RESOLVED ab. Eine Slack-Nachricht wurde in #all-aegisops gesendet, und der Vorgang wurde wieder in das episodische Gedächtnis geschrieben.
Das ist zwar weniger dramatisch als ein GRPC-Ausfall aufgrund von Speicherproblemen um 3 Uhr morgens, doch der Ablauf bleibt unverändert: derselbe Zyklus aus Denken, Handeln und Beobachten, dieselbe Überprüfung der Richtlinien vor der Skalierung sowie derselbe Speicherzugriff am Ende. Nur die Umgebung ist einfacher gestaltet. Der Agent kann keinen Unterschied zwischen einem Laptop-Cluster und einer Flotte aus 50 Knoten erkennen. Ändert man die kubeconfig-Datei und richtet Prometheus auf echte Metriken aus, funktioniert derselbe Code auch in der realen Umgebung.
Slack als Ausgabekanal
Bemerkenswerte Aktionen senden eine Slack-Nachricht. Wenn ein Incident abgeschlossen wird, schreibt der Lösungsknoten in #all-aegisops den Schweregrad in Form einer Farbe, die MTTR sowie eine vollständige Zusammenfassung der Lösung – dadurch wird der diensthabende Ingenieur mit einem grünen Häkchen und nicht mit einer Pager-Benachrichtigung geweckt.
Die Technologiestack
Alles läuft auf einem schlichten Technologiestack. PostgreSQL mit pgvector verwaltet die episodische Speicherung sowie die LangGraph-Checkpoints. Redis speichert Richtlinien und ist für zukünftige Rate-Limiting-Funktionen vorgesehen. Prometheus liefert die tatsächlichen Fehlerraten an check_service_health weiter, und Grafana stellt Dashboards bereit.
version: "3.9"
services:
postgres:
image: ankane/pgvector:latest
ports:
- "5445:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: admin123
POSTGRES_DB: aegisops
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:alpine
ports:
- "6380:6379"
app:
build: .
ports:
- "8000:8000"
env_file: .env
depends_on:
- postgres
- redis
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./infra/prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
volumes:
postgres_data:
Die Anwendung ist FastAPI unter uvicorn. Incidents erreichen einen webhook-Endpunkt, der einen IncidentEvent erstellt und run_agent() aufruft. Die Ausführung ist asynchron, sodass mehrere Incidents parallel ohne Blockaden verarbeitet werden können. Der Kubernetes-Client ist kubernetes_asyncio, der die Aufrufe zum Cluster ebenfalls asynchron gestaltet.
Eine Fallstrick: get_pod_status und check_service_health laden die Kubernetes-Konfiguration beim Import des Moduls, nicht innerhalb der asynchronen Funktion. Wenn der Cluster beim Start nicht erreichbar ist, scheitern diese Funktionen stumm und geben Warnmeldungen aus – stellen Sie daher sicher, dass KUBECONFIG oder das Service-Konto im Cluster vor dem Start der Anwendung konfiguriert ist.
Die Audit-Trail
Jeder Aufruf eines Tools wird in einem ToolInvocation TypedDict aufgezeichnet: Was ausgeführt wurde, mit welchen Parametern, was zurückkam, ob es erfolgreich war, wie lange es dauerte, wann und welche Richtlinienentscheidung dies ermöglichte. Die Aufzeichnungen sammeln sich während des Laufs in state["tool_invocations"] an und werden bei der Abschlussbewertung in die episodische Speicherung gespeichert. Ein separates write_audit_log-Tool erzeugt für jede bedeutende Aktion eine strukturierte JSON-Zeile.
Dadurch kann jedes Vorkommnis später nachgespielt werden: die Abfolge der Schritte, die geltenden Berechtigungen sowie der Grund für jede davon. Compliance-Teams verlangen genau diese Art von Aufzeichnungen.
Eine Designentscheidung bestimmt den Rest. Jede Änderungsaktion gibt die Richtlinienentscheidung zusammen mit dem Ergebnis der Gesundheitsprüfung zurück, sodass der Agent auf einmal drei Dinge erfährt: ob der Aufruf erfolgreich war, ob er erlaubt war und ob der Dienst derzeit funktionsfähig ist. Alle drei Faktoren prägen seinen nächsten Gedanken.
Was fehlt noch
Alles oben genannte funktioniert von Anfang bis Ende. Weitere Entwicklungen wurden nur dadurch eingeschränkt, dass ein lokaler Rechner Schwierigkeiten hatte, Ollama neben dem gesamten System auszuführen. Mit mehr Ressourcen treten zunächst drei Lücken zutage.
Der Genehmigungsprozess ist die dringendste Lücke. approval_status existiert bereits im State und requires_approval wird bereits überprüft, doch tatsächlich wird nichts gestoppt. Der Plan ist es, einen Slack-Button zu senden, den Graphen anzuhalten und erst wieder fortzusetzen, nachdem jemand geklickt hat, bevor irgendeine L3-Aktion ausgeführt wird. LangGraph kann einen Graphen während seiner Ausführung durch seine Kontrollpunkte aussetzen, sodass es nur noch um die Verknüpfung der Komponenten geht und nicht um ein Neudesign.
Vergleich der Metriken vor und nach der Änderung ist die zweite Herausforderung. check_service_health liefert lediglich einen Zeitpunkt-Snapshot nach der Korrektur. Was der Agent benötigt, ist ein Prometheus-Delta-Wert – der Fehlerquotenwert fünf Minuten vor und fünf Minuten nach der Änderung –, um sicherzustellen, dass tatsächlich etwas behoben wurde und nicht nur Zufälle des Zeitpunkts zum Vorteil kamen.
Selbstgenerierung von Runbooks ist das langfristige Ziel. Derzeit versorgen sich die beiden Wissensquellen – vergangene Vorfälle und Runbooks – niemals gegenseitig. Nach einer ungewöhnlichen Lösung sollte ein Agent einen neuen Abschnitt für das Runbook schreiben und ihn in die Wissensdatenbank einfügen, damit retrieve_runbook ihn beim nächsten Mal zurückgeben kann. Dadurch würden die beiden Wissensschichten miteinander verbunden.
Was das Projekt gelehrt hat
Das eigentliche Problem bei autonomen Infrastrukturagenten liegt offenbar nicht im Denkvermögen des Modells, sondern in der Sicherheitsstruktur dahinter. Wissensschichten, ein Policy-Engine, ein Audit-Trail sowie Genehmigungsmechanismen sind es, die ein Team dazu bringen, ein Tool auch um 3 Uhr morgens ungestört arbeiten zu lassen.
AegisOps vertritt die Ansicht, dass eine strukturierte Zustandsmaschine mit klaren Berechtigungsgrenzen einem frei agierenden Agenten, der improvisiert, überlegen ist. Diese Haltung hat sich bisher bewährt – durch ein umfangreicheres Ereignisprotokoll sowie einen Agenten, der sich nach jedem neuen Fall verbessert. Das Checkpointing in LangGraph, die klare Trennung zwischen Diagnose- und Aktionstools sowie der TypedDict-Zustand machen das System leicht überprüfbar, debuggbar und audierbar, wenn etwas schiefgeht und die Frage aufkommt, warum der Agent genau diese Entscheidung getroffen hat.
Falls Sie etwas Ähnliches entwickeln, investieren Sie früh in eine geeignete Speicherverwaltung. Ein Agent mit gutem Speicher ist zehnmal wertvoller als einer mit nur leicht besseren Anweisungen. Transformers benötigten Aufmerksamkeit – Agenten in der realen Welt benötigen hingegen Kontext.
Verwandte Artikel
- Agent-Memory-Snaps, Fähigkeiten, Zerfall und branchenspezifische Speicherung — Vier Erweiterungen für eine eingebettete Vektor-Datenbank-Speicherschicht für Programmieragenten: portable Snapshots mit Zusammenführung, Aktivierung von Fähigkeiten, Zerfall mit Konfliktprüfungen sowie speicherung, die auf bestimmte Branchen beschränkt ist.
- Verwendung von Produktionswerkzeugen für Agenten: Herkunft, Wiederholungsversuche und Beobachtbarkeit — Ein Agent eines Bankhauses rief eine kostenpflichtige API zu oft auf und erfand eine Telefonnummer. Es werden minimale Werkzeugsets, die Herkunft von Parametern, dreistufige Wiederholungsversuche sowie Metriken pro Werkzeug erläutert.