<?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: AVISH MANIAR</title>
    <description>The latest articles on DEV Community by AVISH MANIAR (@avish_maniar_0b4d32e2c2ad).</description>
    <link>https://dev.to/avish_maniar_0b4d32e2c2ad</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%2F4035672%2F06b68169-eff3-4f66-a414-1222ed6b77c2.png</url>
      <title>DEV Community: AVISH MANIAR</title>
      <link>https://dev.to/avish_maniar_0b4d32e2c2ad</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/avish_maniar_0b4d32e2c2ad"/>
    <language>en</language>
    <item>
      <title>Observing a Multi-Agent LLM System: What I Learned Instrumenting 8 Agents with OpenTelemetry</title>
      <dc:creator>AVISH MANIAR</dc:creator>
      <pubDate>Sat, 18 Jul 2026 19:22:21 +0000</pubDate>
      <link>https://dev.to/avish_maniar_0b4d32e2c2ad/observing-a-multi-agent-llm-system-what-i-learned-instrumenting-8-agents-with-opentelemetry-1176</link>
      <guid>https://dev.to/avish_maniar_0b4d32e2c2ad/observing-a-multi-agent-llm-system-what-i-learned-instrumenting-8-agents-with-opentelemetry-1176</guid>
      <description>&lt;p&gt;I spent three days trying to figure out why my multi-agent LLM workflow was taking 15 minutes when it should take 5. Without observability, I was stuck adding print statements and rerunning the entire pipeline. That's when I decided to properly instrument it with OpenTelemetry and SigNoz.&lt;/p&gt;

&lt;p&gt;This post walks through how I added traces, logs, and metrics to a Flask app that orchestrates 8 LLM agents using Ollama and Mistral. More importantly, it covers what I learned about monitoring agent-based systems that you won't find in the standard OpenTelemetry docs.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem: A Black Box LLM Pipeline
&lt;/h3&gt;

&lt;p&gt;My user story automation app processes requirements documents through 8 specialized agents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RequirementsAgent extracts functionalities&lt;/li&gt;
&lt;li&gt;EpicExtractorAgent creates epics in batches&lt;/li&gt;
&lt;li&gt;EpicRefinerAgent polishes the output&lt;/li&gt;
&lt;li&gt;StoryGeneratorAgent writes user stories&lt;/li&gt;
&lt;li&gt;TestCaseAgent generates test scenarios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The workflow runs for 10-15 minutes per document, making debug cycles painful. When something went wrong, I had no idea which agent was slow or failing. I needed to see inside the pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up OpenTelemetry: The Basics Plus Agent Tracing
&lt;/h3&gt;

&lt;p&gt;The standard Flask instrumentation gives you HTTP endpoints and database queries, but it doesn't know anything about your agents. Here's what I added to &lt;code&gt;src/backend/otel_config.py&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;init_telemetry&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;span class="n"&gt;SIGNOZ_BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SIGNOZ_OTLP_ENDPOINT&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;http://localhost:4318&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Resource&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;service.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;user-story-automation&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;service.version&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;1.0.0&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;deployment.environment&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;development&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="c1"&gt;# Traces: Track agent operations and workflow execution
&lt;/span&gt;    &lt;span class="n"&gt;trace_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;resource&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;trace_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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SIGNOZ_BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v1/traces&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;trace_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="n"&gt;trace_exporter&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;trace_provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Logs: Centralized logging with context
&lt;/span&gt;    &lt;span class="n"&gt;logger_provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LoggerProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;log_exporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OTLPLogExporter&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SIGNOZ_BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v1/logs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;logger_provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_log_record_processor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BatchLogRecordProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_exporter&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;set_logger_provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger_provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Metrics: Custom histograms for agent performance
&lt;/span&gt;    &lt;span class="n"&gt;metric_exporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OTLPMetricExporter&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SIGNOZ_BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v1/metrics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;meter_provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MeterProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;metric_readers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;PeriodicExportingMetricReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metric_exporter&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_meter_provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;meter_provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Auto-instrumentation
&lt;/span&gt;    &lt;span class="nc"&gt;FlaskInstrumentor&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;span class="nc"&gt;SQLAlchemyInstrumentor&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nc"&gt;RequestsInstrumentor&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The auto-instrumentation got me basic traces, but here's the first gotcha: Flask traces showed me &lt;code&gt;/process-document&lt;/code&gt; taking 674 seconds, but that single span told me nothing about which agent was slow. I needed manual spans.&lt;/p&gt;

&lt;h3&gt;
  
  
  Manual Spans: The Only Way to See Agent Operations
&lt;/h3&gt;

&lt;p&gt;I wrapped each agent call with a manual span in &lt;code&gt;src/backend/routes/api.py&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="c1"&gt;# Requirements extraction
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;create_manual_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.requirements_extraction&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;agent.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;RequirementsAgent&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;document.length&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extracted_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt;
    &lt;span class="n"&gt;requirements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;refine_doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extract_functionarity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extracted_text&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;mode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Epic extraction with batch tracking
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;batch_idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batches&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;create_manual_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.epic_extraction_batch&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;agent.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;EpicExtractorAgent&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;batch.index&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;batch_idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;batch.total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batches&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;batch.size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}):&lt;/span&gt;
        &lt;span class="n"&gt;batch_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_epics_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch_text&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;mode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where I learned something important: batch operations need special handling. Without tracking &lt;code&gt;batch.index&lt;/code&gt; and &lt;code&gt;batch.total&lt;/code&gt;, I couldn't tell if batch 2 was consistently slower than batches 1 and 3 (spoiler: it was).&lt;/p&gt;

&lt;h3&gt;
  
  
  What The Data Revealed
&lt;/h3&gt;

&lt;p&gt;Once the instrumentation was running, I built dashboards using SigNoz's MCP integration. Here's what I actually learned from the data.&lt;/p&gt;

&lt;h4&gt;
  
  
  Finding the Bottleneck
&lt;/h4&gt;

&lt;p&gt;The main dashboard showed average workflow time at 13.28 minutes across 3 test runs. Breaking it down by agent revealed the issue:&lt;br&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%2Fe0vh2q74dlfoih2in30t.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%2Fe0vh2q74dlfoih2in30t.png" alt="SigNoz dashboard showing 3 total workflow requests, 13.28 minute average workflow time, 23 total agent operations, and 0 errors, with line charts for Agent Execution Time P95 and Workflow Requests Over Time&lt;br&gt;
" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Epic Extraction Batch: 11 operations, 2.06 minutes average&lt;/li&gt;
&lt;li&gt;Requirements Extraction: 3 operations, 2.68 minutes average&lt;/li&gt;
&lt;li&gt;Epic Refinement: 2 operations, 5.77 minutes average&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Epic Refinement was taking 3x longer per operation than anything else. That's where I needed to optimize.&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%2Fh43rf2rmp5ybxh7qnoot.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%2Fh43rf2rmp5ybxh7qnoot.png" alt="Table and donut chart showing Agent Performance Breakdown: epic_extraction_batch (11 ops, 2.06 min avg), test_case_generation_batch (7 ops, 1.21 min avg), requirements_extraction (3 ops, 2.68 min avg), and epic_refinement (2 ops, 5.77 min avg, highest P95 at 7.49 min)" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pie chart showed epic extraction batch consuming 48% of total operations, but the table showed refinement had the worst P95 latency at 7.49 minutes.&lt;/p&gt;
&lt;h4&gt;
  
  
  P99 vs P50: Why Percentiles Matter
&lt;/h4&gt;

&lt;p&gt;I added a Performance Bottlenecks dashboard to track percentile latency. This is where things got interesting:&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%2Fmf8986jg02t0c1jzer1z.jpg" 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%2Fmf8986jg02t0c1jzer1z.jpg" alt="P99 vs P50 latency chart showing P99 spiking to 16.67 minutes while P50 stays near 3.33 minutes, alongside a Time Distribution by Agent donut chart showing epic_extraction_batch consuming 22.63 minutes total" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The graph shows P99 latency spiking to 16.67 minutes while P50 stayed around 3.33 minutes. That's a 5x difference. This told me the LLM (Ollama running Mistral locally) had massive variance in response time, probably due to resource contention.&lt;/p&gt;

&lt;p&gt;The time distribution pie chart revealed another insight: epic extraction batch consumes 45% (22.83 mins) of total workflow time across all agents. Even though individual operations are fast (2.06 min average), the sheer volume makes it the biggest time sink.&lt;/p&gt;
&lt;h4&gt;
  
  
  Error Tracking by Agent
&lt;/h4&gt;

&lt;p&gt;I built an Advanced Analytics dashboard with per-agent error rates:&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%2Fl5537p9rua06d6kqpbbm.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%2Fl5537p9rua06d6kqpbbm.png" alt="Advanced Analytics dashboard with four panels: Latency Breakdown showing P50/P90/P95/P99 with P99 spiking to 13.33 minutes, Agent Success Rate Over Time near 100%, Error Rate by Agent Type showing a 4% spike for epic_refinement, and Workflow Completion Rate above the 90% critical threshold" width="799" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The error rate chart showed epic_refinement had a 4% error spike on July 17th, then recovered. Without this view, I would never have known there was a transient issue that resolved itself.&lt;/p&gt;

&lt;p&gt;The success rate panel showed 100% overall, but the multi-percentile latency breakdown (P50/P90/P95/P99 on one chart) made it obvious where the variance was coming from.&lt;/p&gt;
&lt;h3&gt;
  
  
  Using SigNoz MCP for Dashboard Creation
&lt;/h3&gt;

&lt;p&gt;Instead of clicking through the UI, I used SigNoz's Model Context Protocol (MCP) server with Claude. I configured it in &lt;code&gt;~/.claude.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"signoz"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8000/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"SIGNOZ-API-KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-api-key-here"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I could create dashboards by describing what I wanted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Me: "Create a dashboard showing agent performance with P95 latency"
Assistant: *Uses signoz_create_dashboard MCP tool*
Result: Complete dashboard with 10 widgets created in seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was 10x faster than clicking through the UI. I created all 5 dashboards this way, including complex multi-query widgets for the Advanced Analytics dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alerts: Catching Issues Before They Matter
&lt;/h3&gt;

&lt;p&gt;I set up 7 alerts total, but two were particularly useful:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Agent Performance Anomaly Detection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This uses z-score anomaly detection (2 standard deviations) on the &lt;code&gt;agent_duration_seconds&lt;/code&gt; metric with hourly seasonality. It caught a case where epic refinement suddenly took 2x normal time due to LLM resource contention. A static threshold would have missed this gradual degradation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Workflow Completion Rate&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I track &lt;code&gt;agent.requirements_extraction&lt;/code&gt; (the first operation in every workflow) and calculate success rate with a formula query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(successful_operations / total_operations) * 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The alert fires if completion rate drops below 90%. This was tricky to set up because I initially filtered for &lt;code&gt;/process-document&lt;/code&gt; span name, which doesn't exist. I had to search traces to find the actual span names being generated.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Learned (The Stuff Not In The Docs)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Manual Spans Are Non-Negotiable for Agent Systems&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Auto-instrumentation is great for standard web apps, but it can't see your domain logic. Every agent operation needs an explicit span with relevant attributes like &lt;code&gt;agent.name&lt;/code&gt;, &lt;code&gt;batch.index&lt;/code&gt;, and &lt;code&gt;batch.size&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Batch Operations Need Tracking&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're processing items in batches, track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;batch.index&lt;/code&gt;: Which batch is this? (1, 2, 3)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;batch.total&lt;/code&gt;: How many batches total?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;batch.size&lt;/code&gt;: How many items in this batch?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This let me discover that batch 2 was consistently 20% slower than batches 1 and 3, which pointed to a data distribution problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. P95/P99 Reveals What Average Hides&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Average latency was 3.33 minutes, but P99 was 13.33 minutes. If I'd only looked at averages, I would have thought performance was fine. The high percentiles exposed the LLM variance issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Resource Attributes Speed Up Queries&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I tagged all data with &lt;code&gt;service.name&lt;/code&gt; as a resource attribute (not a span attribute). SigNoz queries filter on resource attributes first, making every query significantly faster. This is in the docs but easy to miss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. MCP Integration Changed My Workflow&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Being able to describe dashboards in natural language ("show me P50/P90/P95/P99 on one chart") instead of configuring query builders saved hours. The MCP server turned dashboard creation from a chore into a conversation.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Results
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before instrumentation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No visibility into which agent was slow&lt;/li&gt;
&lt;li&gt;Had to add print statements and rerun 15-minute workflows to debug&lt;/li&gt;
&lt;li&gt;Unknown if errors were happening in production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;After instrumentation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identified Epic Refinement as the bottleneck (5.77 min average)&lt;/li&gt;
&lt;li&gt;Discovered batch 2 consistently slower than batches 1 and 3&lt;/li&gt;
&lt;li&gt;Caught transient 4% error spike that self-resolved&lt;/li&gt;
&lt;li&gt;P99 latency tracking revealed 5x variance in LLM performance&lt;/li&gt;
&lt;li&gt;Alerts catch failures within 1 minute&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most importantly, I now have actionable optimization targets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Epic Refinement could be parallelized to reduce the 5.77 min bottleneck&lt;/li&gt;
&lt;li&gt;Investigate why batch 2 is slower (probably contains more complex epics)&lt;/li&gt;
&lt;li&gt;Consider LLM response caching to reduce P99 variance&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Instrumenting a multi-agent LLM system taught me that observability isn't just about adding traces. It's about choosing the right attributes to track (like batch index), measuring the right percentiles (P95/P99, not just average), and building dashboards that answer specific questions ("which agent is slow?" not "here's all the data").&lt;/p&gt;

&lt;p&gt;The combination of OpenTelemetry's flexibility and SigNoz's MCP integration made this project possible in a weekend. If you're building with LLM agents, start with manual spans around each agent operation and work from there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack:&lt;/strong&gt; Flask, LangChain, Ollama (Mistral), OpenTelemetry, SigNoz Foundry&lt;br&gt;
&lt;strong&gt;Dashboards:&lt;/strong&gt; 4 dashboards, 7 alerts, all created via MCP&lt;/p&gt;

</description>
      <category>opentelemetry</category>
      <category>signoz</category>
    </item>
  </channel>
</rss>
