Cutting MTTR: building an AI agent for incident triage and automatic resolution
Introduction
In distributed environments, the problem is rarely just detecting that something failed. Prometheus, Grafana, CloudWatch, Datadog, Loki, OpenTelemetry and other tools can identify degradation within seconds.
The real challenge starts after the alert fires.
A typical incident may require an engineer to:
- identify which application was affected;
- figure out the namespace, cluster and environment;
- check metrics;
- read logs;
- inspect Kubernetes events;
- compare against recent deployments;
- identify configuration changes;
- validate dependencies such as the database, Redis or RabbitMQ;
- decide on a corrective action;
- execute the fix;
- confirm the environment is healthy again.
Every one of these steps takes time and increases MTTR — Mean Time to Recovery (or Mean Time to Repair), the metric representing how long, on average, an organization takes to restore a service after a failure.
This is exactly the space where AI agents can be useful.
Instead of using AI only to summarize alerts, we can build an agent capable of automatically gathering evidence, correlating metrics, logs, traces and recent changes, executing pre-authorized runbooks, and validating whether the intervention actually solved the problem.
The goal should not be:
"Give an LLM admin access to the cluster so it can fix any incident."
A safe architecture should look like:
Alert
↓
Context collection
↓
Evidence correlation
↓
AI / decision engine
↓
Policy Engine
↓
Authorized runbook
↓
Controlled execution
↓
Validation
↓
Closure or escalationThis distinction matters a lot.
The AI agent should be treated as part of the operational automation platform, not as an unrestricted infrastructure administrator.
What is an AI agent for incidents
An AI agent for incidents is a system capable of receiving operational events, gathering context about the environment, analyzing possible causes and deciding which actions should run within a previously defined set of permissions.
It combines traditional observability and automation components with language models.
An implementation might integrate:
Prometheus
Grafana
Alertmanager
Loki
Tempo
OpenTelemetry
Kubernetes API
AWS APIs
Git
ArgoCD
CI/CD
CMDB
Runbooks
LLMThe LLM doesn't need to run commands directly against the infrastructure.
A safer architecture uses intermediate tools.
For example:
LLM
↓
restartDeployment(namespace, deployment)
↓
Policy Engine
↓
Kubernetes APIInstead of:
LLM
↓
shell("kubectl delete pod -A ...")The first approach lets you restrict exactly which operations can be performed.
Key concepts
MTTA
Mean Time to Acknowledge represents the time needed for an incident to be acknowledged.
An automated triage system can shrink this to roughly the interval between the alert firing and the agent processing it.
MTTD
Mean Time to Detect represents the time needed to detect the failure.
This part is usually the responsibility of the observability stack.
MTTR
Mean Time to Recovery measures how long it takes to restore the service.
This is usually where automation and AI agents show the most potential.
Runbook
Runbooks are documented procedures for solving known situations.
Example:
Problem:
Deployment with no available pods.
Diagnosis:
- check replicas;
- check events;
- check logs;
- check cluster resources.
Remediation:
- restart the rollout, if allowed;
- validate pods;
- validate the endpoint.An agent can turn static runbooks into executable workflows.
Tool Calling
In an agent-based architecture, the model is given tools it can request.
Conceptual example:
getPods(namespace)
getDeployment(name)
getPodLogs(pod)
getEvents(namespace)
getMetrics(workload)
restartDeployment(name)
rollbackDeployment(name)This is significantly safer than allowing arbitrary shell execution.
Policy Engine
The Policy Engine determines whether the action suggested by the agent can actually be executed.
For example:
restartDeployment -> allowed
scaleDeployment 3 → 5 -> allowed
scaleDeployment 3 → 100 -> denied
deleteNamespace -> denied
deleteDatabase -> deniedOr:
Production:
restart -> automatic
rollback -> human approval
delete -> forbiddenReference architecture
An architecture for Kubernetes can be structured like this:
┌──────────────────┐
│ Prometheus │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Alertmanager │
└────────┬─────────┘
│ webhook
▼
┌─────────────────────────┐
│ Incident AI Controller │
└────────────┬────────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
Kubernetes Loki Tempo
API Server Logs Traces
│
▼
Prometheus API
│
▼
Git / CI/CD
│
▼
Context Builder
│
▼
AI Reasoning
│
▼
Policy Engine
│
▼
Remediation Executor
│
▼
Kubernetes / AWSAlertmanager can forward notifications via webhook to external systems; rules are normally evaluated by Prometheus and then forwarded to Alertmanager.
How it flows
Imagine the following alert:
HighErrorRate
service=checkout-api
namespace=production
error_rate=18%The agent receives the alert and starts collecting evidence.
1. Check the Deployment
kubectl get deployment checkout-api -n productionResult:
READY UP-TO-DATE AVAILABLE
2/5 5 22. Check the pods
kubectl get pods -n production -l app=checkout-apiResult:
checkout-api-abc12 Running
checkout-api-def34 CrashLoopBackOff
checkout-api-ghi56 CrashLoopBackOff
checkout-api-jkl78 Running
checkout-api-mno90 CrashLoopBackOff3. Check events
kubectl get events -n production \
--sort-by=.metadata.creationTimestampThe agent finds:
Back-off restarting failed container4. Check logs
kubectl logs checkout-api-def34 \
-n production \
--previous \
--tail=200It finds:
Connection pool exhausted
database timeout after 5000ms5. Check metrics
Telemetry shows:
DB connections: 100/100
API latency p95: 4.8s
API error rate: 18%
CPU: 42%
Memory: 53%The agent can already rule out CPU and memory as the main cause.
6. Correlate recent changes
The agent checks Git or ArgoCD:
Deployment shipped 7 minutes earlier.
Change:
DB_POOL_SIZE=50 → DB_POOL_SIZE=100Five replicas with pools of up to 100 connections could potentially push the database limit much further than the previous configuration.
The AI then produces a hypothesis:
Likely cause:
The last deployment raised the max connection pool.
The database hit its max connection limit.
Pods started timing out and CrashLoopBackOff'ing.
Confidence: high.But the AI still doesn't execute anything.
The decision moves to the Policy Engine.
Problem → Solution → Configuration → How it works
Before
The incident required an engineer to manually run:
kubectl get pods
kubectl describe pod
kubectl logs
kubectl get eventsThen check Grafana, Loki, GitHub, ArgoCD and the database.
Average time:
Detection: 1 minute
Triage: 8 minutes
Investigation: 15 minutes
Fix: 5 minutes
Validation: 5 minutes
Approximate MTTR: 34 minutesAfter
With the agent:
Detection: 1 minute
Collection: 20 seconds
Correlation: 10 seconds
Remediation: 30 seconds
Validation: 2 minutes
MTTR: approximately 4 minutesThe gain doesn't come from AI alone.
Most of the reduction comes from automating evidence gathering and runbook execution.
Real-world production example
Consider a company running about 40 microservices on Amazon EKS.
The architecture includes:
AWS
└── EKS
├── APIs
├── Workers
├── RabbitMQ
├── Redis
├── PostgreSQL
└── Observability
├── Prometheus
├── Grafana
├── Loki
└── TempoThe agent runs in a dedicated namespace:
incident-systemand has read access to the monitored namespaces.
Destructive operations are not allowed.
Allowed automatic actions could include:
restart a Deployment
restart a specific StatefulSet
scale a workload within defined limits
re-run a Job
remove an unhealthy Pod
trigger a previously defined rollbackDepending on risk, some of these actions may require human approval.
YAML example: ServiceAccount and RBAC
Kubernetes lets you use ServiceAccounts alongside RBAC to grant only the permissions a workload needs, following the principle of least privilege.
An initial configuration might look like:
apiVersion: v1
kind: Namespace
metadata:
name: incident-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: incident-agent
namespace: incident-system
automountServiceAccountToken: true
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: incident-agent
namespace: production
rules:
- apiGroups:
- ""
resources:
- pods
- pods/log
- events
verbs:
- get
- list
- watch
- apiGroups:
- apps
resources:
- deployments
- replicasets
verbs:
- get
- list
- watch
- patch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: incident-agent
namespace: production
subjects:
- kind: ServiceAccount
name: incident-agent
namespace: incident-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: incident-agentWhat this configuration does
The ServiceAccount:
incident-agentis used by the agent.
The Role allows querying:
pods
logs
events
deployments
replicasetsThe verb:
patchallows changing certain objects, needed for example by restart mechanisms based on updating the Deployment template.
Cautions
Avoid:
resources:
- "*"
verbs:
- "*"especially in production.
Also avoid granting a ClusterRole if the agent only needs to act on specific namespaces.
Deploying the agent
apiVersion: apps/v1
kind: Deployment
metadata:
name: incident-agent
namespace: incident-system
spec:
replicas: 2
selector:
matchLabels:
app: incident-agent
template:
metadata:
labels:
app: incident-agent
spec:
serviceAccountName: incident-agent
containers:
- name: agent
image: registry.example.com/platform/incident-agent:1.4.2
ports:
- name: http
containerPort: 8080
env:
- name: ENVIRONMENT
value: production
- name: PROMETHEUS_URL
value: http://prometheus.monitoring.svc:9090
- name: LOKI_URL
value: http://loki.monitoring.svc:3100
- name: AUTO_REMEDIATION
value: "true"
- name: MAX_SCALE_FACTOR
value: "2"
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
readinessProbe:
httpGet:
path: /health/ready
port: http
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health/live
port: http
initialDelaySeconds: 15
periodSeconds: 20
---
apiVersion: v1
kind: Service
metadata:
name: incident-agent
namespace: incident-system
spec:
selector:
app: incident-agent
ports:
- port: 80
targetPort: httpThis Deployment runs two replicas of the agent so the incident response tool itself doesn't depend on a single pod.
The CPU and memory values are just an initial reference. They should be tuned using real metrics.
It's also important to avoid storing API keys directly in variables declared on the Deployment.
Use Secrets or an external secret management mechanism.
Receiving alerts from Alertmanager
A simplified Alertmanager configuration could forward critical alerts:
route:
receiver: default
routes:
- receiver: incident-agent
matchers:
- severity="critical"
receivers:
- name: default
- name: incident-agent
webhook_configs:
- url: http://incident-agent.incident-system.svc/incidents
send_resolved: trueIn this model:
Prometheus
↓
Alertmanager
↓
Webhook
↓
Incident AgentThe agent receives labels such as:
alertname
service
namespace
severity
cluster
environmentThe better the alert labels, the better the automation.
An alert with just:
something-is-brokenisn't very useful.
An alert containing:
service=payment-api
namespace=production
cluster=eks-prod
team=payments
severity=critical
runbook=payment-error-rateenables much more precise automation.
The decision engine
The agent shouldn't just send:
"There's an alert. What do I do?"to the model.
It should first build structured context.
For example:
{
"incident": {
"alert": "HighErrorRate",
"service": "checkout-api",
"namespace": "production"
},
"metrics": {
"error_rate": 18.4,
"latency_p95_ms": 4800,
"cpu_percent": 42,
"memory_percent": 53
},
"kubernetes": {
"desired_replicas": 5,
"available_replicas": 2,
"crashloop_pods": 3
},
"logs": ["connection pool exhausted", "database timeout"],
"recent_changes": [
{
"type": "deployment",
"minutes_ago": 7,
"revision": "4fd92ab"
}
]
}The AI receives normalized data instead of thousands of lines of logs.
This reduces:
- token usage;
- latency;
- cost;
- noise;
- the chance of misinterpretation.
Separating diagnosis from execution
An important architectural decision is separating:
Reasoning Enginefrom:
Execution EngineThe first can suggest:
{
"diagnosis": "database_connection_exhaustion",
"confidence": 0.94,
"action": "rollback",
"target_revision": "previous"
}The executor validates:
Does the action exist?
Does the service allow automatic rollback?
Does the environment allow it?
Is the change within the allowed window?
Is there an incident in progress?
Was there already an attempt?
Is the rollback within the configured limit?Only then does the operation run.
Autonomy by levels
Not every incident should get the same level of autonomy.
A mature architecture might adopt:
| Level | Behavior |
|---|---|
| 0 | AI only summarizes the incident |
| 1 | AI investigates and suggests actions |
| 2 | AI executes actions after approval |
| 3 | AI automatically executes safe runbooks |
| 4 | AI executes multiple actions and validates the result |
The safest path is to evolve gradually.
Don't start at the maximum level.
Security
Security is probably the most important aspect of this architecture.
RBAC
The agent should have only the resources it needs.
Kubernetes lets you restrict a ServiceAccount's permissions through RBAC.
Never use:
cluster-adminas a shortcut to make implementation easier.
IAM
If the agent needs to query AWS:
CloudWatch
EC2
RDS
EKS
SQS
SNSuse an identity associated with the workload.
On EKS, EKS Pod Identity lets you associate an IAM Role with a ServiceAccount, avoiding the need to distribute static AWS credentials inside containers.
For example, the agent might have:
cloudwatch:GetMetricData
cloudwatch:GetMetricStatistics
logs:StartQuery
logs:GetQueryResultsbut not:
ec2:TerminateInstances
rds:DeleteDBInstance
iam:*NetworkPolicy
NetworkPolicies let you control Pod communication at the IP and port level, as long as the cluster's network plugin implements enforcement.
An agent doesn't necessarily need direct access to every workload.
Example:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: incident-agent
namespace: incident-system
spec:
podSelector:
matchLabels:
app: incident-agent
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
ports:
- protocol: TCP
port: 8080
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-systemThis example should be adapted to the cluster's topology, DNS setup and required external endpoints.
NetworkPolicy shouldn't be copied directly without validating the CNI's behavior and the agent's actual dependencies.
Observability of the agent itself
Building an automated incident response system without observing the system itself would be a mistake.
The agent should produce metrics such as:
incident_agent_incidents_total
incident_agent_investigation_duration_seconds
incident_agent_actions_total
incident_agent_actions_failed_total
incident_agent_auto_remediation_total
incident_agent_escalations_total
incident_agent_llm_requests_total
incident_agent_llm_tokens_totalIt's also worth tracking:
MTTA before/after
MTTR before/after
percentage of incidents diagnosed
percentage of automatic remediations
reverted-remediation rate
false-diagnosis rateLogs
Every decision needs to leave an auditable trail.
For example:
{
"incident_id": "inc-8472",
"service": "checkout-api",
"action": "restart_deployment",
"reason": "pods_unhealthy_after_dependency_recovery",
"approved": true,
"automatic": true,
"timestamp": "2026-09-26T15:10:32Z"
}This structure can be sent to Loki.
Traces
OpenTelemetry can be used to track the full flow:
Alert received
↓
Collect Kubernetes data
↓
Collect Prometheus metrics
↓
Collect Loki logs
↓
Call LLM
↓
Policy validation
↓
Execute remediation
↓
Validate serviceThis is especially important for figuring out why the automation system itself was slow or failed during an incident.
Real-world example: a stuck worker
Imagine workers consuming messages from RabbitMQ:
RabbitMQ
↓
payment-worker
↓
PostgreSQLPrometheus identifies:
queue_messages_ready > 100000The agent checks:
current replicas: 4
CPU: 82%
consumer throughput: 400 msg/s
incoming rate: 900 msg/sThe runbook allows scaling up to 12 replicas.
The AI recommends:
4 → 8 replicasThe Policy Engine checks:
maxReplicas = 12and authorizes it.
After five minutes:
incoming rate: 900 msg/s
consumer throughput: 1100 msg/s
queue depth: decreasingThe remediation is considered successful.
Afterward, the agent can scale the workload back down once the queue normalizes, or that responsibility can belong to mechanisms like HPA or KEDA.
In this case, the structural fix would probably be to use autoscaling driven by queue depth instead of permanently relying on the agent.
This distinction matters:
AI shouldn't replace deterministic automations that already solve the problem better.
Development vs. production
In development, it might be acceptable to allow:
automatic restart;
automatic scaling;
rollback tests;
runbook simulation;
higher log verbosity.In production, additional controls are needed:
minimal RBAC;
minimal IAM;
audit trail;
rate limiting;
timeouts;
circuit breakers;
approval for critical actions;
execution limits;
loop protection;
concurrency control;
rollback of the runbook itself.There should also be a kill switch.
For example:
AUTO_REMEDIATION=falseimmediately turning the system read-only.
Troubleshooting
Agent can't list pods
Test:
kubectl auth can-i list pods \
-n production \
--as=system:serviceaccount:incident-system:incident-agentIf it returns:
nocheck the Role and RoleBinding.
Checking the agent
kubectl get pods -n incident-systemThen:
kubectl describe pod \
-n incident-system \
-l app=incident-agentLogs
kubectl logs \
-n incident-system \
-l app=incident-agent \
--tail=200Events
kubectl get events \
-n incident-system \
--sort-by=.metadata.creationTimestampChecking permissions
kubectl auth can-i patch deployments \
-n production \
--as=system:serviceaccount:incident-system:incident-agentWebhook not arriving
Check the Service:
kubectl get svc -n incident-systemThen:
kubectl get endpoints \
-n incident-system \
incident-agentAlso check:
NetworkPolicy
DNS
Service selector
containerPort
targetPort
readinessProbeBest practices
A mature implementation should follow a few principles.
- Automate repetitive incidents first. Known CrashLoopBackOff, unhealthy pods, stuck workers, accumulated queues and known transient failures are better candidates than novel incidents.
- Start with AI for investigation only. Compare the diagnoses produced by the AI against engineers' diagnoses before enabling remediation.
- Use typed tools. Prefer
restartDeployment()overexecuteShell().
- Implement limits. An agent should never scale 5 pods to 500 simply because it misread a metric.
- Control repetition. For example:
maximum of 1 restart per deployment every 15 minutes.- Implement idempotency. Receiving the same alert twice should not trigger two concurrent remediations.
- Preserve evidence. Collect logs and relevant information before restarting a workload.
- Measure results. A real reduction in MTTR matters more than the number of incidents handled by AI.
What to avoid
Giving the agent cluster-admin
It's the fastest way to build something that works, and one of the worst decisions for production.
Allowing arbitrary shell access
A tool like:
execute_command(command)turns virtually any misinterpretation into a potentially dangerous action.
Prefer specific, previously validated operations.
Sending all logs to the LLM
Besides being expensive, this increases noise.
Do this first:
filtering
↓
aggregation
↓
extracting relevant events
↓
LLMExecuting actions based only on the model's "confidence"
Don't use something like:
confidence > 90%
→ execute any actionModel confidence doesn't replace deterministic controls.
Creating remediation loops
Imagine:
Pod fails
↓
agent restarts it
↓
Pod fails
↓
agent restarts it
↓
Pod fails
↓
agent restarts itThis can mask the root cause.
Set limits:
maxAttempts = 2
cooldown = 15mAfter that:
escalateToHuman()Costs and FinOps
An agent's cost includes several components:
CPU/memory for the workload itself
log storage
metric queries
log queries
traces
model calls
tokens
external APIsThe most common mistake is sending large volumes of telemetry directly to the model.
For example:
50,000 Loki lines
→ LLMIt's usually better to do:
50,000 lines
↓
specific query
↓
aggregation
↓
50 relevant events
↓
LLMThis can significantly reduce tokens, latency and cost.
AI doesn't replace autoscaling
There's an important distinction here.
If the problem is:
CPU > 80%the solution is probably HPA.
If it's:
growing RabbitMQ queueKEDA might be more appropriate.
If it's:
cluster out of capacityKarpenter can solve it.
The agent should mainly step in when the problem requires correlating multiple pieces of evidence or choosing between different runbooks.
A mature platform tends to follow:
Deterministic automation
↓
Autoscaling
↓
Self-healing
↓
Runbooks
↓
AI agent
↓
Humannot:
Any problem
↓
LLMWhen to use this
This approach makes sense especially in environments with:
many microservices;
Kubernetes;
high alert volume;
on-call rotation;
known runbooks;
recurring problems;
mature observability stack;
SRE or Platform Engineering;
high availability requirements.It's particularly interesting when a large part of MTTR comes from manual information gathering and correlation.
When not to use this
An AI solution can be unnecessary when:
there are few services;
there are few incidents;
the environment is simple;
there are no runbooks;
observability is still weak;
failures can be fixed with simple scripts.If a 30-line CronJob solves the problem with predictable behavior, adding an AI agent likely increases complexity without proportional value.
Recommended evolution
A safe rollout could look like:
Phase 1
Alerts
↓
AI generates a summary
Phase 2
Alerts
↓
AI collects metrics/logs
↓
AI suggests a diagnosis
Phase 3
AI suggests a runbook
↓
Human approves
Phase 4
AI automatically executes
low-risk runbooks
Phase 5
AI investigates
↓
executes
↓
validates
↓
reverts if neededThis progression builds operational trust based on real results.
Practical summary
The real value of an AI agent for incidents isn't letting a model run commands freely.
It's combining:
Observability
+
Context
+
Runbooks
+
Automation
+
AI
+
Policies
+
Audit trailto turn a process like:
alert
→ engineer wakes up
→ opens Grafana
→ looks up the service
→ opens Kubernetes
→ checks pods
→ reads logs
→ checks Git
→ finds the cause
→ executes the fix
→ monitors recoveryinto:
alert
↓
automatic collection
↓
correlation
↓
diagnosis
↓
policy
↓
runbook
↓
remediation
↓
validationkeeping the engineer in the loop only when necessary.
Conclusion
Cutting MTTR doesn't just mean responding to alerts faster.
It means reducing the number of manual decisions needed between detecting a failure and recovering the service.
AI agents can play an important role in this because they can correlate information from Kubernetes, Prometheus, Loki, OpenTelemetry, Git, CI/CD, databases and cloud providers within a single investigation flow.
However, the safest architecture isn't one where AI gets unrestricted access to the infrastructure.
The more robust model is:
Observability
↓
Incident Controller
↓
Context Builder
↓
AI Reasoning
↓
Policy Engine
↓
Runbook Executor
↓
Infrastructure
↓
Validation EngineIn this design, AI helps decide; deterministic policies define what's allowed; controlled executors make the changes; and observability confirms whether the intervention worked.
This approach also changes the role of SRE and Platform Engineering. Instead of continuously running the same manual procedures, teams start turning operational knowledge into automated runbooks, policies and recovery mechanisms.
The most interesting outcome isn't simply a "bot that fixes alerts."
It's creating a context-driven self-healing layer, capable of automating known incidents, speeding up investigation of unknown ones, and maintaining clear security and governance controls.
Combined with autoscaling, observability, Infrastructure as Code, GitOps, security policies and reliability engineering, this architecture can turn incident response from a mostly reactive process into a progressively automated system — cutting MTTR without turning autonomy into an absence of control.
Related articles
Want to apply this to your environment?
Book a free 30-minute call. I will look at your setup and tell you where to start.
Book a callGet new articles by email
One email when a new article on infrastructure, backend and AI is published. No spam, unsubscribe anytime.