<?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: vishruthvayu</title>
    <description>The latest articles on DEV Community by vishruthvayu (@vishruthvayu).</description>
    <link>https://dev.to/vishruthvayu</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%2F4033921%2F7d910c62-3d1f-421a-8edf-20aa24e9ec3f.png</url>
      <title>DEV Community: vishruthvayu</title>
      <link>https://dev.to/vishruthvayu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vishruthvayu"/>
    <language>en</language>
    <item>
      <title>Building AgentLens: An Autonomous, Self-Healing AI SRE Copilot with SigNoz</title>
      <dc:creator>vishruthvayu</dc:creator>
      <pubDate>Sun, 26 Jul 2026 11:32:14 +0000</pubDate>
      <link>https://dev.to/vishruthvayu/building-agentlens-an-autonomous-self-healing-ai-sre-copilot-with-signoz-3dfc</link>
      <guid>https://dev.to/vishruthvayu/building-agentlens-an-autonomous-self-healing-ai-sre-copilot-with-signoz-3dfc</guid>
      <description>&lt;p&gt;The Problem: Generative AI is a Financial Black Box&lt;/p&gt;

&lt;p&gt;Generative AI agents are very powerful but they are also a mystery when it comes to money and how they work. If something goes wrong with an agent like it gets stuck in a loop it can spend all of a startups money for models in one night. Most tools that are supposed to help us see what is going on with AI just show us what already happened and then people have to fix the problem.&lt;/p&gt;

&lt;p&gt;I wanted to make something that would really solve this problem. For the WeMakeDevs SigNoz Hackathon I made AgentLens: a tool that helps AI agents work better by watching what they do. AgentLens uses SigNozs OpenTelemetry traces and alerts to tell the agent what to do. If the agent starts to cost too much money or does not work well it can fix itself by switching to a cheaper model or stopping before a person even notices.&lt;/p&gt;

&lt;p&gt;Here is how I made it using LangGraph, FastAPI and SigNoz including the code I wrote and the problems I had when I was testing it.&lt;/p&gt;

&lt;p&gt;Step 1: Deep OpenTelemetry Instrumentation&lt;/p&gt;

&lt;p&gt;Before an agent can fix itself it needs to know what it is doing. I made the part of the agent using LangGraph, which is like a plan that the agent follows. To get information into SigNoz I set up the OpenTelemetry Python SDK in my otel_setup.py file. I made a TracerProvider and a MeterProvider with settings for AI costs.&lt;/p&gt;

&lt;p&gt;The important part was adding special details to the agents actions, in the agent.py file. For example when the agent was generating text I tracked how many tokens it used and calculated how much it cost in real money. I made a graph to show this information, which is called a histogram metric and it is named gen_ai.usage.cost_usd.&lt;/p&gt;

&lt;p&gt;`def _record_llm_cost(span, model: str, usage: dict, session_id: str):&lt;br&gt;
    meter = _get_meter()&lt;br&gt;
    cost_hist = meter.create_histogram("gen_ai.usage.cost_usd", unit="usd")&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input_tokens = usage.get("prompt_tokens", 0) if usage else 0
output_tokens = usage.get("completion_tokens", 0) if usage else 0
cost = _cost_usd(model, input_tokens, output_tokens)

span.set_attribute("gen_ai.request.model", model)
span.set_attribute("gen_ai.usage.input_tokens", input_tokens)
span.set_attribute("gen_ai.usage.output_tokens", output_tokens)
span.set_attribute("gen_ai.usage.cost_usd", cost)
cost_hist.record(cost, {"model": model, "session_id": session_id})

return cost`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;By pushing this via OTLP to my local SigNoz instance, I could instantly see the exact waterfall of my agent's thought process.&lt;/p&gt;

&lt;p&gt;Step 2: Closing the Loop with Webhooks &amp;amp; Self-Healing&lt;br&gt;
Passive observability isn't enough. I set up two alerts in SigNoz: Cost Spike Per Session and Hallucination Score Drop. I configured these alerts to hit a local webhook endpoint.&lt;/p&gt;

&lt;p&gt;When SigNoz detects a spike, it POSTs to the webhook, which triggers my self_healing.py controller. This flips an in-memory flag. The next time the agent runs a node, it checks these flags. If the cost guard is active, it silently swaps the expensive model (llama-3.3-70b-versatile) for the much cheaper llama-3.1-8b-instant:&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;# From agent.py inside generate_node
&lt;/span&gt;&lt;span class="n"&gt;model_to_use&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AGENT_MODEL&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self_healing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_state&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;cost_alert_active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;model_to_use&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JUDGE_MODEL&lt;/span&gt;  &lt;span class="c1"&gt;# cheaper model
&lt;/span&gt;    &lt;span class="n"&gt;self_healing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;annotate_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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;downgraded_to_cheap_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;self_healing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record_action&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;add_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;self_healing_applied&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;action&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;downgraded_to_cheap_model&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;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;model_to_use&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;This forces the agent to autonomously adapt mid-session, recording the exact action on the trace so SREs can see the self-healing event in SigNoz.&lt;/p&gt;

&lt;p&gt;Step 3: Natural-Language Diagnostics with SigNoz MCP&lt;br&gt;
I wanted AgentLens to explain why a request failed in plain English. In mcp_diagnostic.py, I connected to the signoz-mcp-server as a client to pull live span data via the signoz_get_trace_details tool.&lt;/p&gt;

&lt;p&gt;What I learned (The debugging story): When I first ran this, my LLM crashed with a 413 Request too large error. It turns out the raw OpenTelemetry trace JSON returned by the MCP server is massive, and my free-tier Groq API key has a strict 6,000 Tokens Per Minute limit!&lt;/p&gt;

&lt;p&gt;I had to write a custom summarization function (_summarize_trace) to parse the SigNoz query-range response, sort the spans by duration, and extract only the most important attributes before sending it to the LLM.&lt;/p&gt;

&lt;p&gt;`def _summarize_trace(trace_data_text: str) -&amp;gt; str:&lt;br&gt;
    # ... JSON parsing ...&lt;br&gt;
    rows_sorted = sorted(rows, key=_row_duration_ns, reverse=True)[:MAX_SPANS_TO_INCLUDE]&lt;br&gt;
    lines = [f"Top {len(rows_sorted)} spans by duration:"]&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for row in rows_sorted:
    duration_ms = _row_duration_ns(row) / 1_000_000
    name = _row_name(row)

    # Only pull relevant AI and HTTP attributes to save tokens
    interesting = {
        k: v for k, v in row.items()
        if isinstance(k, str)
        and k.startswith(("gen_ai.", "agentlens.", "http.", "rpc."))
        and v not in (None, "", [], {})
    }

    line = f"- {name}: {duration_ms:.1f}ms | {json.dumps(interesting)}"
    lines.append(line)

# Hard cap at ~750 tokens to guarantee we never crash the LLM
return "\n".join(lines)[:MAX_PROMPT_CHARS]`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;By defensively truncating the prompt and filtering for keys starting with gen_ai. or agentlens., the LLM could successfully diagnose bottlenecks without blowing past token limits.&lt;/p&gt;

&lt;p&gt;Outcomes and What I’d Do Differently&lt;br&gt;
The outcome was a fully functioning SRE Sidekick that successfully catches cost explosions (simulated via my /chaos endpoint) and applies self-healing.&lt;/p&gt;

&lt;p&gt;If I were to build this again, I would focus on improving the MCP trace summarization. Currently, the diagnosis parses spans in a flat list sorted by duration. What I'd do differently next time is parse the parent-child span trees, allowing the LLM to distinguish between a parent span being inherently slow versus it just waiting on a bottlenecked child span.&lt;/p&gt;

&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;Building AgentLens showed that observability tools do not have to be passive. By instrumenting your code with OpenTelemetry and connecting SigNoz alerts directly back, into your applications logic you can create systems that do not just alert you when they break on a dashboard. These systems actively heal themselves in time.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>devchallenge</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Debugging the Black Box: My First Week With SigNoz</title>
      <dc:creator>vishruthvayu</dc:creator>
      <pubDate>Fri, 17 Jul 2026 17:55:32 +0000</pubDate>
      <link>https://dev.to/vishruthvayu/debugging-the-black-box-my-first-week-with-signoz-1gn2</link>
      <guid>https://dev.to/vishruthvayu/debugging-the-black-box-my-first-week-with-signoz-1gn2</guid>
      <description>&lt;p&gt;I spent weeks building a 4-step LLM agent (plan → retrieve → generate → judge) and then realized I had zero visibility into why it was failing or costing money. I had been building this agent for a little while before I really understood why "just add logging" doesn't cut it. When something went wrong, I had no way to answer the most basic question: which step actually broke? Was the LLM slow, or was my retrieval step the bottleneck? Was I paying for one LLM call or six because of a silent retry loop? I was flying blind in exactly the way observability tools exist to fix; I just hadn't set one up yet.&lt;/p&gt;

&lt;p&gt;So, before starting my actual hackathon build, I spent an afternoon self-hosting SigNoz and wiring it into a small FastAPI service, mostly to answer one question: is this actually going to help me see inside my agent, or is it just another dashboard I'll ignore?&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting it up (and hitting the first wall immediately)
&lt;/h3&gt;

&lt;p&gt;I went with self-hosted SigNoz—no cloud dependency, full control, and I wanted to be sure I could reproduce my own setup later. My first mistake was following an old mental model of the install process. As of v0.130.0, the old &lt;code&gt;docker-compose.yaml&lt;/code&gt; paths are deprecated in favor of a new CLI called Foundry.&lt;/p&gt;

&lt;p&gt;The actual process is now much cleaner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;https://signoz.io/foundry.sh]&lt;span class="o"&gt;(&lt;/span&gt;https://signoz.io/foundry.sh&lt;span class="o"&gt;)&lt;/span&gt; | sh

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; casting.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
apiVersion: v1alpha1
kind: Installation
metadata:
  name: signoz
spec:
  deployment:
    flavor: compose
    mode: docker
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;foundryctl cast &lt;span class="nt"&gt;-f&lt;/span&gt; casting.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A couple minutes later: five containers were up, and the UI was live at localhost:8080.&lt;/p&gt;

&lt;p&gt;Wiring up the first trace&lt;br&gt;
Instrumenting a FastAPI service turned out to be genuinely three pieces of code, not the sprawling configuration I expected:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.sdk.trace&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TracerProvider&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.sdk.trace.export&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BatchSpanProcessor&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.exporter.otlp.proto.http.trace_exporter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OTLPSpanExporter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.instrumentation.fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPIInstrumentor&lt;/span&gt;

&lt;span class="n"&gt;tracer_provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TracerProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;tracer_provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_span_processor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;BatchSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OTLPSpanExporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:4318/v1/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="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_tracer_provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tracer_provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;FastAPIInstrumentor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instrument_app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every request my app handled immediately showed up as a trace, with zero manual span creation for the HTTP layer. That part was easy. The part that took real debugging was getting cost to show up as a first-class metric, not just a log line.&lt;/p&gt;

&lt;p&gt;Where it actually got interesting: custom attributes&lt;br&gt;
Auto-instrumentation gives you HTTP-level visibility for free, but it doesn't know about LLM costs. The moment I cared about was adding a manual span around my agent's decision logic and attaching custom attributes:&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.generate&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.usage.cost_usd&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="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;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input_tokens&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;model_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&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%2Fmdnvstrb8z48knzkhoyc.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%2Fmdnvstrb8z48knzkhoyc.png" alt="Full trace view with custom gen_ai attributes and hallucination score." width="799" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Caption: Full trace view with custom gen_ai attributes and hallucination score.&lt;/p&gt;

&lt;p&gt;Watching gen_ai.usage.cost_usd show up as a searchable, filterable field on the span—not buried in a log string—was the moment this stopped feeling like logging and started feeling like observability. I could click into any single request and see exactly what it cost, which model handled it, and how long each step took, all correlated by one trace_id.&lt;/p&gt;

&lt;p&gt;The dumb bug that taught me the most&lt;br&gt;
While building a dashboard panel to chart that cost metric over time, I hit something that looked broken but wasn't. My cost values are tiny—around $0.0001 per request—and SigNoz's panel decimal precision only goes up to three decimal places. 0.0001 rounded to 0.000 on the chart, so the line just sat flat at zero. The fix wasn't a SigNoz bug, it was a unit-scaling problem: I added a formula (A * 1000000) to the query builder to convert the metric into millionths of a dollar before charting it.&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%2Fuakmwy8jvjiyn5g4qbhs.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%2Fuakmwy8jvjiyn5g4qbhs.png" alt="SigNoz Time Series showing agent step latencies." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Caption: SigNoz Time Series showing agent step latencies.&lt;/p&gt;

&lt;p&gt;I also learned that similarity_search_with_relevance_scores in my retriever doesn't always return values between 0 and 1; I saw negative numbers in my logs. It was harmless for my use case, but it served as a reminder to actually read library warnings instead of assuming output is always well-behaved.&lt;/p&gt;

&lt;p&gt;Setting Up Alerts for the Ops Console &amp;amp; Self-Healing&lt;br&gt;
This initial exploration was just the warm-up. For the main hackathon build, I focused on building a highly reactive self-healing loop. I created a custom alert rule targeting the gen_ai.usage.cost_usd attribute. When the cost breached the threshold, SigNoz triggers a webhook to my FastAPI backend.&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="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/webhooks/signoz-alert&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;signoz_alert_webhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;payload&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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;alerts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&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;alerts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;triggered&lt;/span&gt; &lt;span class="o"&gt;=&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;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;alerts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&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;labels&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="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;a&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="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;alert_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;labels&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;alertname&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;a&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;ruleId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&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;a&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;self_healing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handle_alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alert_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;triggered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alert_name&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&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;ok&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;triggered&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;triggered&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;asdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self_healing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_state&lt;/span&gt;&lt;span class="p"&gt;())}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&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%2F520l8t9q5yyl6629a5xe.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%2F520l8t9q5yyl6629a5xe.png" alt="Self-healing Ops Console after injecting cost chaos." width="799" height="480"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Caption: Self-healing Ops Console after injecting cost chaos.&lt;/p&gt;

&lt;p&gt;My ultimate goal is for the agent to react seamlessly to its own telemetry — catching an unexpected latency spike or cost explosion and downgrading itself to a cheaper, lighter model mid-session without human intervention. Wrestling with webhook payload verification and avoiding cyclic retry loops was challenging, but seeing the event stream sync up proved the architecture worked.&lt;/p&gt;

&lt;p&gt;What I'd tell someone setting this up today&lt;br&gt;
Use Foundry: Do not use the old docker-compose path. Check the SigNoz repo's current docs.&lt;/p&gt;

&lt;p&gt;Go beyond auto-instrumentation: The HTTP spans tell you that something happened; custom span attributes on your business logic (cost, tokens, model name) are what let you debug why.&lt;/p&gt;

&lt;p&gt;Build dashboards early: Creating a panel early surfaces unit and precision issues that you won't notice just staring at raw trace data.&lt;/p&gt;

&lt;p&gt;If you're setting up SigNoz for the first time, budget about 10 minutes from zero to your first trace. This project is part of my submission for the Agents of SigNoz Hackathon by WeMakeDevs. Observability-powered self-healing agents are incredibly powerful, and I'm excited to push this setup to its absolute limit.&lt;/p&gt;

</description>
      <category>signoz</category>
      <category>agents</category>
      <category>observability</category>
      <category>wemakedevs</category>
    </item>
  </channel>
</rss>
