This guide covers LLM incident runbooks with production-focused examples and trade-offs.
Generic runbook chatbots hallucinate remediations under sev-1 pressure. The unfair advantage is grounding every suggested action in live CloudWatch signals — Live Tail snippets, Logs Insights aggregates, X-Ray traces, and alarm state — so the assistant cites evidence before it proposes a rollback, a scale-out, or a feature-flag flip.⚡ TL;DR: Build a retrieval + tool loop that pulls alarm context, runs pre-approved Logs Insights queries, fetches recent X-Ray traces, and only then asks the model to draft a remediation. Require citations (logGroup,queryId,traceId) on every claim. Cap blast radius with allowlisted actions. Pair with Lambda Powertools structured logs and timeouts/retries/DLQs so signals exist when you need them.
Bind the assistant to alarm and service context — LLM incident runbooks
On page, inject the firing alarm ARN, dimensions, and service map — never a free-form “what’s wrong?” with no scope.// incident-bot/context.ts
export type IncidentContext = {
alarmArn: string;
service: string; // e.g. checkout-api
env: "prod" | "staging";
region: string;
startedAt: string; // ISO
severity: "SEV1" | "SEV2" | "SEV3";
};
export function systemPrompt(ctx: IncidentContext): string {
return [
"You are an on-call assistant. Cite CloudWatch/X-Ray evidence for every claim.",
`Service=${ctx.service} env=${ctx.env} region=${ctx.region} sev=${ctx.severity}`,
`Alarm=${ctx.alarmArn} started=${ctx.startedAt}`,
"Never invent metric names. Prefer tool results over memory.",
"Remediations must map to allowlisted runbook IDs.",
].join("\n");
}
❌ Dump the entire company wiki and hope it finds the right playbook.
Pre-approve Logs Insights queries as tools
Do not let the model invent arbitrary Insights queries against* log groups. Ship parameterized, reviewed queries.
# tools/logs_insights.py
import time, boto3
logs = boto3.client("logs")
APPROVED = {
"error_rate_by_path": """
fields @timestamp, path, status
| filter status >= 500 and service = '{service}'
| stats count() as errors by path
| sort errors desc
| limit 20
""",
"p99_latency": """
fields @timestamp, latencyMs, path
| filter service = '{service}'
| stats pct(latencyMs, 99) as p99 by path
| sort p99 desc
| limit 20
""",
}
def run_approved(query_id: str, service: str, log_group: str, start: int, end: int) -> dict:
if query_id not in APPROVED:
raise ValueError("query_not_allowlisted")
q = APPROVED[query_id].format(service=service)
# ❌ Never: interpolate raw model text into the query string
start_q = logs.start_query(
logGroupName=log_group,
startTime=start,
endTime=end,
queryString=q,
)
qid = start_q["queryId"]
while True:
r = logs.get_query_results(queryId=qid)
if r["status"] in ("Complete", "Failed", "Cancelled"):
return {"queryId": qid, "status": r["status"], "results": r.get("results", [])}
time.sleep(0.4)
Pull X-Ray traces before blaming “the database”
// tools/xray.ts
import { XRayClient, GetTraceSummariesCommand, BatchGetTracesCommand } from "@aws-sdk/client-xray";
const xray = new XRayClient({});
export async function slowTraces(service: string, start: Date, end: Date) {
const summaries = await xray.send(new GetTraceSummariesCommand({
StartTime: start,
EndTime: end,
FilterExpression: `service("${service}") AND responseTime > 2`,
}));
const ids = (summaries.TraceSummaries ?? []).slice(0, 5).map(t => t.Id!).filter(Boolean);
if (!ids.length) return { traces: [], note: "no_slow_traces" };
const batch = await xray.send(new BatchGetTracesCommand({ TraceIds: ids }));
// ✅ Return truncated segment names + durations, not full payloads with PII
return {
traces: (batch.Traces ?? []).map(t => ({
id: t.Id,
duration: t.Duration,
segments: (t.Segments ?? []).slice(0, 12).map(s => s.Document?.slice(0, 400)),
})),
};
}
evidence: [{type, id, snippet}]. Reject replies without evidence in a validator — treat uncited remediations as failed tool use.
Allowlist remediations; never open-ended shell
# runbooks/allowlist.yaml
actions:
- id: rollback_ecs_service
requires: ["evidence.deploy_marker", "sev<=SEV2"]
params: ["service", "previousTaskDef"]
- id: scale_out_asg
requires: ["evidence.cpu_or_latency"]
params: ["asgName", "desiredCapacity"]
maxDesired: 20
- id: flip_feature_flag
requires: ["evidence.error_burst"]
params: ["flagKey", "value"]
# ❌ no: arbitrary aws cli / kubectl from the model
function assertAction(action: { id: string; params: Record<string, unknown> }, allow: Set<string>) {
if (!allow.has(action.id)) throw new Error(`deny:${action.id}`);
// ✅ Human approval gate for SEV1 destructive actions
}
Observability of the incident bot itself
Emit: tool latency, citation rate, human override rate, wrong-query denials. Illustrative SLO: ≥95% of SEV1 answers include ≥1 Logs Insights or X-Ray citation; median tool round-trip under ~3s for Insights on warm queries.Closing checklist
✅ Dos – ✅ Inject alarm + service + time window into the system prompt – ✅ Allowlist Logs Insights query templates; parameterize service only – ✅ CitequeryId / traceId on every remediation claim
– ✅ Gate destructive actions behind human approval + allowlist
– ✅ Redact secrets from Live Tail before model context
❌ Don’ts
– ❌ Don’t let the model invent Insights queries against *
– ❌ Don’t accept remediations without evidence blocks
– ❌ Don’t grant the bot * IAM for “faster incident response”
– ❌ Don’t stream raw request bodies with PII into the LLM
– ❌ Don’t skip structured logging — you cannot ground what you never emitted
Related reading
- Lambda Powertools for Node: Structured Logs That Survive On-Call
- Lambda Timeouts, Retries, and DLQs: Idempotent Failure Handling
- LLM Coding Agents on AWS: Safe Tool Sandboxes with Lambda
- Amazon Bedrock Agents: Tool Use, Memory, and Production Guardrails
Related: AI On-Call Copilots: Suggest Runbooks Without Mutating Production · CloudWatch EMF for LLM Cost: Per-Tenant Token Metrics That Survive Sampling · LLM Context Window Engineering: Fit More Into 200K Tokens and Get Better Answers
Related: AWS CloudWatch Insights Queries That Actually Find Production Bugs · Day 77: Caching RAG Answers Safely · Day 90: Project: Incident Timeline Summarizer
Watch: 100 Days of AI on YouTube
Short videos from CheatCoders, one AI engineering topic per day.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- Css Animation Performance: Performance-Optimized Motion for Modern UIs
Newly added
- AWS Systems Manager Parameter Store: Hierarchical Runtime Config for Coding Agents
- Amazon Cognito Identity Pools: Federate Coding-Agent Tool Callers Without Long-Lived Keys
- Amazon Athena for Coding Agents: Query Tool Audit Logs and Eval Traces on S3 Without a Warehouse
- AWS Batch for Coding Agents: Parallel Overnight PR Reviews and Long-Running Jobs Without Lambda Timeouts
- Amazon Bedrock Prompt Management: Version and A/B Test Coding-Agent System Prompts
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.

Pingback: AI On-Call Copilots: Suggest Runbooks Without Mutating Production - CheatCoders
Pingback: Incident ChatOps AI: Draft Change Tickets Without Auto-Remediating - CheatCoders