
We are going to build a root-cause analysis agent that ingests a vague infrastructure symptom and reasons through competing hypotheses before committing to an answer. It is built for on-call engineers who need structured thinking instead of guesswork at 3 a.m. By forcing the model to externalize its full chain of thought, we turn a black-box completion into an auditable diagnostic process that can be pasted straight into an incident thread. Deep reasoning is not about making the model sound smarter. It is about constraining the output so that conclusions follow from explicit evidence rather than surface pattern matching.
What you'll need
- An Oxlo.ai API key from https://portal.oxlo.ai. The free tier includes 60 requests per day, which is enough to prototype this agent.
- Python 3.10 or newer.
- The OpenAI SDK installed with
pip install openai.
Step 1: Scaffold the Oxlo.ai client
I start every project by pinning the client and the model. For deep reasoning I reach for kimi-k2.6 on Oxlo.ai. It handles advanced chain-of-thought reasoning and maintains multiple hypotheses in working memory without drifting. I also keep qwen-3-32b and deepseek-v3.2 in my back pocket because Oxlo.ai hosts all of them behind the same base URL, so switching is a one-line change. Because Oxlo.ai bills per request rather than per token, I do not have to worry about the prompt length ballooning as I add monitoring context, past incident history, or long log excerpts. That constraint removal is important. On traditional token-based providers, long prompts actively discourage deep reasoning because the meter spins on every character you feed in. Here, the cost is fixed once you hit send, which means you can iterate on the prompt without budget anxiety.
from openai import OpenAI
import os
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key=os.environ.get("OXLO_API_KEY")
)
MODEL = "kimi-k2.6"
Step 2: Design the system prompt for deep reasoning
Deep reasoning is not automatic. You have to architect it. The system prompt is the actual product. It locks the model into a step-by-step protocol, forces it to consider alternatives it might otherwise ignore, and demands raw JSON so we can parse the reasoning chain independently of the final verdict. I keep the instructions imperative and numbered. That structure tends to survive longer context windows better than prose instructions, and it makes the output predictable enough to parse with standard library json. I also explicitly forbid markdown fences. In my experience, if you do not tell the model to skip them, it will wrap the JSON roughly half the time, which breaks automated pipelines.
SYSTEM_PROMPT = """You are a senior site-reliability engineer performing root-cause analysis.
When given a symptom description, follow this exact reasoning protocol:
1. Restate the observed symptom in your own words.
2. List at least three distinct hypotheses that could explain it.
3. For each hypothesis, note what evidence would confirm or refute it.
4. Compare the hypotheses against the evidence implied in the report.
5. State the most likely root cause and a confidence score from 0 to 1.
6. Recommend one immediate mitigation and one long-term fix.
Format your entire response as a JSON object with two keys:
- "reasoning": a list of strings, one per step above
- "final_answer": an object containing "root_cause", "confidence", "mitigation", and "long_term_fix"
Do not output markdown fences. Output raw JSON only."""
Step 3: Send the incident report to the model
Now we wire the user report into the messages array. Oxlo.ai is fully OpenAI SDK compatible, so the only difference from a stock OpenAI script is the base_url pointing to https://api.oxlo.ai/v1. I wrap the call in a small function so I can swap models later without touching the rest of the pipeline. I also strip accidental markdown fences because some chat-tuned models instinctively wrap JSON in triple backticks even when you tell them not to. Defensive parsing saves you a retry during an outage. I use json.loads directly on the stripped string. If the model hallucinates invalid JSON, that exception bubbles up and I can log it for prompt tuning later.
import json
def analyze_symptom(report: str) -> dict:
response = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": report},
],
)
raw = response.choices[0].message.content.strip()
# Defensive parse: drop markdown fences if the model added them
if raw.startswith("```"):
raw = raw.split("\n", 1)[1].rsplit("```", 1)[0].strip()
return json.loads(raw)
Step 4: Inspect the reasoning chain before trusting the verdict
The value of this agent is not the final sentence. It is the trace. If the model skips a hypothesis or cherry-picks evidence, the engineer needs to catch that before acting. We unpack the JSON and print each reasoning step so it can be read aloud during a war-room bridge call. I use dict.get with fallbacks so that a malformed key does not crash the CLI tool when the team is already under pressure. Separating reasoning from final_answer also lets me store the chain in a database for post-mortem review. Auditable reasoning is the difference between a toy demo and infrastructure you actually trust.
def print_analysis(result: dict):
print("=== REASONING CHAIN ===")
for i, step in enumerate(result.get("reasoning", []), 1):
print(f"{i}. {step}")
print("\n=== FINAL VERDICT ===")
ans = result.get("final_answer", {})
print(f"Root Cause: {ans.get('root_cause', 'N/A')}")
print(f"Confidence: {ans.get('confidence', 'N/A')}")
print(f"Mitigation: {ans.get('mitigation', 'N/A')}")
print(f"Long-term Fix: {ans.get('long_term_fix', 'N/A')}")
Step 5: Run the agent against a real incident
Here is a deliberately ambiguous symptom. CPU is normal, the database is only at forty percent, yet users see 502s. A shallow completion might blame the gateway and send someone on a wild goose chase. A deep reasoning model should notice the twelve-second latency spike and suspect an upstream timeout. We call analyze_symptom and print the result. If the reasoning chain is weak, we can tighten the system prompt or switch to deepseek-v3.2, which is also available on Oxlo.ai with the same client and base URL. I often run the same incident against two models and diff the reasoning chains. Because Oxlo.ai does not cold-start popular models, those extra requests come back immediately.
if __name__ == "__main__":
incident = (
"Users in region us-east-1 report intermittent 502s on the checkout API "
"starting at 14:05 UTC. CPU on the gateway pods is normal. "
"Latency spikes to 12s during the errors. Database connections are at 40% capacity. "
"No deployments occurred in the last 6 hours."
)
result = analyze_symptom(incident)
print_analysis(result)
Run it
When I run this script against Oxlo.ai with kimi-k2.6, I get output that looks like the block below. Your exact wording will vary, but the structure should remain intact because the system prompt constrains it. If you see markdown fences, your strip logic caught them. If you see a JSON decode error, the model likely ran out of context or ignored the schema. In that case, shorten the incident report or move to a model with a larger context window. The example below shows the kind of disciplined thinking you want to see in production.
=== REASONING CHAIN ===
1. The symptom is intermittent 502 Bad Gateway responses on the checkout API in us-east-1, starting at 14:05 UTC, with normal gateway CPU but latency spiking to 12 seconds and database connections at 40%.
2. Hypothesis A: An upstream dependency is timing out, causing the gateway to return 502 after exhausting its wait period.
3. Hypothesis B: A network partition or load-balancer health-check failure is dropping packets between the gateway and upstream service.
4. Hypothesis C: A sudden traffic spike is causing back-pressure, but the report does not mention elevated CPU or connection pool saturation, so this is unlikely.
5. Evidence for A: Latency climbing to exactly 12 seconds aligns with common gateway timeout defaults, while CPU and DB remain healthy, pointing to a downstream blocker.
6. Evidence against B: No regional DNS or health-check errors are noted in the report, making a pure network fault less probable.
7. Evidence against C: Without elevated CPU or DB connections, back-pressure is not supported by the data provided.
8. Hypothesis A best explains the observed pattern: the gateway is healthy but waiting on a slow upstream.
9. Most likely root cause is an upstream microservice timeout or degradation.
10. Immediate mitigation is to enable the circuit breaker and temporarily raise the gateway timeout.
11. Long-term fix is to implement distributed tracing and refactor the checkout flow to reduce synchronous dependency chains.
=== FINAL VERDICT ===
Root Cause: Upstream service timeout causing gateway 502s under latent dependency conditions
Confidence: 0.85
Mitigation: Enable circuit breaker and raise gateway timeout to 15s as a temporary band-aid
Long-term Fix: Implement distributed tracing and refactor checkout flow to async events where possible
Wrap-up
This agent gives you an audit trail, not just an answer. Two concrete next steps. First, wire it into a Slack slash command so on-call engineers can paste a PagerDuty description and get a structured RCA in seconds. Second, run an A/B test between qwen-3-32b and deepseek-v3.2 on Oxlo.ai to see which reasoning depth fits your domain. Because Oxlo.ai uses request-based pricing, you can stuff the prompt with full log dumps and stack traces without watching the meter climb on input tokens. That pricing model removes the friction that usually discourages long-context deep reasoning. You can see the exact tiers at https://oxlo.ai/pricing. If you are currently on a token-based provider and your reasoning prompts are growing, the Oxlo.ai Enterprise plan even guarantees thirty percent savings over your current bill.


