<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Harjapan Singh</title>
    <description>The latest articles on DEV Community by Harjapan Singh (@harjapan_2005).</description>
    <link>https://dev.to/harjapan_2005</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4047118%2F62523031-abbc-4b51-8499-03cad086d582.jpg</url>
      <title>DEV Community: Harjapan Singh</title>
      <link>https://dev.to/harjapan_2005</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/harjapan_2005"/>
    <language>en</language>
    <item>
      <title>AI SRE Agents on SigNoz MCP: Root-Cause Analysis for $0.0013</title>
      <dc:creator>Harjapan Singh</dc:creator>
      <pubDate>Sat, 25 Jul 2026 21:25:59 +0000</pubDate>
      <link>https://dev.to/harjapan_2005/ai-sre-agents-on-signoz-mcp-root-cause-analysis-for-00013-4ne2</link>
      <guid>https://dev.to/harjapan_2005/ai-sre-agents-on-signoz-mcp-root-cause-analysis-for-00013-4ne2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;🏆 Built for the &lt;strong&gt;WeMakeDevs × Agents of SigNoz Hackathon 2026&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;· &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/HARJAPAN2005/MIB-Men-in-Backend" rel="noopener noreferrer"&gt;github.com/HARJAPAN2005/MIB-Men-in-Backend&lt;/a&gt; &lt;br&gt;
· &lt;strong&gt;Demo:&lt;/strong&gt; &lt;a href="https://youtu.be/sMqGD6IibTI" rel="noopener noreferrer"&gt;https://youtu.be/sMqGD6IibTI&lt;/a&gt;&lt;br&gt;
· &lt;strong&gt;Hackathon:&lt;/strong&gt; &lt;a href="https://www.wemakedevs.org/hackathons/signoz" rel="noopener noreferrer"&gt;wemakedevs.org/hackathons/signoz&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;How we built a reactive + proactive AI SRE platform on top of SigNoz MCP — and what we learned when 38% of our own top metrics were costing money for nothing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;⏱️ &lt;strong&gt;12 min read&lt;/strong&gt; · 🔧 Python · FastAPI · SigNoz MCP · OpenTelemetry · Foundry&lt;/p&gt;




&lt;h2&gt;
  
  
  Project at a Glance
&lt;/h2&gt;

&lt;p&gt;We built &lt;strong&gt;MIB (Men in Backend)&lt;/strong&gt; — two autonomous AI SRE agents running on top of the SigNoz MCP Server.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Agent J&lt;/th&gt;
&lt;th&gt;Agent K&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Role&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Incident responder&lt;/td&gt;
&lt;td&gt;Observability auditor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trigger&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Alert webhook&lt;/td&gt;
&lt;td&gt;Scheduled / on-demand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Slack root-cause card&lt;/td&gt;
&lt;td&gt;Slack precinct report + Approve button&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.0013 per investigation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Key finding&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full RCA in &lt;strong&gt;3.7 seconds&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;38% of top metrics&lt;/strong&gt; never read by anyone&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both agents instrument themselves with OpenTelemetry GenAI semantic convention spans, emitting into the &lt;em&gt;same&lt;/em&gt; SigNoz — so SigNoz ends up watching the agents that watch SigNoz.&lt;/p&gt;




&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The Problem with "Chat With Your Traces"&lt;/li&gt;
&lt;li&gt;The Idea: An Autonomous Observability Workforce&lt;/li&gt;
&lt;li&gt;Architecture&lt;/li&gt;
&lt;li&gt;
Building It: The Parts That Mattered

&lt;ul&gt;
&lt;li&gt;Agent J's Playbook: Why Determinism Wins&lt;/li&gt;
&lt;li&gt;The Double-Decode Problem&lt;/li&gt;
&lt;li&gt;The Host-Rewrite Problem&lt;/li&gt;
&lt;li&gt;Foundry Patches: Deliberate Molding&lt;/li&gt;
&lt;li&gt;The GenAI Semantic Convention Spans&lt;/li&gt;
&lt;li&gt;Agent K: The Auditor Who Never Auto-Applies&lt;/li&gt;
&lt;li&gt;The Predictive Engine&lt;/li&gt;
&lt;li&gt;The Confidence Engine&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SigNoz Integration: What We Actually Used&lt;/li&gt;
&lt;li&gt;The Mistakes We Made&lt;/li&gt;
&lt;li&gt;Lessons Learned&lt;/li&gt;
&lt;li&gt;The Part We Didn't Expect&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Problem with "Chat With Your Traces"
&lt;/h2&gt;

&lt;p&gt;The most common AI observability demo goes like this: connect an LLM to your telemetry backend, ask it a question, get an answer. &lt;em&gt;"What's the error rate on checkout?" "Which service is slowest?"&lt;/em&gt; It's impressive the first time. By the fifth time, a question forms:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why does a tool that can see everything still need me to ask?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The deeper problem is that observability has always been reactive. Dashboards show you what already happened. Alerts fire after a threshold is crossed. PagerDuty wakes someone up who then manually traces from metric → trace → log, building an evidence chain at 2 AM that any deterministic program could assemble automatically.&lt;/p&gt;

&lt;p&gt;A chatbot doesn't fix this. A chatbot is still reactive — it just answers questions faster. What we wanted was something different: agents that &lt;em&gt;act&lt;/em&gt; without being asked, that &lt;em&gt;audit&lt;/em&gt; things you forgot to look at, that observe themselves so you can measure their cost, and that flag problems &lt;em&gt;before&lt;/em&gt; the alert fires.&lt;/p&gt;

&lt;p&gt;That's MIB.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Idea: An Autonomous Observability Workforce
&lt;/h2&gt;

&lt;p&gt;The mental model that unlocked the design was simple: &lt;strong&gt;treat your observability platform as a workplace, not a database.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;SigNoz holds everything — traces, logs, metrics, dashboards, alerts — and the SigNoz MCP server exposes all of it through a structured tool interface. So instead of building a chatbot that queries it, we built &lt;em&gt;employees&lt;/em&gt; who use those tools on a fixed schedule, with defined job descriptions, human-approval gates on anything destructive, and expense reports (token cost per investigation).&lt;/p&gt;

&lt;p&gt;Two agents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent J&lt;/strong&gt; &lt;em&gt;(the rookie who gets paged)&lt;/em&gt;: alert fires → J runs a fixed 5-step MCP playbook → posts a root-cause card to Slack with one-click links back to the exact trace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent K&lt;/strong&gt; &lt;em&gt;(the veteran who inspects the precinct)&lt;/em&gt;: scheduled audits of the observability stack itself — which metrics cost money but nobody reads, which alerts fire into a dead channel — then proposes one fix, approval-gated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the twist: both agents instrument themselves with OpenTelemetry GenAI semantic convention spans, emitting into the &lt;em&gt;same&lt;/em&gt; SigNoz. You end up with SigNoz watching the agents that watch SigNoz. We called this layer the &lt;strong&gt;Neuralyzer&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;demo-lite app ──OTLP──► SigNoz (Foundry: docker/compose)
                            │ alert webhook             ▲
                            ▼                           │ OTel (GenAI spans + metrics)
                    Agency HQ (FastAPI :8100)           │
                    ├── Agent J (5-step MCP playbook) ──► SigNoz MCP :8000
                    ├── Agent K (audit checklist)     ──►
                    └── Mission Control++ (predictive, replay, debate, postmortem)
                            │
                            ▼
                    Slack: incident cards · precinct reports · Approve buttons
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three observability layers in the same SigNoz instance:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;What SigNoz sees&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1 — Victim app&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;demo-lite, 14 microservices&lt;/td&gt;
&lt;td&gt;OTLP traces, logs, metrics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2 — The agents&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FastAPI + manual GenAI spans&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;agent_j.investigation.*&lt;/code&gt; spans, cost metrics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3 — MCP server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;signoz-mcp itself&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mcp.tool.calls&lt;/code&gt;, &lt;code&gt;mcp.tool.call.duration.*&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0zz9z6ql9xe2r9asd8sm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0zz9z6ql9xe2r9asd8sm.png" alt="The central command center of MIB, displaying real-time AI investigations, active incidents, predictive alerts, and the operational status of Agent J and Agent K. Engineers can monitor ongoing investigations, review timelines, and access AI-generated insights from a single interface. " width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mission Control++: live agent activity, predictive incident warning, and evidence graph.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Components
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;SigNoz + Foundry&lt;/strong&gt; — We deploy SigNoz via &lt;a href="https://github.com/SigNoz/foundry" rel="noopener noreferrer"&gt;Foundry&lt;/a&gt;, SigNoz's own declarative installer. The &lt;code&gt;casting.yaml&lt;/code&gt; file is 47 lines and contains three deliberate RFC 6902 JSON patches on the generated compose file — more on those in a moment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SigNoz MCP&lt;/strong&gt; — The MCP server exposes 41 tools covering every layer of SigNoz: traces, logs, metrics, dashboards, alerts, and notification channels. The agents use it as their only interface to the platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agency HQ&lt;/strong&gt; — A FastAPI application that receives alert webhooks from SigNoz's Alertmanager, dispatches investigations to Agent J as background asyncio tasks (so the webhook returns fast), and runs Agent K on demand. It also hosts the Mission Control++ web UI and the SSE event stream.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mission Control++&lt;/strong&gt; — A separate module (&lt;code&gt;mission_control_plus.py&lt;/code&gt;, ~655 lines) implementing a predictive incident engine, a replay system, a confidence engine with delta attribution, an AI debate layer, evidence graph generation, and auto-postmortem generation in Markdown and PDF — no external dependencies beyond the standard library and httpx.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building It: The Parts That Mattered
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Agent J's Playbook: Why Determinism Wins
&lt;/h3&gt;

&lt;p&gt;The hardest design decision was also the first: should Agent J be a true agentic loop, choosing its own tools, or a fixed deterministic playbook?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We chose fixed.&lt;/strong&gt; Here's the reasoning:&lt;/p&gt;

&lt;p&gt;An agentic loop that picks tools based on intermediate results produces different investigation paths for the same incident. That's interesting academically and catastrophic in a demo. More importantly, an SRE investigating a service degradation doesn't have infinite investigative freedom — there's a canonical order of operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Confirm the alert fired&lt;/li&gt;
&lt;li&gt;Find the bad traces&lt;/li&gt;
&lt;li&gt;Drill into the worst trace&lt;/li&gt;
&lt;li&gt;Correlate the logs&lt;/li&gt;
&lt;li&gt;Measure the metric delta&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every good SRE does this. We encoded it.&lt;/p&gt;

&lt;p&gt;The result is &lt;code&gt;run_playbook()&lt;/code&gt; in &lt;code&gt;agent_j.py&lt;/code&gt; — 150 lines, five sequential MCP calls. The LLM comes in exactly once, for synthesis only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;litellm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acompletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;AGENT_J_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SYS_PROMPT&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;response_format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json_object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;reasoning_effort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# gemini-2.5-flash is a thinking model; disable it so
&lt;/span&gt;                                 &lt;span class="c1"&gt;# the JSON never truncates and latency stays ~2s not ~16s
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;reasoning_effort="disable"&lt;/code&gt; line came from a real bug. On the first fully-instrumented run, the LLM call took &lt;strong&gt;16 seconds&lt;/strong&gt; and returned truncated JSON. The cause: gemini-2.5-flash is a thinking model; with &lt;code&gt;max_tokens=1600&lt;/code&gt; and thinking enabled, it was burning most of the budget on chain-of-thought reasoning before outputting any JSON, then truncating. Setting &lt;code&gt;reasoning_effort="disable"&lt;/code&gt; cut latency to &lt;strong&gt;~2s&lt;/strong&gt; and the parse failure rate to zero.&lt;/p&gt;

&lt;p&gt;Every MCP call also passes a &lt;code&gt;searchContext&lt;/code&gt; argument — this isn't decoration. The MCP server uses it to tag its own telemetry, which is how the layer-3 panel on our surveillance dashboard works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;s2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;mcp_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signoz_search_traces&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;service&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;start_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;end_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;searchContext&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; (step 2: error traces)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mcp.tool.calls&lt;/code&gt; by &lt;code&gt;gen_ai.tool.name&lt;/code&gt; shows a per-tool breakdown, and &lt;code&gt;searchContext&lt;/code&gt; gives each call a human-readable label in the trace.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzfur8pekxp4sfsfnr7jq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzfur8pekxp4sfsfnr7jq.png" alt="Agent J's incident card — root cause, evidence chain, and one-click trace link, generated in ~3.7 seconds." width="798" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Agent J's incident card — root cause, evidence chain, and one-click trace link, generated in ~3.7 seconds.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  The Double-Decode Problem
&lt;/h3&gt;

&lt;p&gt;MCP tool results are not what you'd expect. The response envelope from &lt;code&gt;tools/call&lt;/code&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {...}}"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual payload is a JSON string &lt;em&gt;inside&lt;/em&gt; &lt;code&gt;result.content[0].text&lt;/code&gt; — every MCP call needs double-decoding. Some tools also append a &lt;code&gt;[Decisions applied]&lt;/code&gt; human-readable block after the JSON, which breaks a naive &lt;code&gt;json.loads()&lt;/code&gt;. Our &lt;code&gt;_loads_lenient()&lt;/code&gt; handles both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_loads_lenient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# extract the first balanced {...} — trims the trailing [Decisions applied] block
&lt;/span&gt;        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rfind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;pass&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  The Host-Rewrite Problem
&lt;/h3&gt;

&lt;p&gt;Every &lt;code&gt;webUrl&lt;/code&gt; returned by MCP tools is built from the MCP server's own &lt;code&gt;SIGNOZ_URL&lt;/code&gt; env variable, which defaults to &lt;code&gt;http://signoz-signoz-0:8080&lt;/code&gt; — the internal Docker service name. Not browser-clickable. We run a &lt;code&gt;deep_rewrite()&lt;/code&gt; pass on every MCP result before using any URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;INTERNAL_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signoz-signoz-0:8080&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;BROWSER_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SIGNOZ_BROWSER_HOST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost:8080&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deep_rewrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;deep_rewrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;deep_rewrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;rewrite_host&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The alert webhook's &lt;code&gt;externalURL&lt;/code&gt; field &lt;em&gt;is&lt;/em&gt; already browser-clickable (it comes from Alertmanager, not the MCP server), so we use it as-is for the alert rule deep link in the Slack card.&lt;/p&gt;




&lt;h3&gt;
  
  
  Foundry Patches: Deliberate Molding
&lt;/h3&gt;

&lt;p&gt;One of Foundry's strengths is the ability to apply RFC 6902 JSON patches to the generated compose file without touching it directly. We used three:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Patch 1 — Healthcheck fix&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;signoz-mcp&lt;/code&gt; image is distroless (no shell, no wget, no curl). The default healthcheck is &lt;code&gt;wget --spider /livez&lt;/code&gt;. This can never run — the container reported as &lt;code&gt;unhealthy&lt;/code&gt; while actually serving traffic. We patched it out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;replace&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/services/signoz-mcp/healthcheck&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NONE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Patch 2 — MCP self-telemetry (the third observability layer)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The MCP server logs "OpenTelemetry export not configured" by default. Two environment variables flip it on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;add&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/services/signoz-mcp/environment/-&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OTEL_EXPORTER_OTLP_ENDPOINT=http://signoz-ingester-1:4317&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;add&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/services/signoz-mcp/environment/-&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OTEL_SERVICE_NAME=signoz-mcp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this patch, &lt;code&gt;mcp.tool.calls&lt;/code&gt; and &lt;code&gt;mcp.tool.call.duration.*&lt;/code&gt; metrics start appearing in SigNoz, tagged with &lt;code&gt;gen_ai.tool.name&lt;/code&gt;. &lt;strong&gt;Four lines to add a complete third observability layer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Patch 3 — Alert latency&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Alertmanager's default &lt;code&gt;group_wait&lt;/code&gt; is 30 seconds. With a 30s evaluation window on the ruler, worst-case fault-to-Slack-card is ~3.5 minutes. We patched it to 5 seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;op&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;add&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/services/signoz-alertmanager/environment/-&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ALERTMANAGER_GROUP_WAIT=5s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was the difference between a demo where you wait three minutes and one where the card lands before the coffee is poured.&lt;/p&gt;




&lt;h3&gt;
  
  
  The GenAI Semantic Convention Spans
&lt;/h3&gt;

&lt;p&gt;We chose manual instrumentation over OpenLLMetry's auto-instrument for one reason: &lt;strong&gt;determinism&lt;/strong&gt;. With auto-instrument, the exact attributes depend on the library version. With manual spans, we know exactly what lands in SigNoz:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_as_current_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent_j.synthesize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get_gen_ai_system_provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AGENT_J_MODEL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.operation.name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.request.model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AGENT_J_MODEL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.response.model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp_model&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.usage.input_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in_tok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.usage.output_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out_tok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.usage.cost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# computed, not from the API
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;gen_ai.usage.cost&lt;/code&gt; isn't returned by the API — we compute it from a pricing table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;GENAI_PRICING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini/gemini-2.5-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;# USD per 1M tokens (input, output)
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.60&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anthropic/claude-haiku-4-5-20251001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;5.00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cost panel on the Agency Surveillance dashboard is &lt;code&gt;sum(gen_ai.usage.cost) GROUP BY investigation.id&lt;/code&gt; in SigNoz Query Builder v5.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Real run against the redis fault:&lt;/strong&gt; 1,733 input + 305 output tokens with gemini-2.5-flash = &lt;strong&gt;$0.0012824&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;investigation.id&lt;/code&gt; attribute (carried as a Python contextvar so it flows to every child span automatically) lets you group a whole investigation in one query — the root span, all five MCP call spans, and the synthesis span share the same fingerprint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6j9aqsixbxo9tcqjycim.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6j9aqsixbxo9tcqjycim.png" alt="A complete Agent J investigation as a SigNoz trace. Five MCP tool call spans, one synthesis span,  raw `gen_ai.usage.cost = 0.00128` endraw ." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A complete Agent J investigation as a SigNoz trace. Five MCP tool call spans, one synthesis span, &lt;code&gt;gen_ai.usage.cost = 0.00128&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Agent K: The Auditor Who Never Auto-Applies
&lt;/h3&gt;

&lt;p&gt;Agent K runs a four-step audit checklist via MCP:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step A — Top metrics by ingestion volume&lt;/strong&gt; (&lt;code&gt;signoz_get_top_metrics&lt;/code&gt;)&lt;br&gt;
Finds your cost hogs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step B — Cardinality check&lt;/strong&gt; (&lt;code&gt;signoz_check_metric_cardinality&lt;/code&gt;)&lt;br&gt;
On the top-cost metric, checks how many distinct label value combinations exist. High cardinality = exponential time-series = exponential cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step C — Unused metrics&lt;/strong&gt; (&lt;code&gt;signoz_check_metric_usage&lt;/code&gt;)&lt;br&gt;
Checks whether any dashboard or alert references each top-cost metric.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;On our stack: 15 of the top 39 metrics were referenced by zero dashboards and zero alerts. That's ~38.5% of top-metric ingestion going nowhere.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step D — Orphan alerts&lt;/strong&gt; (&lt;code&gt;signoz_list_alert_rules&lt;/code&gt; + &lt;code&gt;signoz_list_notification_channels&lt;/code&gt;)&lt;br&gt;
Finds alert rules whose notification channels don't exist.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;check_metric_usage&lt;/code&gt; result has a parsing gotcha: the metric→usage map is at the &lt;strong&gt;top level&lt;/strong&gt; of the JSON, not under &lt;code&gt;.data&lt;/code&gt;. We learned this from a failed read-back that confidently reported "no unused metrics":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# WRONG:  umap = usage["data"]  ← this is None for this tool
# CORRECT:
&lt;/span&gt;&lt;span class="n"&gt;umap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt;
&lt;span class="n"&gt;umap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;umap&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dashboards&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alerts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The single proposed fix is &lt;strong&gt;approval-gated&lt;/strong&gt;. Agent K never auto-applies anything. The Slack card has an Approve button linking to &lt;code&gt;GET /agent-k/approve/{fix_id}&lt;/code&gt;. When clicked, K calls &lt;code&gt;signoz_create_dashboard&lt;/code&gt; (an MCP write tool), then calls &lt;code&gt;signoz_list_dashboards&lt;/code&gt; and confirms the title appears. If it does, the fix is confirmed.&lt;/p&gt;

&lt;p&gt;One detail worth sharing: the pending fix is persisted to disk (&lt;code&gt;/tmp/pending_fixes.json&lt;/code&gt;) so that if Agency HQ restarts between audit and approval, the button still works. Without this, every receiver restart invalidated all outstanding Approve buttons — which we discovered during a demo rehearsal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqc25a6ryyvkpjx6tuqzy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqc25a6ryyvkpjx6tuqzy.png" alt="Agent K's Precinct Report — 15 top-cost metrics with zero references. One click to fix." width="788" height="664"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Agent K's Precinct Report — 15 top-cost metrics with zero references. One click to fix.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  The Predictive Engine
&lt;/h3&gt;

&lt;p&gt;The Mission Control++ predictive engine runs in a background asyncio loop every 25 seconds. For each watched service it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetches the last 12 minutes of traces and ERROR logs via MCP&lt;/li&gt;
&lt;li&gt;Computes a snapshot: p95 latency, error rate, retry count, queue depth signal, throughput&lt;/li&gt;
&lt;li&gt;Scores the trend:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Points&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;p95 latency&lt;/td&gt;
&lt;td&gt;Increasing &amp;gt;20%&lt;/td&gt;
&lt;td&gt;+24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retry count&lt;/td&gt;
&lt;td&gt;&amp;gt;30% increase or &amp;gt;8 retries&lt;/td&gt;
&lt;td&gt;+18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error rate&lt;/td&gt;
&lt;td&gt;Rising &amp;gt;25% or &amp;gt;8%&lt;/td&gt;
&lt;td&gt;+19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput&lt;/td&gt;
&lt;td&gt;Dropping &amp;gt;18%&lt;/td&gt;
&lt;td&gt;+12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Historical similarity&lt;/td&gt;
&lt;td&gt;Match found&lt;/td&gt;
&lt;td&gt;+6 to +22&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;If total score ≥ 55 → emit prediction: &lt;code&gt;confidence = min(97, max(66, score))&lt;/code&gt;, &lt;code&gt;eta = max(3, 24 - confidence/5)&lt;/code&gt; minutes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When a real incident is investigated and its prediction was already open, the prediction transitions from &lt;code&gt;open&lt;/code&gt; to &lt;code&gt;realized&lt;/code&gt;. No ML model — just trend scoring against live telemetry, every 25 seconds.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Confidence Engine
&lt;/h3&gt;

&lt;p&gt;After every investigation, the confidence engine runs five scoring dimensions with additive deltas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;  &lt;span class="c1"&gt;# base
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;traces&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;   &lt;span class="c1"&gt;# "Trace matched"
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;logs&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;   &lt;span class="c1"&gt;# "Metric correlation"
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;delta_x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;   &lt;span class="c1"&gt;# "Retry spike"
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;prediction&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;prediction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;historical_similarity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;   &lt;span class="c1"&gt;# "Historical similarity"
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_fallback&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;   &lt;span class="c1"&gt;# "Fallback verdict penalty"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each delta has a label that appears in the Mission Control UI and in the auto-generated postmortem. The result is a confidence score with an audit trail — you can see &lt;em&gt;why&lt;/em&gt; it's 76%, not just that it is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1f9cpm6g2p4fjyx2qhgq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1f9cpm6g2p4fjyx2qhgq.png" alt="Auto-generated postmortem, produced seconds after the investigation completes. Downloadable as Markdown or PDF." width="607" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Auto-generated postmortem, produced seconds after the investigation completes. Downloadable as Markdown or PDF.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp3pffbkz78s2trgqijxg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp3pffbkz78s2trgqijxg.png" alt="Investigation replay in Mission Control++ — each step shows the tool called, timestamp, and evidence count." width="799" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Investigation replay in Mission Control++ — each step shows the tool called, timestamp, and evidence count.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  SigNoz Integration: What We Actually Used
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;How MIB Uses It&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Traces&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Error trace search; worst-trace drill-down; predictive snapshot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Logs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Service ERROR logs (root cause messages) + trace-correlated logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Metrics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pre/in-window call-volume delta; predictive snapshots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dashboards (read)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agent K read-back to confirm fix applied&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dashboards (write)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agent K fix via &lt;code&gt;signoz_create_dashboard&lt;/code&gt; — an MCP write tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Alerts (read)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agent K step D: list rules, check channels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Alerts (create)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;setup_alerts.sh&lt;/code&gt; creates both alert rules via MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Notification channels&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Created via MCP; Agent K checks for orphans&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Alert webhooks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Alertmanager v4 payload → Agency HQ &lt;code&gt;/webhook/alert&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deep links&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;webUrl&lt;/code&gt; from traces/alerts → Slack card one-click navigation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OTel traces (self)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agency HQ auto-instrumented; manual GenAI semconv spans&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OTel metrics (self)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mib.investigations.total&lt;/code&gt;, &lt;code&gt;mib.investigation.cost&lt;/code&gt;, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP self-telemetry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mcp.tool.calls&lt;/code&gt; by &lt;code&gt;gen_ai.tool.name&lt;/code&gt; — third observability layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Query Builder v5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6-panel Agency Surveillance dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Foundry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full deployment + 3 RFC 6902 patches&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9fk6jy2agomnk4ebixzn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9fk6jy2agomnk4ebixzn.png" alt="Agency Surveillance dashboard — cost per investigation, token usage, latency p95, and MCP server self-telemetry (the third observability layer)." width="799" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Agency Surveillance dashboard — cost per investigation, token usage, latency p95, and MCP server self-telemetry (the third observability layer).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One Query Builder v5 gotcha worth calling out: aggregation queries cannot &lt;code&gt;orderBy&lt;/code&gt; the raw metric name. If you try &lt;code&gt;orderBy: mcp.tool.calls&lt;/code&gt; on a &lt;code&gt;sum(mcp.tool.calls)&lt;/code&gt; query, SigNoz rejects it with &lt;code&gt;"invalid order by key for an aggregation query."&lt;/code&gt; You must &lt;code&gt;orderBy&lt;/code&gt; the aggregation expression itself, or a &lt;code&gt;groupBy&lt;/code&gt; key. We fixed this by ordering on &lt;code&gt;gen_ai.tool.name&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mistakes We Made
&lt;/h2&gt;

&lt;p&gt;Real projects have real failure modes. Here are the six that cost us the most time:&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;🔴 Mistake 1: Docker Desktop on Windows&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ClickHouse Keeper segfaults (exit 139) under Docker Desktop because of its virtualization layer. The fix is native Docker Engine inside a real WSL2 distro.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Lost time: 45 minutes at the start of the hackathon.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;🔴 Mistake 2: WSL Idle Teardown&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After fixing Docker, the entire SigNoz stack started restarting every 2–3 minutes. Diagnosis: no &lt;code&gt;wsl.exe&lt;/code&gt; session was held open, so WSL was idling and tearing down the distro — stopping Docker, stopping all containers.&lt;/p&gt;

&lt;p&gt;The fix: a Windows Scheduled Task that runs at logon to keep the distro pinned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wsl &lt;span class="nt"&gt;-d&lt;/span&gt; Ubuntu-24.04 &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;sleep &lt;/span&gt;infinity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;🔴 Mistake 3: Assuming MCP Uses SSE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The SigNoz MCP server's HTTP transport returns plain JSON, not Server-Sent Events. Our first call to &lt;code&gt;tools/list&lt;/code&gt; returned empty because we were grepping for &lt;code&gt;^data:&lt;/code&gt; SSE lines in the response. The server is also stateless — no session ID needed.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;🔴 Mistake 4: Wrong Log Query for Root Cause&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first attempt at Step 4 correlated logs by &lt;code&gt;trace_id&lt;/code&gt;. This surfaced a success log — &lt;code&gt;"Order placed successfully"&lt;/code&gt; — as the root cause. Switching to service-scoped &lt;code&gt;severity=ERROR&lt;/code&gt; logs captured &lt;code&gt;dial tcp ...:6379: i/o timeout&lt;/code&gt; immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; Lead with ERROR-severity logs. Use trace correlation for context, not primary evidence.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;🔴 Mistake 5: Thinking Model Truncation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;gemini-2.5-flash with thinking enabled burned most of &lt;code&gt;max_tokens=1600&lt;/code&gt; on reasoning before writing the JSON. The JSON truncated. &lt;code&gt;response_format=json_object&lt;/code&gt; alone wasn't enough.&lt;/p&gt;

&lt;p&gt;Adding &lt;code&gt;reasoning_effort="disable"&lt;/code&gt; fixed both truncation and latency: &lt;strong&gt;16s → 2s&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;🔴 Mistake 6: In-Memory Approval State&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Agent K's pending fixes were stored only in memory. Every Agency HQ restart wiped them, making all Approve buttons return 404. We added disk persistence and idempotent read-back to &lt;code&gt;/tmp/pending_fixes.json&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Deterministic playbooks beat agentic loops for reliability.&lt;/strong&gt;&lt;br&gt;
In a production SRE context, you want reproducible investigations. A fixed playbook means the same alert always runs the same tools in the same order. You can test it, measure its latency, and know exactly what it costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The third observability layer is free if you think to enable it.&lt;/strong&gt;&lt;br&gt;
Getting the MCP server to emit its own traces and metrics required four lines in &lt;code&gt;casting.yaml&lt;/code&gt;. The result is a dashboard panel showing MCP tool call latency broken down by tool — &lt;code&gt;signoz_get_trace_details&lt;/code&gt; averages 244ms, &lt;code&gt;signoz_query_metrics&lt;/code&gt; averages 328ms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Self-observability is a competitive moat.&lt;/strong&gt;&lt;br&gt;
If your AI system can tell you how much it costs, how long it takes, and which operations are slowest, it becomes auditable in a way that black-box systems cannot. The $0.0013 headline only exists because we emit &lt;code&gt;gen_ai.usage.cost&lt;/code&gt; on every synthesis span.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Human-gated" is not a weakness.&lt;/strong&gt;&lt;br&gt;
Agent K's approval requirement is often the most impressive part of the demo. It shows the design is aware of its own failure modes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'd redesign:&lt;/strong&gt; The predictive engine scores trends linearly and doesn't account for time-of-day seasonality. The evidence graph is hardcoded to the redis→cart→checkout→payment chain; a production version would derive topology from actual trace parent-child spans.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's next:&lt;/strong&gt; True agentic investigation (J decides mid-playbook to dig deeper on a specific span), multi-service correlation across a dependency chain, and Slack interactive messages for direct in-chat approval.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Part We Didn't Expect
&lt;/h2&gt;

&lt;p&gt;The most surprising result wasn't the AI verdict quality or the MCP integration. It was what happened when Agent K looked at unused metrics in a real system.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;15 of our top 39 metrics were being ingested and never read.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not because the system was broken — because the dashboards and alerts had evolved without anyone going back to prune the metrics. The data was being stored, compressed, and replicated in ClickHouse on every scrape, and nobody was looking at any of it.&lt;/p&gt;

&lt;p&gt;That's not an observability problem. That's an &lt;strong&gt;organizational problem that observability makes visible&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Agent K's job isn't just to find the issue — it's to surface the cost in a language everyone understands: &lt;em&gt;"~38.5% of your top-metric ingestion is unread."&lt;/em&gt; And then propose a fix that a human approves. That's the loop that makes AI agents useful in practice: not autonomous action, but autonomous &lt;em&gt;discovery&lt;/em&gt; with human-controlled &lt;em&gt;execution&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We set out to build two AI SREs and ended up with something more interesting: a system where the tool your agents use to observe your stack also observes them back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three layers of observability. One SigNoz. $0.0013 per investigation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The MCP protocol made this possible in a way that wasn't true a year ago. When the interface to your observability platform is a clean set of tool definitions — reads &lt;em&gt;and&lt;/em&gt; writes, traces &lt;em&gt;and&lt;/em&gt; dashboards &lt;em&gt;and&lt;/em&gt; alerts — the agents can do SRE work, not just SRE queries. That's a different category of useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ Code:&lt;/strong&gt; &lt;a href="https://github.com/HARJAPAN2005/MIB-Men-in-Backend" rel="noopener noreferrer"&gt;github.com/HARJAPAN2005/MIB-Men-in-Backend&lt;/a&gt; — three commands to deploy.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with heavy use of AI assistants (Claude, Gemini) — declared per hackathon rules. See README §AI Usage Declaration.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;MIB — Men in Backend · WeMakeDevs × Agents of SigNoz Hackathon · Jul 20–26, 2026&lt;/em&gt;&lt;/p&gt;

</description>
      <category>observability</category>
      <category>ai</category>
      <category>devops</category>
      <category>signoz</category>
    </item>
  </channel>
</rss>
