<?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: S.SKANDHA ROSHAN </title>
    <description>The latest articles on DEV Community by S.SKANDHA ROSHAN  (@skandha_roshan).</description>
    <link>https://dev.to/skandha_roshan</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%2F4035907%2Fdadab61d-29f5-4fe9-ae0b-bdf726ad5d4c.png</url>
      <title>DEV Community: S.SKANDHA ROSHAN </title>
      <link>https://dev.to/skandha_roshan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/skandha_roshan"/>
    <language>en</language>
    <item>
      <title>I Traced My First AI Agent With SigNoz — and Caught a Bug Hiding in Plain Sight</title>
      <dc:creator>S.SKANDHA ROSHAN </dc:creator>
      <pubDate>Sun, 19 Jul 2026 02:01:10 +0000</pubDate>
      <link>https://dev.to/skandha_roshan/i-traced-my-first-ai-agent-with-signoz-and-caught-a-bug-hiding-in-plain-sight-2j7f</link>
      <guid>https://dev.to/skandha_roshan/i-traced-my-first-ai-agent-with-signoz-and-caught-a-bug-hiding-in-plain-sight-2j7f</guid>
      <description>&lt;h2&gt;
  
  
  I Traced My First AI Agent With SigNoz — and Caught a Bug Hiding in Plain Sight
&lt;/h2&gt;

&lt;p&gt;I filtered my traces for &lt;code&gt;has_error = true&lt;/code&gt;. &lt;strong&gt;No results found.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But I was staring right at an exception — &lt;code&gt;Simulated weather API timeout&lt;/code&gt; — sitting inside a span, plain as day. It felt like finding the rat had been in the trap the whole time, except the trap said "empty." That mismatch ended up being the most useful thing I learned tonight, and I only found it because I built something real enough to break.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I was doing this
&lt;/h2&gt;

&lt;p&gt;I'm prepping for the Agents of SigNoz hackathon, and before building the actual project, I wanted to get my hands dirty with SigNoz first — not just skim the docs. So I set myself one goal for the night: build a small AI agent, trace it end to end with OpenTelemetry, and break it on purpose to see what SigNoz actually shows me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting SigNoz running wasn't as simple as I expected
&lt;/h2&gt;

&lt;p&gt;I'm on WSL (Ubuntu on Windows). The guide I'd bookmarked earlier that week used the old &lt;code&gt;docker-compose&lt;/code&gt; setup, but SigNoz deprecated that as of v0.130.0 in favor of a new installer called &lt;strong&gt;Foundry&lt;/strong&gt; — so half of what I'd read was already out of date.&lt;/p&gt;

&lt;p&gt;Their current docs are also explicit about one thing: on Windows, run Docker natively inside WSL 2, not Docker Desktop, because ClickHouse Keeper is known to segfault under Docker Desktop's virtualization layer. I installed Docker directly in WSL with &lt;code&gt;get.docker.com&lt;/code&gt;, which matched this recommendation.&lt;/p&gt;

&lt;p&gt;Once Docker was running, I installed Foundry and deployed:&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; https://signoz.io/foundry.sh | bash

&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;It pulled all four images fine — ClickHouse, Postgres, the OTel collector, SigNoz itself — then died mid-startup with &lt;code&gt;signal: killed&lt;/code&gt;. Turned out my WSL instance only had 1.8GB of RAM allocated by default, well under SigNoz's documented 4GB minimum — and Docker only reported &lt;code&gt;signal: killed&lt;/code&gt;, which made the memory issue hard to recognize at first. The fix was a &lt;code&gt;.wslconfig&lt;/code&gt; file on the Windows side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[wsl2]
memory=6GB
processors=4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;followed by &lt;code&gt;wsl --shutdown&lt;/code&gt; and restarting. After that, &lt;code&gt;foundryctl cast&lt;/code&gt; succeeded and I had a working SigNoz UI at &lt;code&gt;localhost:8080&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the agent
&lt;/h2&gt;

&lt;p&gt;Once SigNoz was up, I built a small LangGraph agent with two tools and one LLM call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User query → weather_tool → calculator_tool → llm_call → Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;weather_tool&lt;/code&gt; — fetches (fake) weather data, with a simulated chance of timing out&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;calculator_tool&lt;/code&gt; — converts the temperature, always succeeds&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;llm_call&lt;/code&gt; — combines both results and answers the user via Gemini&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each node is wrapped in its own OpenTelemetry span:&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;weather_tool_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&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;weather_tool&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;tool.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;weather_tool&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;tool.input&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;Coimbatore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;should_fail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FORCE_FAIL&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;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.3&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;should_fail&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;tool.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;failed&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;record_exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Simulated weather API timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weather_result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ERROR: weather API timed out&lt;/span&gt;&lt;span class="sh"&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;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;tool.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;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weather_result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;32°C, clear skies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Getting the traces to actually reach SigNoz took another round of trial and error. I first pointed the exporter at the gRPC endpoint on port 4317, and it kept failing with a TLS handshake error — &lt;code&gt;SSL_ERROR_SSL: WRONG_VERSION_NUMBER&lt;/code&gt;. Switching to the HTTP exporter on port 4318 instead fixed it immediately:&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;exporter&lt;/span&gt; &lt;span class="o"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also hit two separate 404s from the Gemini API because &lt;code&gt;gemini-1.5-flash&lt;/code&gt; and then &lt;code&gt;gemini-2.5-flash&lt;/code&gt; had both been deprecated since I last checked model names. Switching to the &lt;code&gt;gemini-flash-latest&lt;/code&gt; alias solved it — pointing at "latest" instead of pinning a specific version turned out to be the safer move.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watching it actually work
&lt;/h2&gt;

&lt;p&gt;With all of that sorted, running the script sent real traces into SigNoz, and the waterfall view showed exactly what I'd hoped: &lt;code&gt;agent_run&lt;/code&gt; as the parent span, with &lt;code&gt;weather_tool&lt;/code&gt;, &lt;code&gt;calculator_tool&lt;/code&gt;, and &lt;code&gt;llm_call&lt;/code&gt; nested underneath, each with its own duration.&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%2Fuacuuak1otbtbfk06c99.jpeg" 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%2Fuacuuak1otbtbfk06c99.jpeg" alt="SigNoz trace waterfall showing the agent_run span with weather_tool, calculator_tool, and llm_call nested spans and their individual durations" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;llm_call&lt;/code&gt; took up about 64% of the total execution time. I'd assumed the LLM call would be the slowest step, but seeing the actual percentage broken out was more convincing than just guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Breaking it on purpose
&lt;/h2&gt;

&lt;p&gt;Next I forced the &lt;code&gt;weather_tool&lt;/code&gt; to fail, to see how SigNoz would surface it. The span showed up with the exception recorded under its &lt;strong&gt;Events&lt;/strong&gt; tab — &lt;code&gt;exception.message: Simulated weather API timeout&lt;/code&gt;, full type and stack trace, timestamped to the millisecond.&lt;/p&gt;

&lt;p&gt;Exactly what I expected. So I turned on SigNoz's "Highlight errors" toggle and filtered by &lt;code&gt;has_error = true&lt;/code&gt;, expecting this trace to show up immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero results.&lt;/strong&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%2F7lche40mchmn8ogd2u74.jpeg" 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%2F7lche40mchmn8ogd2u74.jpeg" alt="SigNoz has_error filter showing zero results despite a recorded exception inside the weather_tool span" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At first I thought I'd broken something on my end. Then I realized what was actually going on: &lt;code&gt;span.record_exception()&lt;/code&gt; logs the exception as an &lt;strong&gt;event&lt;/strong&gt; attached to the span — it does not automatically change the span's &lt;strong&gt;status&lt;/strong&gt; to error. In my setup, the &lt;code&gt;has_error = true&lt;/code&gt; filter didn't match spans where I'd only called &lt;code&gt;record_exception()&lt;/code&gt;. Nothing in the quick-start docs I'd read made that distinction obvious ahead of time.&lt;/p&gt;

&lt;p&gt;Interestingly, even with that gap, the agent itself handled the failure gracefully — when I asked it for the weather, it told me the weather API had timed out but still gave me the temperature conversion from the calculator tool. The failure was real, but the agent degraded instead of crashing outright.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing the loop
&lt;/h2&gt;

&lt;p&gt;Before fixing anything, I wanted a real number, not just one missed trace. I forced &lt;code&gt;weather_tool&lt;/code&gt; to fail &lt;strong&gt;8 times&lt;/strong&gt; with the original code, then filtered &lt;code&gt;has_error = true&lt;/code&gt; across all of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;0 out of 8&lt;/strong&gt; showed up.&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%2Frpb8olvxdqgslnms0w2e.jpeg" 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%2Frpb8olvxdqgslnms0w2e.jpeg" alt="SigNoz has_error filter showing zero results after forcing eight failed traces, before adding span.set_status" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I then added the missing line to &lt;code&gt;weather_tool_node&lt;/code&gt;:&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.trace&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StatusCode&lt;/span&gt;

&lt;span class="c1"&gt;# inside the failure branch, alongside record_exception():
&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_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Simulated weather API timeout&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;Same test, 5 more forced failures, same filter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5 out of 5&lt;/strong&gt; showed up this time, each one correctly listed with its timestamp and duration.&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%2Flwuqsviecfx0hwbwcy45.jpeg" 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%2Flwuqsviecfx0hwbwcy45.jpeg" alt="SigNoz has_error filter showing all five forced failures correctly detected after adding span.set_status" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's the actual size of the gap: recording an exception alone gave me a 0% detection rate under the error filter; adding one explicit status call brought it to 100%, on the exact same underlying failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing steps with a quick dashboard
&lt;/h2&gt;

&lt;p&gt;Once I had a few runs recorded, I built a small dashboard panel to compare average duration across all four spans instead of reading it off one trace at a time:&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%2Fr0kmhd2b2uiz0is8g3zn.jpeg" 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%2Fr0kmhd2b2uiz0is8g3zn.jpeg" alt="SigNoz dashboard bar chart comparing average duration across weather_tool, calculator_tool, llm_call, and agent_run spans" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Across multiple runs, &lt;code&gt;weather_tool&lt;/code&gt; and &lt;code&gt;llm_call&lt;/code&gt; were consistently the two heaviest steps, with &lt;code&gt;calculator_tool&lt;/code&gt; barely registering by comparison. That matched what I'd noticed in individual traces, but seeing it aggregated across runs made the pattern obvious in a way a single waterfall view doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up one alert
&lt;/h2&gt;

&lt;p&gt;The last SigNoz feature I wanted to actually touch, not just read about, was alerting. I set up a traces-based alert rule with the same filter as before — &lt;code&gt;service.name = 'hackathon-agent' AND has_error = true&lt;/code&gt;, using &lt;code&gt;count()&lt;/code&gt; — configured to fire when the count goes above 0 at least once in a rolling 5-minute window.&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%2Fvzj8eykua0fao4t5z0jv.jpeg" 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%2Fvzj8eykua0fao4t5z0jv.jpeg" alt="SigNoz alert rule list showing the weather_tool error alert with OK status" width="799" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I didn't wire up a real notification channel for this local demo, so the rule exists and evaluates correctly, but nothing actually gets delivered anywhere yet. Looking at the rule's own history chart, I could see it clearly picking up the spike from my earlier forced-failure runs and crossing the threshold line exactly when expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Recording an exception on a span and marking a span as errored are two separate things in OpenTelemetry — if you want your errors to show up in error-based filters or alerts, you need to explicitly set the span status.&lt;/li&gt;
&lt;li&gt;WSL's default memory allocation (1.8GB) is nowhere near SigNoz's documented 4GB minimum, and Docker fails silently with &lt;code&gt;signal: killed&lt;/code&gt; rather than telling you it's a memory problem.&lt;/li&gt;
&lt;li&gt;Model names for hosted LLMs go stale fast. Pointing at &lt;code&gt;-latest&lt;/code&gt; aliases instead of specific version strings saved me from repeating the same 404 error twice.&lt;/li&gt;
&lt;li&gt;The waterfall view genuinely changes how you reason about where time goes in a multi-step agent — my assumption about which step was slowest and the actual measured percentage weren't the same thing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Most of tonight was small, unglamorous debugging — a memory limit, a deprecated model, a wrong port — and none of that is what I expected to be writing about going in. But the one thing that actually taught me something about observability was the smallest, quietest bug of the night: a filter that should have caught my error, and silently didn't. That gap between "the data exists" and "the data is queryable the way you'd assume" is, I think, the entire reason to build something small and break it yourself instead of just reading the docs.&lt;/p&gt;

&lt;p&gt;I started the evening trying to learn SigNoz. I ended it with a much better understanding of observability — and a working prototype I'll keep building on for the actual hackathon project.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
      <category>tools</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
