Strona główna / Artykuły

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.

Wydawca: REACTAPP.TOP12 min czytania2723 słów

Ź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_node jest pierwszy i ładuje wszystko, czego agent będzie potrzebował, zanim rozpocznie się jakiekolwiek rozumowanie.
  • agent_node to pętla rozumowania LLM.
  • tools to standardowy ToolNode z LangGraph, który wykonywać może wszystko, o co poprosi model.
  • resolution_node zamyka 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.

  1. 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.
  2. 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_runbook przeszukuje je w sposób semantyczny, dostarczając agencie instytucjonalną wiedzę zespołu.
  • Pamięć proceduralna (zasady YAML, silnik reguł oraz Redis) obejmuje sztywne reguły określające, co agent może robić na każdym poziomie autonomii. Są one ładowane z pliku 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ęć robocza (stan AegisOps oraz punkt kontrolny LangGraph) obejmuje stan procesu w czasie rzeczywistym: wiadomości, bieżące myśli, wywołania narzędzi, wyniki sprawdzania stanu oraz liczbę prób ponownych. Robienie punktów kontrolnych przy każdym węźle zapewnia, że kontekst przetrwa restarty.
  • 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ę:

    1. 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 Counter sortuje 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.
    2. 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 funkcji get_logs.
    3. restart_deployment wykonuje większość prac. Naprawa adnotacji kubectl.kubernetes.io/restartedAt uruchamia 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.
    4. 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.
    5. check_service_health łączy informacje o gotowości podów z Kubernetes z aktualną stopą błędów z Prometheus. Zwraca wartość True tylko wtedy, gdy wszystkie pody są gotowe, a odpowiedzi typu 5xx stanowią mniej niż 5%.
    6. 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

  • Wybór architektury RAG: Naive, Advanced, CRAG, GraphRAG, Agentic — Praktyczne porównanie sześciu architektur RAG wraz z zaletami, wadami i przypadkami zastosowania, a także czterostopniowy proces podejmowania decyzji oraz sytuacje, w których RAG nie jest odpowiednim narzędziem.
  • Notatki praktyczne: Dałem agentowi AI URL w formacie JSON. Stworzył on działające przedsiębiorstwo — Szczegółowy przewodnik po notatkach praktycznych: Dałem agentowi AI URL w formacie JSON. Stworzył on działające przedsiębiorstwo: umowy, sprawdzenia oraz gotowe elementy kodu dla zespołów wdrażających ten model.
  • Przejście na Claude Opus 5.5: pięć istotnych zmian związanych z identyfikatorem modelu — Narzędzie do przełączenia się na Claude Opus 5.5: ciągłe myślenie, przymusowy wybór narzędzia, blokady myślenia związane z modelem, narzędzia do korzystania z komputera oraz cichy przepływ postępów.
  • Od prostego rozwiązania do pełnowartościowego pipeline RAG dla dokumentów SEC 10-K: dzielenie na fragmenty, wyszukiwanie hybrydowe — Dlaczego standardowy pipeline RAG zawodził przy 300-stronicowych dokumentach 10-K i jak to naprawić: fragmenty uwzględniające strukturę dokumentu, hybrydowe wyszukiwanie, ponowna sortowanie za pomocą kodera, weryfikacja oraz odwołania do metadanych.
  • Inżynieria agentów Harness: budżety, uprawnienia i oceny dla agentów produkcyjnych — Większość agentów zawodzi w środowisku produkcyjnym z powodu słabej infrastruktury, a nie modelu. Dowiedz się, jak pętle, poziomy ryzyka, budżety, warstwowanie kontekstu oraz oceny bezpieczeństwa zapewniają bezpieczeństwo agentom.
  • Jak agent AI do poprawy dokumentacji oszukał samego siebie: fałszywie pozytywne wyniki w środowisku sandbox Docker — Agent LangGraph, który naprawia uszkodzony kod README, najpierw opublikował rozwiązanie bez żadnego efektu, a następnie zawodził przy pakietach src-layout. Oto błąd w środowisku sandbox oraz prawdziwe rozwiązanie problemu z dokumentacją.
  • Stworzenie serwera MCP do dziennika pracy z funkcją wyszukiwania w 190 liniach kodu TypeScript — niewielki serwer MCP działający z plikami Markdown: cztery narzędzia oparte na intencjach, opisy w stylu promptów, klient demonstracyjny typu stdio służący również jako test, a także rzeczywiste trudności.
  • Jak działa RAG w systemach produkcyjnych: dzielenie na fragmenty, embeddingi, wyszukiwanie wektorowe i ponowna sortowanie — szczegółowe omówienie procesu Retrieval-Augmented Generation: dzielenie na fragmenty, embeddingi, bazy danych wektorowych, kompletowanie promptów, ponowne sortowanie, ograniczenia związane z halucynacjami oraz krótka notatka o KAG.