AI-агент для SRE: автодиагностика инцидентов

Article Markdown: Building an AI Agent That Runs Your SRE Operations — What I Learned, What Works, and How You Can Do It Too

Not a smarter alert. Not a better dashboard. An actual reasoning system — one that reads Splunk, queries AppDynamics, checks the Kubernetes pods, searches the Confluence runbooks, looks at what GitLab deployed recently, and comes back with a diagnosis and a proposed action.

I’ve been building this. Here’s what I learned.

  • * * * *

Why SRE Is a Perfect Fit for an AI Agent

SRE work is fundamentally about **correlating information across multiple systems** and making decisions under uncertainty. During an incident, an engineer does something like:

1.  Check the alert. What's the signal?
2.  Look at the metrics. Is this a spike, a trend, or a plateau?
3.  Check recent deployments. Did anything go out?
4.  Search the runbooks. Has this happened before?
5.  Look at logs. What's actually failing?
6.  Decide on an action. Rollback? Scale? Page someone?

This is exactly what an LLM-based agent is good at: **structured reasoning over heterogeneous data sources**, with tool-calling to gather more information.

The difference between a chatbot and an agent is that the agent doesn't wait for you to hand it information. It goes and gets it.

* * * * *

The Architecture
----------------

Here's what I built. The core is a **ReAct-style agent loop** (Reason → Act → Observe → Repeat) running on top of an LLM (I used GPT-4o, but Claude 3.5 works well too).

The agent has access to a set of tools:

```
tools = [
    splunk_search,          # Run a Splunk query, return top results
    appdynamics_get_health, # Get health status of an AppDynamics application tier
    k8s_get_pods,           # List pods in a namespace, with status
    k8s_describe_pod,       # Describe a specific pod
    confluence_search,      # Search Confluence for a page matching a query
    gitlab_recent_commits,  # Get recent commits/deployments to a service
    pagerduty_create_alert, # Escalate to a human
]
```

Each tool is a Python function with a docstring that becomes part of the tool schema. The LLM sees the tool name, description, and parameter spec. It decides which tool to call, with what arguments, and then receives the result before deciding what to do next.

The loop looks like this:

```
while not agent.done:
    response = llm.chat(messages, tools=tools)

    if response.tool_call:
        result = execute_tool(response.tool_call)
        messages.append(tool_result(result))
    else:
        agent.done = True
        final_answer = response.content
```

That's it. That's the core.

* * * * *

Making the Tools Actually Work
-------------------------------

The hardest part isn't the agent loop. It's the tools.

### Splunk

Splunk's REST API is straightforward but you need to handle async job creation. You submit a search, poll for completion, then fetch results:

```
def splunk_search(query: str, earliest: str = "-1h", latest: str = "now") -> list[dict]:
    """
    Run a Splunk search query. Returns up to 20 results.
    Use SPL syntax. Example: 'index=prod sourcetype=app_logs level=ERROR'
    """
    job = splunk_client.jobs.create(query, earliest_time=earliest, latest_time=latest)
    while not job.is_done():
        time.sleep(1)
    return [dict(r) for r in job.results()][:20]
```

Key insight: **return less, not more**. The LLM doesn't need 500 log lines. Give it 20. Let it ask for more specific queries if needed.

### AppDynamics

AppDynamics has a REST API that returns health rules, violations, and tier metrics. The tricky part is that their API is authenticated with a controller-level token and the endpoint structures differ between SaaS and on-prem.

```
def appdynamics_get_health(app_name: str, tier_name: str = None) -> dict:
    """
    Get current health status for an AppDynamics application.
    Optionally filter to a specific tier. Returns active violations and overall health.
    """
    violations = appdyn_client.get_health_rule_violations(app_name, duration_in_mins=60)
    tier_summary = appdyn_client.get_tier_summary(app_name, tier_name) if tier_name else None
    return {
        "violations": violations,
        "tier": tier_summary
    }
```

### Kubernetes

Use the official Python client. The key is structuring what you return — raw Kubernetes objects are verbose:

```
def k8s_get_pods(namespace: str) -> list[dict]:
    """
    List all pods in a Kubernetes namespace. Returns pod name, status, restarts, and age.
    Use this to check for crashing or pending pods.
    """
    v1 = client.CoreV1Api()
    pods = v1.list_namespaced_pod(namespace)
    return [
        {
            "name": p.metadata.name,
            "status": p.status.phase,
            "restarts": sum(cs.restart_count for cs in (p.status.container_statuses or [])),
            "ready": all(cs.ready for cs in (p.status.container_statuses or [])),
        }
        for p in pods.items
    ]
```

### Confluence

Confluence's REST API has a content search endpoint. You pass a CQL query and get back page titles and bodies. The challenge: page bodies are in Confluence's storage format (XML-ish). Strip it:

```
def confluence_search(query: str) -> list[dict]:
    """
    Search Confluence for runbooks or documentation matching a query.
    Returns page title and a plain-text excerpt of the content.
    """
    results = confluence_client.cql(f'text ~ "{query}" AND space = "RUNBOOKS"', limit=3)
    pages = []
    for r in results["results"]:
        body = r["body"]["storage"]["value"]
        text = BeautifulSoup(body, "html.parser").get_text()[:2000]
        pages.append({"title": r["title"], "content": text})
    return pages
```

### GitLab

The GitLab API is clean. You want recent commits or pipeline runs for a service:

```
def gitlab_recent_commits(project_path: str, branch: str = "main", limit: int = 5) -> list[dict]:
    """
    Get recent commits to a GitLab project. Use this to check if a recent deployment
    might have caused a regression.
    """
    project = gl_client.projects.get(project_path)
    commits = project.commits.list(ref_name=branch, per_page=limit)
    return [
        {
            "sha": c.short_id,
            "message": c.message,
            "author": c.author_name,
            "date": c.created_at,
        }
        for c in commits
    ]
```

* * * * *

The System Prompt
-----------------

The system prompt is doing a lot of work. Here's the core of what I use:

```
You are an SRE agent. Your job is to investigate incidents, diagnose root causes, and propose actions.

When given an incident description or alert, you should:
1. Gather relevant data using the tools available to you.
2. Reason step by step about what the data tells you.
3. Search for prior incidents or runbooks that match the pattern.
4. Propose a concrete action: rollback, scale, restart, page someone, or declare it a non-issue.

Rules:
- Always check at least two independent data sources before concluding.
- If you're unsure, say so, and tell me what additional information would resolve your uncertainty.
- Never call pagerduty_create_alert unless you've exhausted all other diagnostic options.
- Be concise in your final answer. Include: what you found, what you think caused it, and what to do.
```

The "at least two sources" rule matters. Without it, the agent sometimes latches onto the first signal it finds and stops.

* * * * *

A Real Example Run
------------------

Here's what a real agent run looks like (condensed):

**Input:**\
`"Latency spike on checkout service. P99 > 4s for the last 10 minutes."`

**Agent thinks:**\
*I should check AppDynamics for the checkout service health, then look at Kubernetes pod status, and check recent deployments.*

**Tool call 1:** `appdynamics_get_health("checkout-service")`\
→ 2 active violations: "Backend call time > 2s" on the `payment-gateway` tier

**Tool call 2:** `k8s_get_pods("checkout")`\
→ All pods running, 0 restarts, all ready

**Tool call 3:** `gitlab_recent_commits("platform/checkout-service")`\
→ Last commit 3 hours ago, message: "Increase payment gateway timeout to 10s"

**Tool call 4:** `confluence_search("payment gateway timeout latency")`\
→ Finds runbook: "If payment gateway latency > 2s, check for upstream rate limiting from provider"

**Tool call 5:** `splunk_search("index=prod service=payment-gateway ERROR timeout")`\
→ 847 `connection timeout` errors in the last hour, all to `api.paymentprovider.com`

**Final output:**\
*Root cause: External payment provider appears to be rate-limiting or degraded. Recent commit increased internal timeout threshold, which is masking fast-fail behavior. Recommend: (1) Check payment provider status page. (2) If provider is degraded, enable payment fallback mode per runbook P-14. (3) Consider reverting the timeout change to restore fast-fail.*

That took about 40 seconds and would have taken a human engineer 15 minutes.

* * * * *

What Didn't Work
----------------

**Giving the agent too many tools.** When I added a tool for every possible system, the agent started making unnecessary tool calls "just to check." Keep the tool set focused.

**Unstructured tool outputs.** If your tool returns raw JSON or a blob of text, the LLM wastes tokens parsing it. Structure your returns as clean dicts with obvious keys.

**Asking the agent to take actions directly.** When I let the agent call `kubectl rollout restart` or trigger CI pipelines, it would sometimes act prematurely. Now the agent proposes actions but doesn't execute them without a human confirmation step. This is the right call for production systems.

**Overly long system prompts.** I had a version with a 2000-word system prompt covering every edge case. The model started treating it like a checklist and doing unnecessary work. Short, principled prompts perform better.

* * * * *

What Works Surprisingly Well
-----------------------------

**The agent is good at temporal correlation.** "Deployment 3 hours ago, error spike 3 hours ago" — this is obvious to a human but it's also obvious to the agent. It makes these connections reliably.

**It reads runbooks better than most humans.** Runbooks are often dense and poorly organized. The agent reads them, extracts the relevant action, and applies it without losing the thread.

**It knows what it doesn't know.** When I give it an alert with no useful tool results, it says so: "I don't have enough signal to diagnose this. I'd recommend pulling the full error trace from X and checking Y." That's useful.

**Latency is acceptable.** A full 5-tool-call investigation takes 30--60 seconds. For an incident that would take a human 15 minutes, that's fine.

* * * * *

Deployment Considerations
--------------------------

-   **Run it in a read-only mode first.** No write tools. Diagnosis only. Earn trust before giving it action capabilities.
-   **Log every tool call and response.** You want an audit trail. When the agent makes a wrong diagnosis, you need to understand why.
-   **Gate the PagerDuty escalation.** The agent should only escalate if it's confident the issue is real and can't be resolved without human intervention.
-   **Wrap tools in timeouts.** External APIs hang. A Splunk query that takes 3 minutes will stall your agent loop.
-   **Token costs are manageable.** A full investigation run with GPT-4o costs about $0.04--0.08. At incident volume, that's noise.

* * * * *

The Bigger Picture
------------------

The goal isn't to replace SRE engineers. The goal is to handle **the mechanical part of incident response** — the part that's about navigating dashboards and cross-referencing systems — so engineers can focus on the parts that actually require judgment: architectural decisions, postmortems, capacity planning, building the systems that make future incidents less likely.

An SRE agent that correctly diagnoses 70% of incidents automatically, and brings a full diagnostic report for the other 30%, is an enormous force multiplier. The bottleneck in incident response isn't human intelligence. It's human attention and context-switching cost.

This is solvable now, with current models, current APIs, and a few hundred lines of Python.

---

If you want to build this: start with two tools (Splunk + Kubernetes), write a good system prompt, and run it against your last 10 incidents. See if it gets them right. Add tools from there.

The hardest part isn't the AI. It's writing clean tool wrappers.

== Почему SRE идеально подходит для AI-агента

Работа SRE по своей сути сводится к *корреляции информации из множества систем* и принятию решений в условиях неопределённости. Во время инцидента инженер действует примерно так:

. Смотрит на алерт. Что это за сигнал?
. Анализирует метрики. Это выброс, тренд или плато?
. Проверяет последние деплои. Что выходило в прод?
. Ищет в рунбуках. Такое уже случалось?
. Читает логи. Что конкретно ломается?
. Принимает решение. Откатить? Масштабировать? Позвонить кому-то?

Это именно то, в чём силён агент на основе большой языковой модели (LLM-based agent): *структурированные рассуждения над разнородными источниками данных* с вызовом инструментов для получения дополнительной информации.

Разница между чат-ботом и агентом в том, что агент не ждёт, пока вы принесёте ему данные. Он идёт и добывает их сам.

== Архитектура

Вот что я построил. В основе — *цикл агента в стиле ReAct* (Reason → Act → Observe → Repeat, то есть «рассуждай → действуй → наблюдай → повтори»), работающий поверх LLM (я использовал GPT-4o, но Claude 3.5 тоже хорошо справляется).

Агент имеет доступ к набору инструментов:

[source,python]
----
tools = [
    splunk_search,          # Выполнить поисковый запрос в Splunk, вернуть топ результатов
    appdynamics_get_health, # Получить статус здоровья уровня приложения в AppDynamics
    k8s_get_pods,           # Вывести поды в неймспейсе с их статусом
    k8s_describe_pod,       # Описать конкретный под
    confluence_search,      # Найти страницу в Confluence по запросу
    gitlab_recent_commits,  # Получить последние коммиты/деплои сервиса
    pagerduty_create_alert, # Эскалировать на человека
]
----

Каждый инструмент — это Python-функция с докстрингом, который становится частью схемы инструмента. LLM видит название инструмента, его описание и спецификацию параметров. Модель сама решает, какой инструмент вызвать, с какими аргументами, а затем получает результат и решает, что делать дальше.

Цикл выглядит так:

[source,python]
----
while not agent.done:
    response = llm.chat(messages, tools=tools)

    if response.tool_call:
        result = execute_tool(response.tool_call)
        messages.append(tool_result(result))
    else:
        agent.done = True
        final_answer = response.content
----

Вот и всё. Это и есть ядро системы.

== Как сделать инструменты рабочими

Самое сложное — не цикл агента. Самое сложное — это инструменты.

=== Splunk

REST API Splunk достаточно прост, но нужно обрабатывать асинхронное создание задач: отправить запрос, ждать завершения, затем получить результаты.

[source,python]
----
def splunk_search(query: str, earliest: str = "-1h", latest: str = "now") -> list[dict]:
    """
    Выполнить поисковый запрос в Splunk. Возвращает до 20 результатов.
    Используйте синтаксис SPL. Например: 'index=prod sourcetype=app_logs level=ERROR'
    """
    job = splunk_client.jobs.create(query, earliest_time=earliest, latest_time=latest)
    while not job.is_done():
        time.sleep(1)
    return [dict(r) for r in job.results()][:20]
----

Ключевой принцип: *возвращайте меньше, а не больше*. LLM не нужно 500 строк логов. Дайте ей 20. Если нужно — она сама запросит более конкретный запрос.

=== AppDynamics

У AppDynamics есть REST API, возвращающий правила работоспособности (health rules), нарушения и метрики уровней. Сложность в том, что API аутентифицируется токеном уровня контроллера, а структура эндпоинтов различается для SaaS и on-prem версий.

[source,python]
----
def appdynamics_get_health(app_name: str, tier_name: str = None) -> dict:
    """
    Получить текущий статус работоспособности для приложения в AppDynamics.
    Опционально фильтровать по конкретному уровню. Возвращает активные нарушения и общий статус.
    """
    violations = appdyn_client.get_health_rule_violations(app_name, duration_in_mins=60)
    tier_summary = appdyn_client.get_tier_summary(app_name, tier_name) if tier_name else None
    return {
        "violations": violations,
        "tier": tier_summary
    }
----

=== Kubernetes

Используйте официальный Python-клиент. Главное — грамотно структурировать возвращаемые данные: сырые объекты Kubernetes очень многословны.

[source,python]
----
def k8s_get_pods(namespace: str) -> list[dict]:
    """
    Вывести все поды в неймспейсе Kubernetes. Возвращает имя пода, статус, количество рестартов и возраст.
    Используйте для проверки крашащихся или зависших подов.
    """
    v1 = client.CoreV1Api()
    pods = v1.list_namespaced_pod(namespace)
    return [
        {
            "name": p.metadata.name,
            "status": p.status.phase,
            "restarts": sum(cs.restart_count for cs in (p.status.container_statuses or [])),
            "ready": all(cs.ready for cs in (p.status.container_statuses or [])),
        }
        for p in pods.items
    ]
----

=== Confluence

В REST API Confluence есть эндпоинт поиска по контенту. Вы передаёте CQL-запрос и получаете заголовки и тела страниц. Сложность в том, что тела страниц хранятся в формате Confluence Storage Format (нечто вроде XML). Его нужно зачищать:

[source,python]
----
def confluence_search(query: str) -> list[dict]:
    """
    Найти рунбуки или документацию в Confluence по запросу.
    Возвращает заголовок страницы и отрывок содержимого в виде простого текста.
    """
    results = confluence_client.cql(f'text ~ "{query}" AND space = "RUNBOOKS"', limit=3)
    pages = []
    for r in results["results"]:
        body = r["body"]["storage"]["value"]
        text = BeautifulSoup(body, "html.parser").get_text()[:2000]
        pages.append({"title": r["title"], "content": text})
    return pages
----

=== GitLab

API GitLab чистый и удобный. Нас интересуют последние коммиты или запуски пайплайнов для сервиса:

[source,python]
----
def gitlab_recent_commits(project_path: str, branch: str = "main", limit: int = 5) -> list[dict]:
    """
    Получить последние коммиты в проект GitLab. Используйте, чтобы проверить,
    не вызвал ли недавний деплой регрессию.
    """
    project = gl_client.projects.get(project_path)
    commits = project.commits.list(ref_name=branch, per_page=limit)
    return [
        {
            "sha": c.short_id,
            "message": c.message,
            "author": c.author_name,
            "date": c.created_at,
        }
        for c in commits
    ]
----

== Системный промпт

Системный промпт (system prompt) несёт на себе огромную нагрузку. Вот его суть в том виде, который я использую:

[source,text]
----
Ты — SRE-агент. Твоя задача — расследовать инциденты, диагностировать первопричины и предлагать действия.

Получив описание инцидента или алерт, ты должен:
1. Собрать релевантные данные с помощью доступных инструментов.
2. Рассуждать шаг за шагом о том, что говорят тебе данные.
3. Искать предыдущие инциденты или рунбуки, соответствующие паттерну.
4. Предложить конкретное действие: откат, масштабирование, рестарт, эскалация на человека или признание проблемы несущественной.

Правила:
- Всегда проверяй как минимум два независимых источника данных, прежде чем делать выводы.
- Если не уверен — скажи об этом и укажи, какая дополнительная информация помогла бы устранить неопределённость.
- Никогда не вызывай pagerduty_create_alert, пока не исчерпаны все другие диагностические варианты.
- Будь краток в финальном ответе. Включи: что ты обнаружил, что, по твоему мнению, стало причиной, и что нужно сделать.
----

Правило «как минимум два источника» важно. Без него агент порой цепляется за первый же найденный сигнал и останавливается.

== Пример реального запуска

Вот как выглядит реальный запуск агента (в сокращении):

*Входные данные:*
`"Всплеск задержки в сервисе checkout. P99 > 4s последние 10 минут."`

*Агент рассуждает:*
_Нужно проверить AppDynamics для сервиса checkout, затем посмотреть на статус подов в Kubernetes и проверить последние деплои._

*Вызов инструмента 1:* `appdynamics_get_health("checkout-service")`
→ 2 активных нарушения: «Backend call time > 2s» на уровне `payment-gateway`

*Вызов инструмента 2:* `k8s_get_pods("checkout")`
→ Все поды запущены, 0 рестартов, все готовы

*Вызов инструмента 3:* `gitlab_recent_commits("platform/checkout-service")`
→ Последний коммит 3 часа назад, сообщение: «Increase payment gateway timeout to 10s»

*Вызов инструмента 4:* `confluence_search("payment gateway timeout latency")`
→ Найден рунбук: «Если задержка payment gateway > 2s, проверьте наличие rate limiting со стороны провайдера»

*Вызов инструмента 5:* `splunk_search("index=prod service=payment-gateway ERROR timeout")`
→ 847 ошибок `connection timeout` за последний час, все к `api.paymentprovider.com`

*Финальный ответ:*
_Первопричина: внешний платёжный провайдер, по всей видимости, применяет rate limiting или деградировал. Последний коммит увеличил внутренний порог таймаута, что маскирует поведение быстрого отказа (fast-fail). Рекомендации: (1) Проверьте страницу статуса платёжного провайдера. (2) Если провайдер деградировал — включите резервный режим оплаты согласно рунбуку P-14. (3) Рассмотрите откат изменения таймаута, чтобы восстановить fast-fail._

На это ушло около 40 секунд. Инженер-человек потратил бы 15 минут.

== Что не сработало

*Слишком много инструментов.* Когда я добавил по инструменту на каждую возможную систему, агент начал делать лишние вызовы «просто чтобы проверить». Держите набор инструментов сфокусированным.

*Неструктурированный вывод инструментов.* Если инструмент возвращает сырой JSON или кусок текста, LLM тратит токены на его разбор. Структурируйте возвращаемые данные в виде аккуратных словарей с понятными ключами.

*Разрешение агенту самостоятельно выполнять действия.* Когда я позволил агенту вызывать `kubectl rollout restart` или запускать CI-пайплайны, он иногда действовал преждевременно. Теперь агент предлагает действия, но не выполняет их без подтверждения человека. Для production-систем это единственно правильный подход.

*Слишком длинные системные промпты.* У меня была версия с промптом на 2000 слов, охватывавшим все пограничные случаи. Модель начала воспринимать его как чеклист и выполнять лишнюю работу. Короткие, принципиальные промпты работают лучше.

== Что работает неожиданно хорошо

*Агент хорошо справляется с временно́й корреляцией.* «Деплой три часа назад, всплеск ошибок три часа назад» — для человека это очевидно, но и для агента тоже. Такие связи он устанавливает стабильно.

*Рунбуки он читает лучше большинства людей.* Рунбуки часто плотные и плохо организованные. Агент читает их, извлекает нужное действие и применяет его, не теряя нить рассуждений.

*Он знает, чего не знает.* Когда я даю ему алерт без полезных результатов инструментов, он прямо говорит: «У меня недостаточно сигналов для диагностики. Рекомендую получить полный трейс ошибки из X и проверить Y.» Это ценно.

*Задержка приемлемая.* Полное расследование с пятью вызовами инструментов занимает 30–60 секунд. Для инцидента, на который у человека ушло бы 15 минут, это вполне нормально.

== Соображения по развёртыванию

* *Сначала запускайте в режиме только чтения.* Никаких инструментов записи. Только диагностика. Завоюйте доверие, прежде чем давать агенту возможность что-то менять.
* *Логируйте каждый вызов инструмента и его ответ.* Вам нужна цепочка аудита. Когда агент ставит неверный диагноз, вы должны понять почему.
* *Контролируйте эскалацию в PagerDuty.* Агент должен эскалировать только в том случае, если уверен, что проблема реальна и не может быть решена без человека.
* *Оборачивайте инструменты в таймауты.* Внешние API зависают. Splunk-запрос, выполняющийся 3 минуты, заморозит цикл агента.
* *Стоимость токенов управляема.* Одно полное расследование с GPT-4o стоит около $0.04–0.08. При реальном объёме инцидентов это несущественная сумма.

== Более широкая перспектива

Цель — не заменить SRE-инженеров. Цель — взять на себя *механическую часть реагирования на инциденты* — ту, что связана с навигацией по дашбордам и перекрёстной проверкой систем, — чтобы инженеры могли сосредоточиться на том, что действительно требует суждения: архитектурных решениях, постмортемах, планировании ёмкости, построении систем, которые делают будущие инциденты менее вероятными.

SRE-агент, который автоматически правильно диагностирует 70% инцидентов и предоставляет полный диагностический отчёт для оставшихся 30%, — это колоссальный мультипликатор сил. Узкое место в реагировании на инциденты — не человеческий интеллект. Это человеческое внимание и стоимость переключения контекста.

Это решаемо уже сейчас: с помощью текущих моделей, текущих API и нескольких сотен строк на Python.

---

Если хотите это построить: начните с двух инструментов (Splunk + Kubernetes), напишите хороший системный промпт и прогоните агента по последним 10 инцидентам. Посмотрите, правильно ли он их разберёт. Добавляйте инструменты по мере необходимости.

Самое сложное — не AI. Самое сложное — написать чистые обёртки для инструментов.
© 2026 meganuke