AegisOps: Autonomiczny agent SRE z czterema warstwami pamięci
Jak agent LangGraph ReAct rozwiązuje incydenty w Kubernetes, wykorzystując epizodyczną pamięć pgvector, poziomy autonomii kontrolowane przez zasady, szesnaście narzędzi oraz pełny ślad audytowy.
Źródła: I Built an Autonomous SRE Agent That Resolves Production Incidents While I Sleep
Jest 3:14 w nocy i w PagerDuty panuje duży ruch. Prometheus wydał krytyczne ostrzeżenie: wskaźnik błędów w usłudze płatności wzrósł do 34%, kontenery przestają działać z powodu błędów OOMKills, a okno naruszenia umowy SLA otwiera się za sześć minut. Można albo wstać z łóżka i połączyć się SSH z klastrum, albo pozwolić agentowi zająć się tym problemem.
AegisOps to autonomiczny agent SRE stworzony właśnie do takich sytuacji. Diagnozuje, podejmuje działania i rozwiązuje incydenty w środowisku produkcyjnym bez udziału człowieka, a także rejestruje każdą podjętą decyzję oraz jej powód. To nie jest chatbot oparty na kubectl – posiada prawdziwą pamięć, wielopoziomowe mechanizmy kontroli autonomii oraz możliwość uczenia się na podstawie incydentów, z którymi już się zmierzył. Deklarowanym celem nie było zastąpienie inżyniera dyżurnego, lecz zapewnienie mu niezmordowanego pierwszego interwentu, który zna już procedury, pamięta ostatnie dziesięć incydentów i sprawdza zasady przed wprowadzaniem zmian w środowisku produkcyjnym.
Dlaczego ReAct w LangGraph?
ReAct (Rozumowanie plus Działanie) doskonale pasuje do reagowania na incydenty. Zwykły łańcuch wykonywałby kroki ślepo, natomiast agent ReAct formułuje myśl, wybiera działanie, obserwuje to, co się stało, i powtarza ten proces. To właściwy sposób postępowania w sytuacjach awarii, gdy istotne są pytania o to, co jest wiadome, co jest obserwowane oraz co należy spróbować dalej.
LangGraph umożliwia wykonywanie operacji na grafie z zachowaniem stanu. Każdy węzeł ma dostęp do pełnego obiektu AegisState – typu TypedDict, który przechowuje wiadomości, pamięć, szczegóły incydentu, decyzje polityki, wywołania narzędzi oraz wyniki weryfikacji. Ponieważ stan jest zapisywany do PostgreSQL po każdym kroku, proces można zawiesić, przeanalizować i wznowić.
Graph składa się z czterech węzłów, które są celowo proste, aby uniknąć nadmiernego projektowania:
-
memory_nodejest pierwszy i ładuje wszystko, czego agent będzie potrzebował, zanim rozpocznie się jakiekolwiek rozumowanie.
agent_nodeto pętla rozumowania LLM.toolsto standardowyToolNodez LangGraph, który wykonywać może wszystko, o co poprosi model.resolution_nodezamyka incydent: zapisuje informacje z pamięci, oblicza MTTR i wysyła wiadomość do Slacka.
Cztery warstwy pamięci
Większość systemów agentowych pomija architekturę pamięci lub ogranicza się do dwóch warstw. Człowiek zajmujący się SRE wprowadza do incydentu cztery rodzaje pamięci, a AegisOps odtwarza je wszystkie.
- Pamięć epizodyczna (pgvector, podobieństwo kosinowe) przechowuje wcześniej rozwiązane incydenty w postaci embeddingów. Gdy pojawia się nowy incydent, pobierane są trzy najbardziej podobne wcześniejsze przypadki, które są włączane do promptu systemu wraz z ich przyczyną podstawową, podjętymi działaniami oraz wynikiem.
- Pamięć semantyczna (RAG w bazie wiedzy opartej na pgvector) przechowuje podzielone na fragmenty instrukcje postępowania, dokumentację architektury oraz analizy po incydentach. Narzędzie
retrieve_runbookprzeszukuje je w sposób semantyczny, dostarczając agencie instytucjonalną wiedzę zespołu.
params.yml do Redis na początku przetwarzania grafu i sprawdzane przed każdą akcją, która coś zmienia. Można ją uznać za wytrenowany odruch agenta zapewniający bezpieczne działanie.Pamięć epizodyczna w praktyce
Przy wejściu memory_node wywołuje funkcję retrieve_similar_incidents z listą objawów bieżącego incydentu. Kodownik zdań o wymiarze 768 embeduje te objawy, a funkcja odległości kosinowej z pgvector porównuje je z tabelą IncidentMemory. Incydenty z przeszłości, których stopień podobieństwa przekracza próg 0,72, są formatowane i umieszczane w instrukcji systemowej, przed pierwszą myślą agenta.
Po rozwiązaniu problemu resolution_node wywołuje funkcję save_incident_memory, dzięki czemu agent może zapisać własne wspomnienia. Embedding powstaje na podstawie objawów połączonych z hipotezą przyczyny podstawowej, więc późniejsze incydenty o podobnych objawach będą mogły odnaleźć ten konkretny przypadek.
Opcją była dedykowana baza danych wektorowych, takich jak Pinecone lub Weaviate. Przechowywanie wszystkiego w PostgreSQL eliminowało jedną z zależności infrastrukturalnych i zapewniało spójność transakcyjną pomiędzy rekordami incydentów a ich embeddingami. W takiej skali pgvector jest więcej niż wystarczający.
Poziomy autonomii od L0 do L3
To było najtrudniejsze pytanie projektowe: jak dać agencie rzeczywistą władzę nad środowiskiem produkcyjnym, nie narażając się na katastrofę? Odpowiedzią jest model autonomii warstwowej, w którym każda akcja zmieniająca stan jest kontrolowana zgodnie z zasadami przed jej wykonaniem.
Każde narzędzie do podejmowania działań (restart_deployment, scale_deployment, update_deployment_image) zaczyna się od wywołania funkcji retrieve_policy. Silnik polityk ocenia działanie, usługę, stopień powagi oraz aktualny poziom autonomii w odniesieniu do zasad zawartych w params.yml. Jeśli odpowiedź jest negatywna, narzędzie natychmiast zwraca odmowę, a agent musi znaleźć inną drogę postępowania, zazwyczaj eskalację.
Pole requires_approval zasługuje na szczególną uwagę. Nawet na poziomie L3 niektóre destruktywne działania, takie jak cofnięcie wersji obrazu w krytycznej usłudze płatności, nadal wymagają wyraźnej zgody człowieka. Agent wysyła wiadomość do Slacka i czeka. Dzięki temu zespół pełniący dyżur ma pewność, że najbardziej ryzykowne operacje są monitorowane przez osobę ludzką.
Szesnaście narzędzi
AegisOps dostarcza szesnaście narzędzi w czterech kategoriach. Sześć z nich wyróżnia się:
- get_logs jest narzędziem do zbierania dowodów. Pobiera ostatnie 500 wierszy pliku logów usługi, zachowuje wybrane poziomy szczegółowości oraz usuwa z każdego wiersza adresy IP, UUID-y oraz zwykłe liczby. Dlatego dwa przypadki odrzucenia pochodzące od różnych hostów są traktowane jako jeden wzorzec. Pythonowy
Countersortuje te wzorce i zwraca te najczęściej występujące. Czytniki terminali oraz pętla rozumowania agenta otrzymują ten sam strukturyzowany wynik, przy czym nie jest potrzebny żaden klastr Elasticsearch – wystarczy plik logów oraz kilka linijek kodu w Pythonie. - get_pod_status żąda od interfejsu Kubernetes CoreV1Api informacji o podczłonkach o etykiecie
app={deployment}i raportuje informacje dotyczące restartów, gotowości oraz stanu kontenerów. Gdy do klastra nie można uzyskać dostępu, narzędzie zachowuje się uprzejmie i zamiast tego wysyła agenta do funkcjiget_logs. - restart_deployment wykonuje większość prac. Naprawa adnotacji
kubectl.kubernetes.io/restartedAturuchamia stopniowy restart, następnie czeka, aż wszystkie repliki będą gotowe, i przeprowadza sprawdzenie stanu. Opcjonalny krok odsysania ruchu zapobiega przepływowi ruchu podczas restartu. - update_deployment_image służy do awaryjnego cofnięcia zmian. Zmienia obraz kontenera w specyfikacji, czeka, aż proces wdrożenia się zakończy, a następnie sprawdza stan podów. Ponieważ jest to najsilniejsze narzędzie w zestawie, krytyczne incydenty w produkcji wymagają autonomii poziomu L3 do jego użycia.
- check_service_health łączy informacje o gotowości podów z Kubernetes z aktualną stopą błędów z Prometheus. Zwraca wartość
Truetylko wtedy, gdy wszystkie pody są gotowe, a odpowiedzi typu 5xx stanowią mniej niż 5%. - retrieve_runbook wyszukuje podręczniki postępowania, analizy po incydentach oraz notatki architektoniczne według znaczenia i zwraca cztery najlepsze fragmenty o podobieństwie co najmniej 0,75, które pojawiają się w instrukcjach systemu pod nagłówkiem „RUNBOOK CONTEXT”.
Jeden incydent, od początku do końca
Oto co zrobił agent w przypadku błędu OOMKill w usłudze płatności o godzinie 3:14 rano:
[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 ✅
Cała procedura trwała 187 sekund – od wykrycia problemu do jego rozwiązania w nieco ponad trzy minuty, przy czym nikt nie został obudzony. To jest czas, który obecnie regularnie osiąga się przy incydentach podobnych do tych wcześniejszych. Agent zaczął od sprawdzenia, co zadziałało ostatnim razem, tak jak zrobiłby to doświadczony inżynier – to właśnie pamięć epizodyczna pełni tu swoją rolę.
Ślad działania systemu został zrekonstruowany dla lepszej przejrzystości. Rzeczywisty proces na skromnym lokalnym urządzeniu, z pojedynczym klastrum Kind i bez Prometheus, wygląda inaczej. Incydent został wywołany prostym wywołaniem webhooka w PowerShellu:
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"
}'
Agent przeprowadził cały proces od początku do końca. Pobrano podobne incydenty i sformułowano hipotezę, że nagły wzrost ruchu przekraczał możliwości pojemności podów, co odpowiadało wcześniejszemu przypadkowi zapisanemu w pamięci. Zawarto wywołanie get_pod_status i stwierdzono, że 2 z 5 podów jest gotowych; następnie zwiększono liczbę replik do 4, potwierdzono wyniki za pomocą check_service_health i zamknięto incydent jako RESOLVED. W kanale #all-aegisops pojawiła się wiadomość w Slacku, a informacje o incydencie zostały zapisane z powrotem do pamięci epizodycznej.
To o wiele łagodniejsze niż awaria z powodu braku pamięci w gRPC o 3 rano, mimo to cykl pozostaje niezmieniony: ten sam cykl Myśl, Działanie, Obserwacja, ta sama weryfikacja zasad przed skalowaniem i ten sam zapis do pamięci na końcu. Tylko otoczenie jest prostsze. Agent nie potrafi odróżnić klastra laptopów od floty składającej się z 50 węzłów. Wystarczy zmienić kubeconfig, skierować Prometheus na rzeczywiste metryki, a ten sam kod będzie działał w rzeczywistym środowisku.
Slack jako kanał wyjściowy
Ważne działania wysyłają wiadomość do Slacka. Gdy incydent zostanie zamknięty, węzeł rozwiązania zapisuje informacje do #all-aegisops – kolor wskazujący stopień powagi, czas reakcji oraz pełny opis rozwiązania – dzięki czemu inżynier na zmianie otrzymuje informację w postaci zielonej gwiazdki, a nie alertu.
Stack technologiczny
Całość działa na skromnym stacku. PostgreSQL z modułem pgvector służy do przechowywania informacji o incydentach oraz plików checkpoint LangGraph. Redis przechowuje polityki i jest przeznaczony na przyszłe zastosowania ograniczeń szybkości. Prometheus dostarcza rzeczywiste wskaźniki błędów do funkcji check_service_health, natomiast Grafana umożliwia tworzenie paneli kontrolnych.
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:
Aplikacja jest zbudowana na FastAPI z użyciem uvicorn. Zdarzenia trafiają do endpointu webhook, który tworzy obiekt IncidentEvent i wywołuje funkcję run_agent(). Wykonywanie jest asynchroniczne, dzięki czemu kilka zdarzeń może być przetwarzanych równolegle bez blokowania. Klient do Kubernetes to kubernetes_asyncio, który zapewnia, że wywołania do klastra nie są blokujące.
Jedna pułapka: funkcje get_pod_status i check_service_health ładują konfigurację Kubernetes przy imporcie modułu, a nie wewnątrz funkcji asynchronicznej. Jeśli klastrowi nie można się połączyć przy uruchamianiu, te funkcje działają bez komunikatu błędu i zwracają wiadomości ostrzegawcze, dlatego upewnij się, że KUBECONFIG lub konto usługowe w klastrze jest skonfigurowane przed uruchomieniem aplikacji.
Ślad audytowy
Każde wezwanie narzędzia jest zapisywane w obiekcie typu ToolInvocation z typem TypedDict: co zostało uruchomione, z jakimi parametrami, co wróciło, czy operacja zakończyła się sukcesem, ile czasu zajęła, kiedy to nastąpiło oraz która decyzja polityki na to pozwoliła. Zapisy gromadzą się w state["tool_invocations"] podczas wykonywania i są przechowywane w pamięci epizodycznej po zakończeniu procesu. Odrębne narzędzie write_audit_log wytwarza strukturyzowaną linię JSON dla każdej istotnej akcji.
W rezultacie każdy incydent może zostać później odtworzony: sekwencja kroków, obowiązujące uprawnienia oraz powód podjęcia każdej z decyzji. Zespoły ds. zgodności właśnie takie zapisy wymagają.
Jedna decyzja projektowa wpływa na wszystkie pozostałe. Każda akcja mutacji przekazuje informację o decyzji zasadniczej wraz z wynikiem sprawdzenia stanu, dzięki czemu agent dowiaduje się jednocześnie trzech rzeczy: czy połączenie się udało, czy było dozwolone oraz czy usługa jest obecnie sprawna. Wszystkie te informacje kształtują jego kolejne rozważania.
Co jeszcze brakuje
Wszystko powyższe działa w sposób ciągły od początku do końca. Dalsze rozwijanie było ograniczone jedynie możliwościami lokalnego komputera, który miał trudności z uruchomieniem Ollamy obok całego systemu. Przy większych zasobach najpierw pojawiają się trzy braki.
Proces zatwierdzania to najpilniejszy brak. W stanie już istnieje approval_status i requires_approval jest już sprawdzane, a mimo to nic tak naprawdę nie jest wstrzymywane. Plan zakłada wysłanie przycisku w Slacku, zatrzymanie wykresu i wznowienie pracy dopiero po kliknięciu użytkownika, zanim zostanie podjęta jakakolwiek akcja na poziomie L3. LangGraph może wstrzymać wykres w trakcie jego działania za pomocą swoich punktów kontrolnych, więc pozostaje tylko dopracowanie logiki, a nie całkowita przebudowa.
Porównanie wskaźników przed i po zmianie to drugi problem. check_service_health dostarcza jedynie zdjęcie stanu w konkretnym momencie po naprawie. Aby agent mógł mieć pewność, że coś naprawiono, a nie że korzysta się jedynie z efektu przypadkowego momentu, potrzebny jest delta z Prometheus – wskaźnik błędów pięć minut przed i pięć minut po naprawie.
Samoautoryzacja książek instrukcji jest długoterminowym celem. Obecnie dwa zasoby wiedzy – wcześniejsze incydenty oraz książki instrukcji – nigdy nie współpracują ze sobą. Po niezwykłym rozwiązaniu problemu agent powinien napisać nowy fragment książki instrukcji i dodać go do bazy wiedzy, aby retrieve_runbook mógł go przywrócić następnym razem. To połączyłoby obie warstwy pamięci.
Co nauczył projekt
Okazuje się, że prawdziwym problemem związanym z autonomicznymi agentami infrastrukturalnymi nie jest rozumowanie modelu, lecz struktura bezpieczeństwa wokół niego. Warstwy pamięci, silnik polityk, śledzenie działań oraz mechanizmy zatwierdzania sprawiają, że zespół jest skłonny pozostawić narzędzie w spokoju o 3 rano.
AegisOps przyjmuje określoną linię postępowania: zorganizowana maszyna stanowa z jasno określonymi granicami uprawnień jest lepsza od agenta działającego swobodnie i improwizującego. Ta zasada sprawdziła się dotychczas, dzięki bogatszemu rejestrowi incydentów oraz agentowi, który udoskonala się po każdym nowym przypadku. Funkcja checkpointingu w LangGraph, wyraźne rozdzielenie narzędzi diagnostycznych od narzędzi działania oraz użycie TypedDict do reprezentacji stanu sprawiają, że system jest łatwy do sprawdzania, debugowania i audytowania, gdy coś idzie nie tak i pojawia się pytanie, dlaczego agent dokonał takiego wyboru.
Jeśli budujesz coś podobnego, wcześnie zainwestuj w architekturę pamięci. Agent z dobrą pamięcią jest dziesięć razy lepszy niż ten z nieco lepszym promptem. Transformery wymagały uwagi, natomiast agenci w rzeczywistym świecie potrzebują kontekstu.
Literatura pokrewna
- Zdjęcia ekranu pamięci agenta, umiejętności, degradacja i zakres pamięci na gałęziach — Cztery ulepszenia warstwy pamięci typu vector-DB wbudowanej w agenty programistyczne: przenośne zdjęcia ekranu z możliwością łączenia, aktywacja umiejętności, degradacja z kontrolą konfliktów oraz pamięć ograniczona do konkretnych gałęzi.
- Wykorzystanie narzędzi produkcyjnych przez agenty: pochodzenie danych, próby ponowne i możliwości obserwacji — Agent banku niewłaściwie wywołał płatną API i wymyślił numer telefonu. Poznaj minimalne zestawy narzędzi, pochodzenie parametrów, trójpoziomowe próby ponowne oraz metryki dla poszczególnych narzędzi.