<?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: Sairaamm Bharadwaj</title>
    <description>The latest articles on DEV Community by Sairaamm Bharadwaj (@sairaammbharadwaj).</description>
    <link>https://dev.to/sairaammbharadwaj</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%2F4034889%2F9cfb73d5-b2c8-42ab-ace4-98e589eadfb3.png</url>
      <title>DEV Community: Sairaamm Bharadwaj</title>
      <link>https://dev.to/sairaammbharadwaj</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sairaammbharadwaj"/>
    <language>en</language>
    <item>
      <title>Your AI Agent Is a Distributed System in Disguise — Instrument It with OpenTelemetry and SigNoz Before It Bankrupts You</title>
      <dc:creator>Sairaamm Bharadwaj</dc:creator>
      <pubDate>Sat, 18 Jul 2026 12:42:20 +0000</pubDate>
      <link>https://dev.to/sairaammbharadwaj/your-ai-agent-is-a-distributed-system-in-disguise-instrument-it-with-opentelemetry-and-signoz-1h4f</link>
      <guid>https://dev.to/sairaammbharadwaj/your-ai-agent-is-a-distributed-system-in-disguise-instrument-it-with-opentelemetry-and-signoz-1h4f</guid>
      <description>&lt;p&gt;&lt;em&gt;My warm-up blog for the &lt;a href="https://www.wemakedevs.org/hackathons/signoz" rel="noopener noreferrer"&gt;Agents of SigNoz hackathon&lt;/a&gt; by WeMakeDevs x SigNoz. Code and the full build log live in my repo: &lt;a href="https://github.com/SairaammBharadwaj/sentinel-signoz" rel="noopener noreferrer"&gt;github.com/SairaammBharadwaj/sentinel-signoz&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Here's a claim I'll defend for the rest of this post: &lt;strong&gt;the moment you wrapped an LLM in a loop and gave it tools, you stopped building an application and started operating a distributed system&lt;/strong&gt; — one that is non-deterministic, fans out across network boundaries you don't control, and bills you per token for the privilege. Most teams are running these systems in production with less instrumentation than they'd put on a toy CRUD app. That gap is where the 2 a.m. incidents, the mystery invoices, and the silent quality regressions live.&lt;/p&gt;

&lt;p&gt;I want to make the case, concretely, for why AI agents demand observability, what the standards-based way to do it looks like in 2026, and why an OpenTelemetry-native backend like SigNoz is the right place to send the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure modes traditional monitoring cannot see
&lt;/h2&gt;

&lt;p&gt;A conventional service has a bounded, mostly-deterministic execution path. Your existing APM catches the things that go wrong there: a 500, a slow query, a memory leak. Now look at what an agent actually does on a single user request:&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
  └─ LLM call #1 (planning)              → 1,900 tokens
       └─ tool: vector_search(k=8)       → 420 ms, 8 chunks
       └─ tool: sql_query()              → ret/err → retry x3
       └─ LLM call #2 (re-plan)          → 2,300 tokens
            └─ tool: http_fetch()        → 200 OK, 14 KB injected into context
            └─ LLM call #3 (synthesis)   → 3,100 tokens
  └─ response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's &lt;strong&gt;three LLM calls, four tool invocations, three retries, and a 14 KB context injection&lt;/strong&gt;, all hidden behind one chat bubble. Every one of these is an independent failure surface, and the interesting failures are precisely the ones your HTTP-status-code dashboards will never register:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost runaway.&lt;/strong&gt; A retry loop or an over-eager re-planning step doesn't throw — it just quietly 10x's your token spend. Cost is a &lt;em&gt;primary&lt;/em&gt; failure mode for agents, not a billing footnote. A single misbehaving conversation can cost more than a thousand well-behaved ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unbounded tool-call loops.&lt;/strong&gt; The agent decides to call a tool, doesn't like the result, calls it again, and again. No exception is raised. Latency climbs, tokens burn, and the only external symptom is a user who gave up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent quality degradation.&lt;/strong&gt; This is the scary one. The system returns &lt;code&gt;200 OK&lt;/code&gt; with a fluent, confident, &lt;em&gt;wrong&lt;/em&gt; answer. Nothing in your logs or metrics moves. You find out from a support ticket or a churned customer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context and retrieval poisoning.&lt;/strong&gt; A retrieval step pulls a bad chunk, or a tool injects untrusted text into the prompt. The model dutifully follows it. From the outside, everything looks nominal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-reproducibility.&lt;/strong&gt; Sampling temperature and model-side non-determinism mean "just reproduce it locally" is often impossible. The trace &lt;em&gt;is&lt;/em&gt; the reproduction — if you captured it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The through-line: &lt;strong&gt;agent failures are semantic and economic, not just operational.&lt;/strong&gt; You need visibility into tokens, cost, tool arguments, retrieval results, model versions, and answer quality — none of which your traditional stack was built to record.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenTelemetry, and why the GenAI semantic conventions matter
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opentelemetry.io/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt; (OTel) is the vendor-neutral CNCF standard for emitting &lt;strong&gt;traces&lt;/strong&gt;, &lt;strong&gt;metrics&lt;/strong&gt;, and &lt;strong&gt;logs&lt;/strong&gt;. The primitives map onto agents almost perfectly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;trace&lt;/strong&gt; is one full request, end to end — the entire tree above is a single trace.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;span&lt;/strong&gt; is one node in that tree (an LLM call, a tool invocation, a retrieval), with a start time, duration, status, and arbitrary key/value &lt;strong&gt;attributes&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Span links and parent/child relationships&lt;/strong&gt; reconstruct the causal structure — which LLM call triggered which tool call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics&lt;/strong&gt; are the aggregates you alert on (p95 latency, tokens/min, cost/hour).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs&lt;/strong&gt; correlate to spans via trace IDs, so a log line is anchored to the exact step that emitted it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What makes this usable for AI specifically is the &lt;strong&gt;&lt;a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/" rel="noopener noreferrer"&gt;OpenTelemetry GenAI semantic conventions&lt;/a&gt;&lt;/strong&gt; — a standardized &lt;code&gt;gen_ai.*&lt;/code&gt; attribute namespace. Instead of every team inventing its own field names, the ecosystem has converged (this is now the de facto 2026 baseline) on conventions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;gen_ai.system&lt;/code&gt; — the provider (&lt;code&gt;openai&lt;/code&gt;, &lt;code&gt;anthropic&lt;/code&gt;, …)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gen_ai.request.model&lt;/code&gt; / &lt;code&gt;gen_ai.response.model&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gen_ai.usage.input_tokens&lt;/code&gt; / &lt;code&gt;gen_ai.usage.output_tokens&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gen_ai.request.temperature&lt;/code&gt;, &lt;code&gt;gen_ai.request.max_tokens&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gen_ai.response.finish_reason&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gen_ai.operation.name&lt;/code&gt; — &lt;code&gt;chat&lt;/code&gt;, &lt;code&gt;tool_execution&lt;/code&gt;, &lt;code&gt;embeddings&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Standardized names are not a cosmetic nicety. They're what lets a backend render a purpose-built LLM view, lets you compute cost with a generic formula across providers, and lets you swap tools without rewriting instrumentation. &lt;strong&gt;Vendor-neutrality is the entire point&lt;/strong&gt; — your data is portable by construction.&lt;/p&gt;

&lt;p&gt;Here's manual instrumentation of one model call. In a real agent you'd lean on auto-instrumentation (OpenLLMetry, OpenInference, or framework hooks for LangChain/LangGraph), but seeing it by hand makes the model concrete:&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.trace&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SpanKind&lt;/span&gt;&lt;span class="p"&gt;,&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="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&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;get_tracer&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.llm&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;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&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="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;"&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;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;kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SpanKind&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="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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai&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.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;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.request.temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2&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="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&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;model&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="mf"&gt;0.2&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;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;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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_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="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&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="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt;

        &lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&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;prompt_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.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;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completion_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.response.finish_reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&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;finish_reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# derive cost from a pricing table and attach it as a first-class metric
&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="nf"&gt;estimate_cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&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;prompt_tokens&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;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;),&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;resp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nest your tool calls as child spans under the active span and you get the full causal tree — with tokens and cost attached to every node. Your black box now has structured, queryable telemetry flowing out of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the backend should be OpenTelemetry-native: SigNoz
&lt;/h2&gt;

&lt;p&gt;OTel emits the data; you still need somewhere to store, query, visualize, and alert on it. The architectural decision that matters here is &lt;strong&gt;native vs. adapted&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Many observability vendors bolt an OTLP endpoint onto a proprietary data model — you ship OTel, they translate, and you inherit the impedance mismatch (dropped attributes, lossy conversions, a UI that was never designed around traces). &lt;a href="https://signoz.io/" rel="noopener noreferrer"&gt;SigNoz&lt;/a&gt; is &lt;strong&gt;OpenTelemetry-native&lt;/strong&gt;: OTLP is the primary ingestion path, spans/metrics/logs are stored in a columnar store (ClickHouse) tuned for high-cardinality attributes — exactly what &lt;code&gt;gen_ai.*&lt;/code&gt; data is — and traces, metrics, and logs are correlated by design rather than stitched after the fact. It's open-source, self-hostable via Docker or Kubernetes, with a managed cloud option.&lt;/p&gt;

&lt;p&gt;For agents, that architecture pays off directly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High-cardinality attributes are first-class.&lt;/strong&gt; Slice by &lt;code&gt;gen_ai.request.model&lt;/code&gt;, by user, by tool, by finish reason, without the cardinality penalties that punish tag-based systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trace → metric → log correlation&lt;/strong&gt; means you can jump from "cost spiked at 14:20" to the exact traces to the exact log lines in a couple of clicks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dashboards and alerts over derived fields&lt;/strong&gt; like &lt;code&gt;gen_ai.usage.cost_usd&lt;/code&gt; and token counts let you alert on economic and semantic signals, not just infrastructure ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The part that reframes the whole workflow: the MCP server
&lt;/h3&gt;

&lt;p&gt;In 2026 SigNoz shipped an &lt;strong&gt;&lt;a href="https://signoz.io/docs/ai/signoz-mcp-server/" rel="noopener noreferrer"&gt;MCP server&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://signoz.io/changelog/2026-05-01-introducing-the-signoz-mcp-server-r5iwnkpxtsz88akwt6abqddn/" rel="noopener noreferrer"&gt;announcement&lt;/a&gt;, &lt;a href="https://github.com/SigNoz/signoz-mcp-server" rel="noopener noreferrer"&gt;source&lt;/a&gt;). MCP (Model Context Protocol) is an open standard that exposes a tool surface to LLMs — and here it means an AI assistant can query your observability data in natural language: &lt;em&gt;"show me the slowest traces in the last hour,"&lt;/em&gt; &lt;em&gt;"which model calls had the highest token cost today,"&lt;/em&gt; even &lt;em&gt;"build an incident dashboard for the checkout service."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sit with the implication. The traditional loop is &lt;strong&gt;human → dashboard → eyeballs → hypothesis&lt;/strong&gt;. With an MCP-queryable backend, an &lt;em&gt;agent&lt;/em&gt; can drive that loop: an LLM can pull the offending trace, read the span attributes, correlate the logs, and construct the dashboard programmatically. Observability stops being a place you go to look and becomes an API your automation can reason over. SigNoz has already demoed a &lt;a href="https://signoz.io/blog/monitoring-langchain-agent-querying-signoz-mcp-server/" rel="noopener noreferrer"&gt;LangChain agent that queries its MCP server&lt;/a&gt; — closing the loop from "AI you observe" to "AI that observes."&lt;/p&gt;

&lt;h2&gt;
  
  
  The thesis I'm taking into the hackathon: close the loop
&lt;/h2&gt;

&lt;p&gt;Today, observability stops at &lt;em&gt;detection&lt;/em&gt;. You get an alert; a human does the diagnosis and the fix. But every ingredient for automating the diagnosis now exists: standardized &lt;code&gt;gen_ai.*&lt;/code&gt; telemetry in SigNoz, and an MCP server that lets an agent read it. So why stop at the alert?&lt;/p&gt;

&lt;p&gt;The architecture I'm prototyping — call it &lt;strong&gt;Sentinel&lt;/strong&gt; — runs the full loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Instrument&lt;/strong&gt; a target agent with OTel GenAI conventions → telemetry lands in SigNoz.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detect&lt;/strong&gt; the agent-specific failure classes traditional monitoring misses — cost runaways, tool-call loops, latency cliffs, and &lt;em&gt;silent quality regressions&lt;/em&gt; via eval-scores-as-span-attributes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diagnose&lt;/strong&gt; by dispatching an investigation agent that queries SigNoz through MCP, isolates the offending span, and reconstructs the causal chain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Act&lt;/strong&gt; — auto-generate an incident dashboard, emit a plain-English root-cause report pinned to the exact failing span, and apply or recommend a remediation (e.g., a circuit breaker on the runaway loop).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An AI that watches other AIs, and doesn't just tell you what broke — it tells you &lt;em&gt;why&lt;/em&gt; and moves to fix it. That's the demo I want to put in front of the judges.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you're shipping agents, start here
&lt;/h2&gt;

&lt;p&gt;You don't need a hackathon as an excuse, and you don't need to boil the ocean. The highest-leverage sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add OpenTelemetry to your agent&lt;/strong&gt; using the GenAI semantic conventions — auto-instrument first, then hand-instrument the spans that matter (tool calls, retrievals).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attach cost and tokens as span attributes&lt;/strong&gt;, and derive a &lt;code&gt;cost_usd&lt;/code&gt; field so spend is a first-class, alertable signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Point OTLP at SigNoz&lt;/strong&gt; — Docker to start, a few commands — and read exactly one real end-to-end conversation trace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attach an eval score&lt;/strong&gt; to your answer spans so you can alert on quality &lt;em&gt;drift&lt;/em&gt;, not just errors.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The scariest property of a black box isn't that it fails. It's that it fails &lt;em&gt;quietly&lt;/em&gt; — and you learn about it from a user, or from your invoice, instead of from your telemetry.&lt;/p&gt;

&lt;p&gt;Instrument the box. Then crack it open.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building this for the Agents of SigNoz hackathon (July 20–26, 2026). The code — an instrumented agent, injected failure scenarios, and a detect → diagnose → act detector — is on GitHub: &lt;a href="https://github.com/SairaammBharadwaj/sentinel-signoz" rel="noopener noreferrer"&gt;github.com/SairaammBharadwaj/sentinel-signoz&lt;/a&gt;. Come talk shop in the &lt;a href="https://signoz.io/" rel="noopener noreferrer"&gt;SigNoz Slack&lt;/a&gt; community.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>signoz</category>
      <category>observability</category>
      <category>opentelemetry</category>
    </item>
    <item>
      <title># Your AI Agent Is a Distributed System in Disguise — Instrument It with OpenTelemetry and SigNoz Before It Bankrupts You</title>
      <dc:creator>Sairaamm Bharadwaj</dc:creator>
      <pubDate>Sat, 18 Jul 2026 07:42:15 +0000</pubDate>
      <link>https://dev.to/sairaammbharadwaj/-your-ai-agent-is-a-distributed-system-in-disguise-instrument-it-with-opentelemetry-and-signoz-2e1d</link>
      <guid>https://dev.to/sairaammbharadwaj/-your-ai-agent-is-a-distributed-system-in-disguise-instrument-it-with-opentelemetry-and-signoz-2e1d</guid>
      <description>&lt;p&gt;&lt;em&gt;My warm-up blog for the &lt;a href="https://www.wemakedevs.org/hackathons/signoz" rel="noopener noreferrer"&gt;Agents of SigNoz hackathon&lt;/a&gt; by WeMakeDevs x SigNoz.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Here's a claim I'll defend for the rest of this post: &lt;strong&gt;the moment you wrapped an LLM in a loop and gave it tools, you stopped building an application and started operating a distributed system&lt;/strong&gt; — one that is non-deterministic, fans out across network boundaries you don't control, and bills you per token for the privilege. Most teams are running these systems in production with less instrumentation than they'd put on a toy CRUD app. That gap is where the 2 a.m. incidents, the mystery invoices, and the silent quality regressions live.&lt;/p&gt;

&lt;p&gt;I want to make the case, concretely, for why AI agents demand observability, what the standards-based way to do it looks like in 2026, and why an OpenTelemetry-native backend like SigNoz is the right place to send the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure modes traditional monitoring cannot see
&lt;/h2&gt;

&lt;p&gt;A conventional service has a bounded, mostly-deterministic execution path. Your existing APM catches the things that go wrong there: a 500, a slow query, a memory leak. Now look at what an agent actually does on a single user request:&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
  └─ LLM call #1 (planning)              → 1,900 tokens
       └─ tool: vector_search(k=8)       → 420 ms, 8 chunks
       └─ tool: sql_query()              → ret/err → retry x3
       └─ LLM call #2 (re-plan)          → 2,300 tokens
            └─ tool: http_fetch()        → 200 OK, 14 KB injected into context
            └─ LLM call #3 (synthesis)   → 3,100 tokens
  └─ response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's &lt;strong&gt;three LLM calls, four tool invocations, three retries, and a 14 KB context injection&lt;/strong&gt;, all hidden behind one chat bubble. Every one of these is an independent failure surface, and the interesting failures are precisely the ones your HTTP-status-code dashboards will never register:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost runaway.&lt;/strong&gt; A retry loop or an over-eager re-planning step doesn't throw — it just quietly 10x's your token spend. Cost is a &lt;em&gt;primary&lt;/em&gt; failure mode for agents, not a billing footnote. A single misbehaving conversation can cost more than a thousand well-behaved ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unbounded tool-call loops.&lt;/strong&gt; The agent decides to call a tool, doesn't like the result, calls it again, and again. No exception is raised. Latency climbs, tokens burn, and the only external symptom is a user who gave up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent quality degradation.&lt;/strong&gt; This is the scary one. The system returns &lt;code&gt;200 OK&lt;/code&gt; with a fluent, confident, &lt;em&gt;wrong&lt;/em&gt; answer. Nothing in your logs or metrics moves. You find out from a support ticket or a churned customer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context and retrieval poisoning.&lt;/strong&gt; A retrieval step pulls a bad chunk, or a tool injects untrusted text into the prompt. The model dutifully follows it. From the outside, everything looks nominal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-reproducibility.&lt;/strong&gt; Sampling temperature and model-side non-determinism mean "just reproduce it locally" is often impossible. The trace &lt;em&gt;is&lt;/em&gt; the reproduction — if you captured it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The through-line: &lt;strong&gt;agent failures are semantic and economic, not just operational.&lt;/strong&gt; You need visibility into tokens, cost, tool arguments, retrieval results, model versions, and answer quality — none of which your traditional stack was built to record.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenTelemetry, and why the GenAI semantic conventions matter
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opentelemetry.io/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt; (OTel) is the vendor-neutral CNCF standard for emitting &lt;strong&gt;traces&lt;/strong&gt;, &lt;strong&gt;metrics&lt;/strong&gt;, and &lt;strong&gt;logs&lt;/strong&gt;. The primitives map onto agents almost perfectly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;trace&lt;/strong&gt; is one full request, end to end — the entire tree above is a single trace.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;span&lt;/strong&gt; is one node in that tree (an LLM call, a tool invocation, a retrieval), with a start time, duration, status, and arbitrary key/value &lt;strong&gt;attributes&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Span links and parent/child relationships&lt;/strong&gt; reconstruct the causal structure — which LLM call triggered which tool call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics&lt;/strong&gt; are the aggregates you alert on (p95 latency, tokens/min, cost/hour).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs&lt;/strong&gt; correlate to spans via trace IDs, so a log line is anchored to the exact step that emitted it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What makes this usable for AI specifically is the &lt;strong&gt;&lt;a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/" rel="noopener noreferrer"&gt;OpenTelemetry GenAI semantic conventions&lt;/a&gt;&lt;/strong&gt; — a standardized &lt;code&gt;gen_ai.*&lt;/code&gt; attribute namespace. Instead of every team inventing its own field names, the ecosystem has converged (this is now the de facto 2026 baseline) on conventions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;gen_ai.system&lt;/code&gt; — the provider (&lt;code&gt;openai&lt;/code&gt;, &lt;code&gt;anthropic&lt;/code&gt;, …)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gen_ai.request.model&lt;/code&gt; / &lt;code&gt;gen_ai.response.model&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gen_ai.usage.input_tokens&lt;/code&gt; / &lt;code&gt;gen_ai.usage.output_tokens&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gen_ai.request.temperature&lt;/code&gt;, &lt;code&gt;gen_ai.request.max_tokens&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gen_ai.response.finish_reason&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gen_ai.operation.name&lt;/code&gt; — &lt;code&gt;chat&lt;/code&gt;, &lt;code&gt;tool_execution&lt;/code&gt;, &lt;code&gt;embeddings&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Standardized names are not a cosmetic nicety. They're what lets a backend render a purpose-built LLM view, lets you compute cost with a generic formula across providers, and lets you swap tools without rewriting instrumentation. &lt;strong&gt;Vendor-neutrality is the entire point&lt;/strong&gt; — your data is portable by construction.&lt;/p&gt;

&lt;p&gt;Here's manual instrumentation of one model call. In a real agent you'd lean on auto-instrumentation (OpenLLMetry, OpenInference, or framework hooks for LangChain/LangGraph), but seeing it by hand makes the model concrete:&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.trace&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SpanKind&lt;/span&gt;&lt;span class="p"&gt;,&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="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&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;get_tracer&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.llm&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;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&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="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;"&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;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;kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SpanKind&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="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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai&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.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;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.request.temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2&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="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&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;model&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="mf"&gt;0.2&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;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;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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_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="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&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="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt;

        &lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&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;prompt_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.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;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completion_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.response.finish_reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&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;finish_reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# derive cost from a pricing table and attach it as a first-class metric
&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="nf"&gt;estimate_cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&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;prompt_tokens&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;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;),&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;resp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nest your tool calls as child spans under the active span and you get the full causal tree — with tokens and cost attached to every node. Your black box now has structured, queryable telemetry flowing out of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the backend should be OpenTelemetry-native: SigNoz
&lt;/h2&gt;

&lt;p&gt;OTel emits the data; you still need somewhere to store, query, visualize, and alert on it. The architectural decision that matters here is &lt;strong&gt;native vs. adapted&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Many observability vendors bolt an OTLP endpoint onto a proprietary data model — you ship OTel, they translate, and you inherit the impedance mismatch (dropped attributes, lossy conversions, a UI that was never designed around traces). &lt;a href="https://signoz.io/" rel="noopener noreferrer"&gt;SigNoz&lt;/a&gt; is &lt;strong&gt;OpenTelemetry-native&lt;/strong&gt;: OTLP is the primary ingestion path, spans/metrics/logs are stored in a columnar store (ClickHouse) tuned for high-cardinality attributes — exactly what &lt;code&gt;gen_ai.*&lt;/code&gt; data is — and traces, metrics, and logs are correlated by design rather than stitched after the fact. It's open-source, self-hostable via Docker or Kubernetes, with a managed cloud option.&lt;/p&gt;

&lt;p&gt;For agents, that architecture pays off directly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High-cardinality attributes are first-class.&lt;/strong&gt; Slice by &lt;code&gt;gen_ai.request.model&lt;/code&gt;, by user, by tool, by finish reason, without the cardinality penalties that punish tag-based systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trace → metric → log correlation&lt;/strong&gt; means you can jump from "cost spiked at 14:20" to the exact traces to the exact log lines in a couple of clicks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dashboards and alerts over derived fields&lt;/strong&gt; like &lt;code&gt;gen_ai.usage.cost_usd&lt;/code&gt; and token counts let you alert on economic and semantic signals, not just infrastructure ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The part that reframes the whole workflow: the MCP server
&lt;/h3&gt;

&lt;p&gt;In 2026 SigNoz shipped an &lt;strong&gt;&lt;a href="https://signoz.io/docs/ai/signoz-mcp-server/" rel="noopener noreferrer"&gt;MCP server&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://signoz.io/changelog/2026-05-01-introducing-the-signoz-mcp-server-r5iwnkpxtsz88akwt6abqddn/" rel="noopener noreferrer"&gt;announcement&lt;/a&gt;, &lt;a href="https://github.com/SigNoz/signoz-mcp-server" rel="noopener noreferrer"&gt;source&lt;/a&gt;). MCP (Model Context Protocol) is an open standard that exposes a tool surface to LLMs — and here it means an AI assistant can query your observability data in natural language: &lt;em&gt;"show me the slowest traces in the last hour,"&lt;/em&gt; &lt;em&gt;"which model calls had the highest token cost today,"&lt;/em&gt; even &lt;em&gt;"build an incident dashboard for the checkout service."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sit with the implication. The traditional loop is &lt;strong&gt;human → dashboard → eyeballs → hypothesis&lt;/strong&gt;. With an MCP-queryable backend, an &lt;em&gt;agent&lt;/em&gt; can drive that loop: an LLM can pull the offending trace, read the span attributes, correlate the logs, and construct the dashboard programmatically. Observability stops being a place you go to look and becomes an API your automation can reason over. SigNoz has already demoed a &lt;a href="https://signoz.io/blog/monitoring-langchain-agent-querying-signoz-mcp-server/" rel="noopener noreferrer"&gt;LangChain agent that queries its MCP server&lt;/a&gt; — closing the loop from "AI you observe" to "AI that observes."&lt;/p&gt;

&lt;h2&gt;
  
  
  The thesis I'm taking into the hackathon: close the loop
&lt;/h2&gt;

&lt;p&gt;Here's where I think this goes, and it's the project I'm building for Track 01.&lt;/p&gt;

&lt;p&gt;Today, observability stops at &lt;em&gt;detection&lt;/em&gt;. You get an alert; a human does the diagnosis and the fix. But every ingredient for automating the diagnosis now exists: standardized &lt;code&gt;gen_ai.*&lt;/code&gt; telemetry in SigNoz, and an MCP server that lets an agent read it. So why stop at the alert?&lt;/p&gt;

&lt;p&gt;The architecture I'm prototyping — call it &lt;strong&gt;Sentinel&lt;/strong&gt; — runs the full loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Instrument&lt;/strong&gt; a target agent with OTel GenAI conventions → telemetry lands in SigNoz.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detect&lt;/strong&gt; the agent-specific failure classes traditional monitoring misses — cost runaways, tool-call loops, latency cliffs, and &lt;em&gt;silent quality regressions&lt;/em&gt; via eval-scores-as-span-attributes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diagnose&lt;/strong&gt; by dispatching an investigation agent that queries SigNoz through MCP, isolates the offending span, and reconstructs the causal chain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Act&lt;/strong&gt; — auto-generate an incident dashboard, emit a plain-English root-cause report pinned to the exact failing span, and apply or recommend a remediation (e.g., a circuit breaker on the runaway loop).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An AI that watches other AIs, and doesn't just tell you what broke — it tells you &lt;em&gt;why&lt;/em&gt; and moves to fix it. That's the demo I want to put in front of the judges.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you're shipping agents, start here
&lt;/h2&gt;

&lt;p&gt;You don't need a hackathon as an excuse, and you don't need to boil the ocean. The highest-leverage sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add OpenTelemetry to your agent&lt;/strong&gt; using the GenAI semantic conventions — auto-instrument first, then hand-instrument the spans that matter (tool calls, retrievals).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attach cost and tokens as span attributes&lt;/strong&gt;, and derive a &lt;code&gt;cost_usd&lt;/code&gt; field so spend is a first-class, alertable signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Point OTLP at SigNoz&lt;/strong&gt; — Docker to start, a few commands — and read exactly one real end-to-end conversation trace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attach an eval score&lt;/strong&gt; to your answer spans so you can alert on quality &lt;em&gt;drift&lt;/em&gt;, not just errors.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The scariest property of a black box isn't that it fails. It's that it fails &lt;em&gt;quietly&lt;/em&gt; — and you learn about it from a user, or from your invoice, instead of from your telemetry.&lt;/p&gt;

&lt;p&gt;Instrument the box. Then crack it open.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building this for the Agents of SigNoz hackathon (July 20–26, 2026). Follow along or come talk shop in the &lt;a href="https://signoz.io/" rel="noopener noreferrer"&gt;SigNoz Slack&lt;/a&gt; community. More once the code starts shipping.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>observability</category>
      <category>opentelemetry</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
