AegisOps: Автономний агент SRE з чотирма рівнями пам’яті
Як агент LangGraph ReAct вирішує інциденти в Kubernetes за допомогою епізодичної пам’яті pgvector, рівнів автономії, контрольованих політиками, шістнадцяти інструментів та повного журналу аудиту.
Джерела: I Built an Autonomous SRE Agent That Resolves Production Incidents While I Sleep
Зараз 3:14 ранку, і у PagerDuty постійні сигнали тривоги. Prometheus відправив критичне сповіщення: рівень помилок у сервісі оплати зріс до 34%, контейнери зникають через помилки типу OOMKills, а термін порушення умов SLA закінчується через шість хвилин. Є два варіанти — піднятися з ліжка та підключитися через SSH до кластера або доручити це агенту.
AegisOps — це автономний агент SRE, створений саме для таких ситуацій. Він діагностує, вживає заходів та усуває проблеми в промисловому середовищі без участі людини, записуючи кожне прийняте рішення та причину його прийняття. Це не чат-бот, обгорнутий навколо kubectl. У нього є справжня пам’ять, багатошарові механізми автономного керування та можливість навчатися на прикладах вже вирішених проблем. Основною метою було не замінити інженера, який працює у режимі чергування, а надати йому невтомного помічника, який вже знає інструкції, пам’ятає останні десять інцидентів та перевіряє правила перед тим, як втручатися в роботу системи.
Чому ReAct у LangGraph
ReAct (міркування плюс дія) ідеально підходить для реагування на інциденти. Проста ланцюгова структура виконує кроки сліпо, тоді як агент ReAct формулює думку, обирає дію, спостерігає за тим, що сталося, та повторює цей процес. Саме така підхід є правильним під час збоїв, адже корисними є запитання про те, що відомо, що спостерігається та що потрібно спробувати далі.
LangGraph забезпечує виконання графа з урахуванням стану. Кожен вузол має доступ до повного об’єкта AegisState — TypedDict, який містить повідомлення, пам’ять, деталі інциденту, рішення політик, виклики інструментів та результати перевірки. Оскільки стан зберігається у PostgreSQL після кожного кроку, виконання можна призупинити, перевірити та продовжити.
У графі є чотири вузли, які навмисно залишені простими, щоб уникнути надмірної складності:
-
memory_nodeпрацює першим та завантажує все, що знадобиться агенту, ще до початку міркувань.
agent_node— це цикл міркувань LLM.tools— це стандартнийToolNodeвід LangGraph, який виконує те, про що просить модель.resolution_nodeзавершує обробку інциденту: він зберігає інформацію з пам’яті, розраховує MTTR та надсилає повідомлення в Slack.
Чотири рівні пам’яті
Більшість систем агентів ігнорують архітектуру пам’яті або обмежуються двома рівнями. Спеціаліст з обслуговування систем людина використовує чотири види пам’яті під час обробки інцидентів, а AegisOps відтворює їх усі.
- Епізодична пам’ять (pgvector, косинусна схожість) зберігає минулі вирішені інциденти у вигляді ембеддингів. Коли з’являється новий інцидент, знаходяться три найбільш схожі попередні випадки, які потім вставляються до системного запиту разом із їхньою первинною причиною, вжитими заходами та результатом.
- Семантична пам’ять (RAG над базою знань у pgvector) зберігає розділені на частини посібники, документацію до архітектури та звіти після аналізу інцидентів. Інструмент
retrieve_runbookшукає серед них семантично, надаючи агенту інституційні знання команди.
params.yml до Redis на початку обробки графу та оцінюються перед будь-якою дією, яка щось змінює. Це можна вважати навченим рефлексом агента для безпечної роботи.Епізодична пам’ять на практиці
Під час входу memory_node викликає функцію retrieve_similar_incidents із списком симптомів поточної події. 768-вимірний кодер речень інкорпорує ці симптоми, а функція косинусової відстані з pgvector порівнює їх із даними з таблиці IncidentMemory. Попередні події, які мають рівень схожості вище порогового значення 0,72, форматуються та додаються до системного запиту перед першою думкою агента.
Після вирішення проблеми resolution_node викликає функцію save_incident_memory, щоб агент міг зберегти власні спогади. Інкорпорація симптомів здійснюється разом із гіпотезою первинної причини, тому подальші події з схожими симптомами знайдуть цю конкретну подію.
Варіантом була спеціалізована векторна база даних, така як Pinecone чи Weaviate. Зберігання всього в PostgreSQL усувало одну залежність від інфраструктури та забезпечувало транзакційну послідовність між записами про інциденти та їхніми векторними представленнями. На такому масштабі pgvector цілком достатній.
Рівні автономії від L0 до L3
Це було найскладніше питання дизайну: як надати агенту справжні повноваження у робочому середовищі, не ризикуючи катастрофою? Відповіддю є модель багатошарової автономії, у якій кожна дія, що змінює стан, проходить перевірку за правилами перед виконанням.
Кожен інструмент дії (restart_deployment, scale_deployment, update_deployment_image) починається з виклику retrieve_policy. Механізм політик оцінює дію, сервіс, ступінь серйозності та поточний рівень автономії з урахуванням правил, вказаних у params.yml. Якщо відповідь «ні», інструмент негайно повертає відмову, і агенту доводиться шукати інший шлях, зазвичай через ескалацію.
Слід звернути увагу на поле requires_approval. Навіть на рівні L3 деякі руйнівні дії, такі як скасування оновлення образу в критичному сервісі оплат, все ще потребують прямої схвалення людини. Агент публікує повідомлення у Slack та чекає. Це дає команді, яка працює у режимі 24/7, впевненість у тому, що найбільш ризиковані операції контролюються людиною.
Шістнадцять інструментів
AegisOps постачає шістнадцять інструментів у чотирьох категоріях. Шість з них виділяються:
- get_logs є збирачем доказів. Він бере останні 500 рядків файлу журналів сервісу, зберігає необхідні рівні деталей та видаляє з кожного рядка IP-адреси, UUID та звичайні числа. Таким чином два випадки відсутності певних елементів з різних хостів вважаються одним шаблоном. Клас
Counterу Python ранжує ці шаблони та повертає найчастіше зустрічаючіся. Зчитувачі терміналу та цикл обробки даних агента отримують однаковий структурований результат; для цього не потрібен кластер Elasticsearch — достатньо файлу журналів та кількох рядків коду на Python. - get_pod_status запитує Kubernetes CoreV1Api щодо подів, позначених міткою
app={deployment}, та повідомляє про їх перезапуски, готовність та стан контейнерів. Якщо до кластера неможливо дістатися, він коректно переходить на альтернативний режим та направляє агента до функціїget_logs.
kubectl.kubernetes.io/restartedAt запускає поступове перезавантаження, після чого очікується, поки всі репліки стануть готовими, і виконується перевірка їхнього стану. Опційний крок відключення дозволяє утримати трафік поза системою під час перезавантаження.True лише тоді, коли всі поди готові та кількість відповідей типу 5xx залишається нижче 5%.Один інцидент — від початку до кінця
Ось що зробив агент у випадку перевантаження сервісу оплати OOMKill о 3:14 ранку:
[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 ✅
Усе це зайняло 187 секунд – від отримання сповіщення до вирішення проблеми пройшло трохи більше трьох хвилин, причому нікого не потрібно було будити. Саме такий час тепер зазвичай спостерігається під час інцидентів, схожих на попередні. Співробітник почав з аналізу того, що спрацювало минулого разу, як це робить старший інженер – це є проявом епізодичної пам’яті, яка виконує свою функцію.
Цей слід було відновлено для кращої зрозумілості. Реальна робота на простій локальній інфраструктурі, з одним кластером Kind без Prometheus, виглядає інакше. Інцидент було спричинено простим викликом webhook у PowerShell:
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"
}'
Агент пройшов усю процедуру від початку до кінця. Він знайшов схожі інциденти та сформулював гіпотезу про те, що сплеск трафіку перевантажує ємність подів, що збігається з попереднім випадком у пам’яті. Він викликав функцію get_pod_status та побачив, що 2 з 5 подів готові, тоді збільшив кількість реплік до 4, підтвердив стан за допомогою check_service_health та закрив інцидент як RESOLVED. У чаті Slack у каналі #all-aegisops з’явилось повідомлення, а інцидент було занесено назад у епізодичну пам’ять.
Це набагато краще, ніж збій через нестачу пам’яті у gRPC о 3 години ранку, проте цикл залишається незмінним: той самий цикл „Мислення – Дія – Спостереження“, та сама перевірка політики перед масштабуванням та той самий запис у пам’ять наприкінці. Лише умови роботи простіші. Агент не може відрізнити кластер з ноутбуків від флоту з 50 вузлів. Якщо змінити kubeconfig та налаштувати Prometheus на реальні метрики, той самий код буде працювати на реальних системах.
Slack як канал виведення інформації
Важливі дії відправляють повідомлення в Slack. Коли інцидент закривається, вузол з рішенням записує інформацію у #all-aegisops із вказанням рівня серйозності, часу усунення проблеми та повним описом рішення, що означає, що інженер, який знаходиться у режимі чергування, отримує повідомлення з зеленим галочком, а не сповіщення через пейджер.
Технологічна стек
Усе це працює на простій технологічній стеку. PostgreSQL з модулем pgvector використовується для зберігання епізодичної інформації та чекпоїнтів LangGraph. Redis зберігає політики та призначений для майбутнього обмеження швидкості запитів. Prometheus надсилає реальні показники кількості помилок у функцію check_service_health, а Grafana створює панелі керування.
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:
Додаток — це FastAPI під керуванням uvicorn. Інциденти надходять на кінцеву точку webhook, яка створює об’єкт IncidentEvent та викликає функцію run_agent(). Виконання є асинхронним, тому кілька інцидентів можуть оброблятися паралельно без блокування. Клієнт Kubernetes — це kubernetes_asyncio, який забезпечує неблокуючі виклики до кластера.
Одна проблема: функції get_pod_status та check_service_health завантажують конфігурацію Kubernetes під час імпорту модуля, а не всередині асинхронної функції. Якщо кластер недоступний під час запуску, ці функції працюють без помилок, але повертають попереджувальні повідомлення, тому переконайтеся, що KUBECONFIG або обліковий запис сервісу в кластері налаштовані перед запуском додатку.
Історія аудиту
Кожен виклик інструменту записується у TypedDict ToolInvocation: що було виконано, з якими параметрами, що повернулося, чи було це успішним, скільки часу це зайняло, коли це сталось та яке рішення політики це дозволило. Ці записи накопичуються у state["tool_invocations"] під час виконання та зберігаються у епізодичній пам’яті після завершення. Окремий інструмент write_audit_log генерує структурований рядок JSON для кожної значущої дії.
У результаті будь-яку подію можна переглянути пізніше: послідовність кроків, діючі дозволи та причина кожного з них. Саме такі записи потребують команди з контролю дотримання правил.
Одне рішення щодо дизайну впливає на все інше. Кожна дія зі змінами повідомляє про рішення політики разом із результатом перевірки стану, тож агент одночасно дізнається три речі: чи вдалося здійснити виклик, чи було це дозволено та чи є сервіс зараз у стабільному стані. Усі ці фактори впливають на його наступні рішення.
Що ще бракує
Усе вищезазначене працює безперервно. Продовження роботи обмежувалося лише можливостями локальної машини, яка намагалась запустити Ollama поруч із повною системою. З більшими ресурсами спочатку проявляються три прогалини.
Процес схвалення є найбільшою проблемою. У стані вже існує approval_status, і requires_approval вже перевіряється, проте ніщо насправді не зупиняє процес. План полягає у тому, щоб надіслати кнопку в Slack, зупинити граф та продовжити його роботу лише після того, як хтось натисне на кнопку, ще до того, як буде виконана будь-яка дія рівня L3. LangGraph може призупинити граф під час його виконання за допомогою своїх точок перевірки, тож залишається лише налаштування логіки, а не повна переробка.
Порівняння показників до та після змін — це друга проблема. check_service_health дає лише моментальний знімок стану після виправлення. Агенту потрібен дельта-показник від Prometheus — кількість помилок за п’ять хвилин до та після змін, щоб бути впевненим, що проблема справді вирішена, а не що це було лише наслідком збігу часу.
Самостійне створення посібників з роботи є довгостроковою метою. Наразі два джерела знань — минулі інциденти та посібники — ніколи не взаємодіють між собою. Після незвичайного рішення проблеми агент має написати новий фрагмент посібника та додати його до бази знань, щоб функція retrieve_runbook могла його повернути наступного разу. Це об’єднало б обидві шари пам’яті.
Що навчив проект
Виявляється, справжньою проблемою автономних агентів інфраструктури є не механізм міркувань моделі, а структура безпеки, яка її оточує. Шари пам’яті, двигун політик, журнал аудиту та механізми схвалення — ось що змушує команду довіряти інструменту навіть о 3 годині ранку.
AegisOps займає чітку позицію: структурована машина станів із чіткими межами дозволів краща за агента, який діє імпровізовано. Ця позиція досі виявилася ефективною завдяки більш детальному журналу інцидентів та агенту, який вдосконалюється після кожного нового випадку. Функція створення контрольних точок у LangGraph, чітке розділення діагностичних та дійових інструментів, а також використання TypedDict для опису станів роблять систему простою для перевірки, виправлення помилок та аудиту у разі проблем, коли виникає питання, чому агент обрав саме такі дії.
Якщо ви створюєте щось подібне, рано інвестуйте у архітектуру пам’яті. Агент із хорошою пам’яттю вартий у десять разів більше, ніж агент із трохи кращим запитом. Трансформерам потрібна увага, а агентам у реальному світі — контекст.
Пов’язана література
- Знімки пам’яті агента, навички, занепад якості та обмеження пам’яті за гілками — Чотири доповнення до вбудованого шару пам’яті у вигляді векторної БД для кодуючих агентів: портативні знімки з можливістю об’єднання, активація навичок, занепад якості з перевіркою конфліктів та пам’ять, обмежена за гілками.
- Використання інструментів у продакшені для агентів: походження даних, повторні спроби та можливість спостереження — Агент банку надмірно викликав платну API та вигадав номер телефону. Дізнайтеся про мінімальні набори інструментів, походження параметрів, трирівневі повторні спроби та показники для кожного інструменту.