🎓 Extend the SRE Agent: Follow-Up Exercises¶
Read BLOGPOST.md and do CODELAB.md before you start. After the
codelab, you have an SRE agent that does these tasks:
- It scans traces.
- It finds the bottleneck span.
- It correlates logs.
- It writes a post-mortem.
📦 Repository: The code is at
github.com/xSAVIKx/sre-agent. Fork the repository and add your changes to the fork.
Each exercise adds one feature to the existing stack. The exercises start with small tasks and end with a production capstone. You can do them in any sequence.
How to Use This Guide¶
Levels:
| Level | Typical time | |
|---|---|---|
| 🟢 | Warm-up: change a tool or a report | 30–60 min |
| 🟡 | Intermediate: change the agent behavior or the workflow graph | 1–3 hours |
| 🔴 | Advanced: new service, RAG, evaluation or production setup | half a day or more |
Obey the five project rules (see AGENTS.md). If you break a rule, the planner of
the agent or the local simulation stops working.
- Each tool must have an
if IS_MOCK:branch. This branch reads frommock_telemetry_data/and does not call the cloud. - Each tool must have full type hints and a clear docstring. The Antigravity SDK makes the LLM tool schema from them.
- Register each tool with
@register_tool(from sre_agent.registry import register_tool). Do not add tools to the config manually. - Keep the Orchestrator deny-by-default. Add an
allow(...)orask_user(...)rule only when you have a reason. - Wrap external calls with
@retry_asyncand@otel_tracefromsre_common.
Verify your work after each exercise:
-
Run the end-to-end simulation:
uv run simulate_incident.py -
Run the three test suites. The last suite includes the workspace import test:
PYTHONPATH=sre_agent/src uv run python -m unittest discover -s sre_agent/test PYTHONPATH=agent/src uv run python -m unittest discover -s agent/test uv run python -m unittest discover -s test -
Run the linter and the format check:
uv run ruff check . && uv run ruff format --check .
CI runs these checks. CI runs the tests on Python 3.11 and on Python 3.14, and ruff one time. If
the checks pass on your computer, CI usually passes too.
Where to Add Your Code¶
flowchart TB
Q["query_traces<br/>🧩 Ex. 8 and Capstone: notify, auto-trigger"] --> TA["🕵️ TraceAnalyzer"]
TA --> FT["fetch_telemetry (topology enrichment)<br/>🧩 Ex. 5–6: new ADK node, multi-trace<br/>🧩 Ex. 9: RAG runbooks"]
FT --> LC["🩺 LogCorrelator + tools<br/>🧩 Ex. 1–3: new tools<br/>🧩 Ex. 7: human-in-the-loop write tool"]
LC --> PM["analyze_trace_cascade<br/>generate_post_mortem<br/>🧩 Ex. 1–3: better report<br/>🧩 Ex. 4: severity in the post-mortem"]
PM --> A2["A2UI surfaces (a2ui_surfaces.py)"] --> UI["💬 web chat + 📥"]
🟢 Level 1: Warm-Ups (Tools and Reports)¶
Exercise 1 · Make the metrics tell the truth 🟢¶
Goal. Without a key, the scripted model (SimulatedLlm) writes the Observability Metrics
section. It shows only the latest value of each time series: 0.24 and 100. A person must still
decide if a value is a problem. Make the model say it: "the connection pool is full", "the CPU is
idle".
Start here. Go to _metric_line in
simulated_llm.py.
- Compare the latest DB connection count with a pool size that you can configure.
- Compare the CPU fraction with a threshold.
Done when.
- With the healthy readings, the report shows a healthy pool. The healthy readings are the
trigger_error=Falseseries of_mock_metric_seriesinapp/main.py. - With the incident readings, the report shows saturation.
- One unit test covers each case.
Note: simulate_incident.py always makes an incident. A healthy trace gives no incident, so test
the healthy case with a unit test.
Exercise 2 · Add a log-pattern clustering tool 🟢¶
Goal. One error message is easy to read. Many errors with small differences are a better signal.
Add a tool summarize_log_patterns(trace_id | query). The tool does these steps:
- It normalizes each correlated log message: it removes IDs and timestamps.
- It groups the logs by the normalized message.
- It returns the most frequent patterns with their counts.
Start here.
- Add the tool to
sre_agent/src/sre_agent/gcp_tools.py, nearquery_logs_by_trace. - Register the tool with
@register_tool. - Add an
IS_MOCKbranch. - Add the tool to the
tools=[...]list oflog_correlatorinsre_workflow.py.
Done when. A unit test in sre_agent/test/ shows that the tool groups a set of mock logs. The
agent can call the tool during a diagnosis.
Exercise 3 · Add an SLO / error-budget section to the post-mortem 🟢¶
Goal. Use SRE terms in the post-mortem. Use the total duration of the trace and a latency SLO that you can configure (for example, 1000 ms). Calculate how much the request went over the budget. Add an "SLO Impact" section with the result.
Start here. Go to generate_post_mortem. Do not change
the heading # 🚨 Incident Post-Mortem. The post-mortem surface in
a2ui_surfaces.py and its tests use this heading.
Done when. The post-mortem has a line that gives the SLO breach as a number. For example: "10270 ms vs. 1000 ms SLO → 927% over budget". The existing tests pass.
Exercise 4 · Carry severity into the post-mortem 🟢¶
Goal. Step 5 of the workshop shows a SEV1, SEV2 or SEV3 badge in the chat. But the post-mortem
file from the Download button (post-mortem-<trace ID prefix>.md) does not show the severity.
Add the severity to the document.
Start here. Go to classify_severity in
a2ui_surfaces.py and to generate_post_mortem in
gcp_tools.py. Do not change the heading
# 🚨 Incident Post-Mortem. The post-mortem surface uses it.
Done when. The Incident Overview section of the downloaded post-mortem has a Severity
line. A test covers this line.
🟡 Level 2: Better Diagnosis (Workflow and Agents)¶
Exercise 5 · Add a third ADK node: the Mitigation Planner 🟡¶
Goal. Now the graph is TraceAnalyzer → fetch_telemetry → LogCorrelator. Add a
MitigationPlanner agent. This agent gets the root cause. It writes a ranked list of remediation
actions, with rollback steps. Thus, the diagnosis and the recommendation are separate.
Start here. Go to sre_workflow.py.
- Define a third
AdkAgent. - Add the new agent to the edges of the
AdkWorkflow. - Teach the scripted model (
simulated_llm.py) to answer the new agent's request, so that the workflow also runs without a key.
Done when. The report has a separate "Mitigation Plan" section, with Gemini and with the
scripted model. Test both with simulate_incident.py: one time with GEMINI_API_KEY and one time
without it.
Exercise 6 · Multi-trace incident correlation 🟡¶
Goal. A real incident usually affects many requests. Find N recent traces with the same failure signature (the same failing span and error). Report them as one incident, with the number of affected traces (the blast radius).
Start here.
- Write a new tool that reads the output of
query_traces. - In the tool, group the traces by failing span and error class.
- Send the summary to the
fetch_telemetrynode insre_workflow.py.
Done when. Put several error traces in mock_telemetry_data/. Run simulate_incident.py with
--keep-data, so that the script keeps the traces of earlier runs. The report contains a sentence
like "12 traces affected by the same /api/database timeout in the last 2h."
Exercise 7 · A human-in-the-loop remediation tool (safety!) 🟡¶
Goal. Let the agent act, but safely. Add a mock tool restart_service(service_name). The
tool runs only after explicit human approval. Use the Antigravity ask_user policy, not a
plain deny or allow rule.
Start here. Go to agent/src/agent/config.py. The file already
imports ask_user.
- Add the tool.
- In
build_safety_policies(), add the ruleask_user("restart_service", handler=...). - Do not remove
deny("*"). - Write the handler. The handler receives the pending
ToolCalland returnsTrueto approve the call. The real SDK refuses anask_userpolicy that has nohandler. - Start with a handler that logs the request and returns
False. - Then connect the handler to the chat UI.
Done when. The tool cannot run without approval. In a comment or a PR note, explain why
deny-by-default with ask_user is safer than allow. This exercise is the most important one to
understand the safety model of the project.
Exercise 8 · Notify a channel (Slack / webhook) 🟡¶
Goal. Tell the humans about the result. When the agent writes a post-mortem, send it with a
POST request to a webhook. In local mock mode, do not send it.
Start here.
- Write a new tool that uses
httpx. Wrap it in@retry_async. - In
IS_MOCKmode, write the payload to a local file. Do not make a network call. - For the error handling, look at
_call_sre_skillinconfig.py. It catches the error and returns a result that starts withError:.
Done when. A local mock run writes the notification payload to disk. The environment variable
WEBHOOK_URL enables real posts. Optional: put the tool behind an ask_user rule, as in
Exercise 7.
🔴 Level 3: Close the Loop (Architecture and Production)¶
Exercise 9 · RAG over runbooks 🔴¶
Goal. The SRE agent already finds diagnostic templates with a similarity search. It searches for each resource that the Inventory agent discovers. Extend this search to retrieval-augmented diagnosis:
- Store the runbooks of your organization.
- Find the runbook that matches the failing service best.
- Add this runbook to the context of the LogCorrelator.
Start here. Go to sre_agent/src/sre_agent/itinerary.py
(DEFAULT_TEMPLATES, get_embedding, find_matching_template). Also go to the enrichment step in
fetch_telemetry (sre_workflow.py). In mock mode,
get_embedding returns a zero vector. Decide how to get useful matches offline. For example, use a
keyword match when IS_MOCK is true.
Done when. A diagnosis for sre-chaos-monkey includes a part of the matching runbook. The
workflow still runs without an API key.
Exercise 10 · Go live on real GCP 🔴¶
Goal. Stop the mocks. Deploy to Cloud Run. Diagnose a real incident with real data from Cloud Trace, Cloud Logging and Cloud Monitoring.
Start here.
- Run
./bootstrap.sh. - Run
./deploy.sh. For the least-privilege service accounts, seeREADME.md. - Trigger an incident:
curl ".../api/gateway?trigger_error=true". - Ask the agent in the
/chatUI.
Done when. With MOCK_GCP=false, the agent diagnoses a live trace from start to end. Then run
./cleanup.sh to stop the costs. Look at the IAM split: the app service account can only write
telemetry. The agent service account can read telemetry, and it can write only its own spans.
Exercise 11 · Build an evaluation harness 🔴¶
Goal. Find out if a prompt change or a model change makes the agent better. Make a small evaluation set of labeled incidents. Each incident has trace fixtures and the expected root cause or bottleneck span. Score the output of the agent automatically.
Start here.
- Add the fixtures to
mock_telemetry_data/. - Write an evaluation runner that calls
run_sre_diagnostics. - In the runner, compare the bottleneck span and the error class with the label.
This method is the same as the ADK evaluation method. Optional: add an "LLM-as-judge" scorer.
Done when. A script (or a make eval target that you add) shows the pass rate over your
incident set. You can compare two agent instructions (A/B).
🏆 Capstone · The fully autonomous loop¶
Goal. Fix the incident before a human logs on. Connect these parts:
- A Cloud Monitoring alert sends a message to Pub/Sub.
- Pub/Sub calls an entry point that runs the diagnosis automatically.
- The entry point sends the post-mortem to your channel (Exercise 8).
- Each remediation needs approval through
ask_user(Exercise 7).
Start here.
- Add a Pub/Sub push endpoint to the Orchestrator
(
agent/src/agent/routes.py). The endpoint calls the samediagnose_sretool as the chat. The existing/diagnoseendpoint is an example. - Create the alert and the topic in
deploy.sh.
Done when. An incident produces a post-mortem in your channel without a human. A human is necessary only when a remediation needs approval. Record a short demo. 🎬
✅ Definition of Done (for all exercises)¶
- [ ] Each new tool has an
IS_MOCKbranch, full type hints and a clear docstring. - [ ]
uv run simulate_incident.pystill writes a complete report. If you changed the workflow, test both tiers. - [ ] All three test suites pass.
ruff checkandruff format --checkshow no errors. - [ ] The Orchestrator is still deny-by-default. Each new capability has its own
allow(...)orask_user(...)rule. - [ ] You can explain why your change is safe.
If you have a problem, read the related section of CODELAB.md again. Also read the
design decisions in BLOGPOST.md and the rules in AGENTS.md.