<?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: Tyson</title>
    <description>The latest articles on DEV Community by Tyson (@tyson1234).</description>
    <link>https://dev.to/tyson1234</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%2F3949478%2F2163b305-9121-448a-874b-7e2cb936a021.jpg</url>
      <title>DEV Community: Tyson</title>
      <link>https://dev.to/tyson1234</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tyson1234"/>
    <language>en</language>
    <item>
      <title>From a Live Voice Prompt to a Safe Canvas Draft: Observing Sidechalk End to End with SigNoZ</title>
      <dc:creator>Tyson</dc:creator>
      <pubDate>Mon, 27 Jul 2026 02:35:03 +0000</pubDate>
      <link>https://dev.to/tyson1234/from-a-live-voice-prompt-to-a-safe-canvas-draft-observing-sidechalk-end-to-end-with-signoz-3mm5</link>
      <guid>https://dev.to/tyson1234/from-a-live-voice-prompt-to-a-safe-canvas-draft-observing-sidechalk-end-to-end-with-signoz-3mm5</guid>
      <description>&lt;p&gt;&lt;strong&gt;Project:&lt;/strong&gt; &lt;a href="https://github.com/ladiesmans217/SideChalk" rel="noopener noreferrer"&gt;Sidechalk on GitHub&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Hackathon:&lt;/strong&gt; WeMakeDevs Agents of SigNoZ&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Track:&lt;/strong&gt; Track 01 — AI &amp;amp; Agent Observability&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Video:&lt;/strong&gt; &lt;a href="https://youtu.be/_Ihun7ITPOQ" rel="noopener noreferrer"&gt;Watch the Sidechalk x SigNoZ demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my final live demo, I asked Sidechalk to inspect an inclined-plane diagram, reason about its missing force, and add one yellow friction arrow as a draft. The application saw the authorized canvas, kept the first two answers conversational, and created a visible proposal only after the third instruction. The learner could inspect, accept, or reject it; an accepted change could still be undone.&lt;/p&gt;

&lt;p&gt;The SigNoZ command center captured the same run. It showed token series for both &lt;code&gt;gpt-realtime-2.1&lt;/code&gt; and &lt;code&gt;gpt-5.6-terra&lt;/code&gt;, structured-provider duration, complete agent-turn duration, estimated model cost, one fallback event, and background-worker activity. A second provisioned dashboard covers proposal validation, decisions, safety blocks, and authoritative realtime delivery.&lt;/p&gt;

&lt;p&gt;Getting there exposed a real blind spot. In an earlier run, the learner-facing workflow succeeded while most AI panels stayed empty and only worker telemetry appeared. The problem was not SigNoZ or metric-export delay: I had instrumented the typed assistant route, but the live WebRTC sideband followed a different lifecycle.&lt;/p&gt;

&lt;p&gt;Fixing that gap forced me to answer a harder question than “Did the model respond?”: &lt;strong&gt;Can I observe the complete agent path that the learner actually used?&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What Sidechalk is trying to make safe
&lt;/h2&gt;

&lt;p&gt;Sidechalk is a collaborative living notebook. A learner can draw on an Excalidraw canvas, work in a document, import media, and speak or type to an assistant. The assistant can inspect only the authorized Sidechalk page and propose arrows, handwriting, circles, movement, comments, citations, replacement, or recoverable deletion.&lt;/p&gt;

&lt;p&gt;Its central invariant is &lt;strong&gt;draft first&lt;/strong&gt;. Model output cannot directly mutate the canonical Yjs document. AI marks stay visually distinct until the learner explicitly accepts them, and accepted work has a deterministic inverse for undo.&lt;/p&gt;

&lt;p&gt;For the demo I use an incomplete free-body diagram: a block on an incline with &lt;code&gt;mg&lt;/code&gt;, &lt;code&gt;N&lt;/code&gt;, and a velocity arrow down the slope, but no friction force. The conversation is deliberately split into three turns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;“What can you see on the board? Do not change anything yet.”&lt;/li&gt;
&lt;li&gt;“The velocity arrow shows the block moving down the slope. In which direction should friction act? Do not draw yet.”&lt;/li&gt;
&lt;li&gt;“Add one yellow arrow up the incline and write &lt;code&gt;friction&lt;/code&gt; beside it. Change nothing else, and keep it as a draft.”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first two turns test fresh visual context, conversation memory, and physics reasoning without authorizing a surface operation. Only the third turn authorizes a draft.&lt;/p&gt;

&lt;p&gt;I arrived at this scenario after another real limitation: an imported PNG is one flattened canvas element. Asking the agent to delete one arrow baked into that image is not the same as deleting an Excalidraw vector element. Instead of hiding that mismatch, I changed the task to a useful additive correction that the surface contract can perform honestly.&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%2Fq9ndg3bl3u88ef0qtr7u.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%2Fq9ndg3bl3u88ef0qtr7u.png" alt="Sidechalk canvas showing a block on an inclined plane with gravity, normal force, and velocity down the slope, but no friction arrow." width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Before the correction: the diagram shows the block moving down the slope, but the friction force is missing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbgmno8s93ltuj9d2qits.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%2Fbgmno8s93ltuj9d2qits.png" alt="Sidechalk showing a yellow friction arrow pointing up the incline as an AI-generated draft, alongside controls to accept or reject the proposal." width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;After the voice instruction: Sidechalk draws friction up the incline as a private proposal; the learner still controls accept or reject.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  One editing turn crosses more than one model system
&lt;/h2&gt;

&lt;p&gt;The system is a TypeScript monorepo with Next.js and Excalidraw on the web, Fastify for authoritative APIs, Supabase for authentication and durable state, Yjs/Hocuspocus for collaboration, Redis/BullMQ for distributed work, and a constrained Python STEM verifier. OpenAI handles live voice and multimodal proposals; provider adapters also exist for Google and Anthropic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Next.js / Excalidraw
        |
        v
Fastify API ----&amp;gt; authorized surface context
        |-------&amp;gt; OpenAI Realtime voice response
        |-------&amp;gt; structured proposal model
        |-------&amp;gt; verifier / citation checks
        |-------&amp;gt; proposal validation and persistence
        |-------&amp;gt; explicit accept, reject, or undo
        v
Yjs + Hocuspocus ----&amp;gt; collaborators

Instrumented services --OTLP/HTTP--&amp;gt; self-hosted SigNoZ
SigNoZ MCP --read only-----&amp;gt; Sidechalk Incident Copilot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Node API, realtime, and worker services export traces, metrics, and selected logs. The Python verifier exports traces, while the Next.js service uses &lt;code&gt;@vercel/otel&lt;/code&gt;. Automatic instrumentation captured HTTP, fetch, and supported library activity, but it could not name the product boundaries I cared about. I added manual spans at each trust boundary:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Boundary&lt;/th&gt;
&lt;th&gt;Spans&lt;/th&gt;
&lt;th&gt;What it answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Learner turn&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sidechalk.ai.turn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Did the complete typed or voice/proposal turn finish?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authorized context&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sidechalk.ai.context.build&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Did the model receive the current permitted surface?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Provider&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sidechalk.gen_ai.invoke&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which model call was slow, cancelled, or failed?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verification&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sidechalk.verifier.check&lt;/code&gt;, &lt;code&gt;sidechalk.citation.verify&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Did deterministic or source checks run?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Draft safety&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sidechalk.proposal.validate&lt;/code&gt;, &lt;code&gt;sidechalk.proposal.persist&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Was the model output valid and stored only as a draft?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human control&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sidechalk.proposal.decide&lt;/code&gt;, &lt;code&gt;sidechalk.proposal.undo&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;What happened after the learner explicitly decided?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Collaboration&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sidechalk.realtime.authoritative_update&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Was the accepted binary update published?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background work&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sidechalk.worker.job&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Did asynchronous processing continue outside the request?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That manual instrumentation follows &lt;a href="https://signoz.io/docs/instrumentation/javascript/nodejs-manual-instrumentation/" rel="noopener noreferrer"&gt;SigNoZ's guidance for tracing business operations&lt;/a&gt; that framework instrumentation cannot explain. It also gives each failure a useful boundary: provider, validator, persistence, human decision, or realtime publication.&lt;/p&gt;

&lt;h2&gt;
  
  
  The blind spot: typed turns and live voice were different systems
&lt;/h2&gt;

&lt;p&gt;My typed &lt;code&gt;/turns&lt;/code&gt; route already wrapped the complete workflow and recorded custom metrics. This shortened excerpt keeps the real span and metric names while omitting unrelated validation and streaming code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;withSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sidechalk.ai.turn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sidechalk.channel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;typed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sidechalk.ai.mode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canonical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;withSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sidechalk.ai.context.build&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sidechalk.surface.type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;productionRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepareTurn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;conversationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;withSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sidechalk.gen_ai.invoke&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gen_ai.operation.name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;createProviderResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;adapters&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;withSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sidechalk.proposal.validate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="nx"&gt;validateProposal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;withSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sidechalk.proposal.persist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;productionRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persistAssistant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reservationId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Live voice does not remain inside one HTTP request. A browser sends audio through WebRTC, a server-side sideband receives provider events, the browser captures the current authorized page, and tool calls may pause while a draft is rendered and acknowledged. The original implementation persisted those events correctly, but never called &lt;code&gt;recordAiTurn()&lt;/code&gt; and never created the equivalent root/provider spans.&lt;/p&gt;

&lt;p&gt;The pre-fix dashboard screenshot made the gap obvious:&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%2Fo1vwxczoix0n687iintd.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%2Fo1vwxczoix0n687iintd.png" alt="Pre-fix Sidechalk AI Command Center with AI-turn, latency, fallback, and model-cost panels showing No Data." width="800" height="424"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Before the fix: successful live-voice activity was absent from the AI panels because the Realtime sideband had not yet been instrumented.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Waiting longer could never repair an uninstrumented path.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fixing an asynchronous Realtime trace
&lt;/h2&gt;

&lt;p&gt;The fix was not to add a timer around the endpoint that triggers &lt;code&gt;response.create&lt;/code&gt;; that request ends before the asynchronous provider lifecycle does. I had to keep a turn span alive across sideband events and create a child provider span for each Realtime response. The following excerpt is shortened, but the lifecycle and attributes are taken from the running implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeTurn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeTurn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;startedAtMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;realtimeTracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sidechalk.ai.turn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;safeAttributes&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sidechalk.channel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;voice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sidechalk.ai.mode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sidechalk.surface.type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sidechalk.visual.available&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visualAvailable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;inputTokens&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="na"&gt;outputTokens&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="na"&gt;responseCount&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="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;otelContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeTurn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;startedAtMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;realtimeTracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sidechalk.gen_ai.invoke&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;safeAttributes&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gen_ai.operation.name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;realtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gen_ai.provider.name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gen_ai.request.model&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENAI_REALTIME_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;response.done&lt;/code&gt; arrives, Sidechalk extracts only numeric usage metadata, ends the provider span, and either continues through a tool call or closes the learner turn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;completedFunctionCalls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;realtimeResponseUsage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;activeTurn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputTokens&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;activeTurn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputTokens&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputTokens&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;activeResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;recordAiTurn&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;durationMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;activeTurn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startedAtMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENAI_REALTIME_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;activeTurn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;outputTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;activeTurn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;activeTurn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The surface-proposal tool is nested under that voice turn. Its structured model call now records provider/model attributes, token usage, estimated cost when the model exists in the checked-in price table, proposal validation, and persistence. Cancelled speech, provider errors, and socket failure close spans explicitly instead of leaking unfinished traces.&lt;/p&gt;

&lt;p&gt;This was the lesson I could not get from an installation guide: &lt;strong&gt;instrument the lifecycle, not the endpoint that starts it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The next fresh voice proposal produced the signals that were missing before:&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%2Fkjd79q7my7591whd59k1.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%2Fkjd79q7my7591whd59k1.png" alt="Sidechalk AI Command Center with live volume, fallback, and model-cost data" width="799" height="357"&gt;&lt;/a&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%2Fv46wat9aflzgq91tbxnj.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%2Fv46wat9aflzgq91tbxnj.png" alt="Sidechalk provider duration, token, worker, and workflow-duration telemetry" width="800" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is not an empty dashboard: the selected window contains fallback, cost, worker, provider-duration, token, and complete agent-turn signals. The separate metric-histogram P95 panel still says &lt;code&gt;No Data&lt;/code&gt; in this capture, while the trace-derived provider and agent-turn P95 panels are populated. I left that visible rather than presenting a cleaner but misleading screenshot.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9i45p8pd0cc93fqoh7fk.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%2F9i45p8pd0cc93fqoh7fk.png" alt="SigNoZ trace waterfall for a Sidechalk voice turn showing the root AI-turn span, voice proposal, model invocation, context building, and a failed provider request." width="800" height="392"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The trace connects the live voice turn to proposal generation and exposes the failed provider request instead of hiding it behind the successful overall workflow.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Metrics and dashboards built around trust
&lt;/h2&gt;

&lt;p&gt;The Node SDK exports traces, metrics, and selected correlated logs over OTLP/HTTP. Metrics use a 15-second periodic reader. I record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI turn volume and end-to-end latency&lt;/li&gt;
&lt;li&gt;provider invocation duration and fallback&lt;/li&gt;
&lt;li&gt;input/output tokens and estimated model cost&lt;/li&gt;
&lt;li&gt;proposal operation, validation, decision, and undo outcomes&lt;/li&gt;
&lt;li&gt;verifier and citation outcomes&lt;/li&gt;
&lt;li&gt;queue wait and worker activity&lt;/li&gt;
&lt;li&gt;canonical-safety invariant violations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two idempotently provisioned dashboards contain eight panels each:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sidechalk AI Command Center&lt;/strong&gt; covers volume, P95 latency, provider duration, fallback, tokens, estimated cost, worker activity, and complete agent-turn duration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sidechalk Trust and Proposal Safety&lt;/strong&gt; covers proposal operations/outcomes, verifier and citation activity, validation, invariant blocks, authoritative realtime delivery, and errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Four enabled rules watch provider reliability, learner-facing latency, model budget, and canonical safety. A tested local webhook accepts alert envelopes. I do not claim a production firing-and-recovery canary; this article describes my verified local stack.&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%2Fond228mlm4lrd0d7gq7s.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%2Fond228mlm4lrd0d7gq7s.png" alt="Sidechalk Trust and Proposal Safety dashboard showing proposal generation, acceptance and rejection, validation, and realtime authoritative delivery" width="800" height="356"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Proposal creation, human decisions, validation, and shared delivery are monitored as separate trust stages.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  “No Data” has three different meanings
&lt;/h2&gt;

&lt;p&gt;This project produced three superficially similar empty states:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Expected absence:&lt;/strong&gt; no fallback or invariant violation occurred. Empty is healthy evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing instrumentation:&lt;/strong&gt; in the pre-fix run, worker data arrived while the voice AI path stayed empty. The application path was invisible until I instrumented its sideband lifecycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broken query infrastructure:&lt;/strong&gt; a P95 query failed because my generated ClickHouse configuration did not load &lt;code&gt;histogramQuantile&lt;/code&gt; correctly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The third case was unusually specific. The generated executable-function configuration referenced the wrong filename pattern, and the UDF's &lt;code&gt;quantile&lt;/code&gt; parameter used the wrong type. I corrected the local generated files, restarted only ClickHouse, and verified the function directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;signoz-telemetrystore-clickhouse-0-0 &lt;span class="se"&gt;\&lt;/span&gt;
  clickhouse-client &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"select name from system.functions where name = 'histogramQuantile'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The distinction matters. I should not manufacture a fallback event to make a panel attractive, but I also should not call an uninstrumented voice route healthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproducing my local path
&lt;/h2&gt;

&lt;p&gt;I used Windows 11, Docker Desktop, WSL2, Node 24, and Foundry v0.2.14. Foundry generated the self-hosted SigNoZ stack from the checked-in &lt;code&gt;casting.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;foundryctl gauge &lt;span class="nt"&gt;-f&lt;/span&gt; casting.yaml
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;The standard container services use SigNoZ UI port &lt;code&gt;8080&lt;/code&gt; and MCP port &lt;code&gt;8000&lt;/code&gt;. Those ports were already occupied on my machine, so my current host mappings are &lt;code&gt;18080&lt;/code&gt; for the UI and &lt;code&gt;18000&lt;/code&gt; for MCP. OTLP/HTTP remains &lt;code&gt;4318&lt;/code&gt;, and the Sidechalk verifier uses &lt;code&gt;8002&lt;/code&gt;. Check your own port owners instead of copying my remap blindly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OTEL_ENABLED=true
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318
OTEL_ENVIRONMENT=development
OTEL_LOGS_ENABLED=true
OTEL_CAPTURE_AI_CONTENT=false
OTEL_HASH_SALT=&amp;lt;private-random-value-at-least-16-characters&amp;gt;

SIGNOZ_UI_URL=http://localhost:18080
SIGNOZ_URL=http://localhost:18080
SIGNOZ_MCP_URL=http://127.0.0.1:18000/mcp
SIGNOZ_API_KEY=&amp;lt;service-account-key&amp;gt;
SIGNOZ_ALERT_CHANNEL=Local Webhook Receiver
OBSERVABILITY_ADMIN_USER_IDS=&amp;lt;my-Supabase-user-UUID&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the repository root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ports:preflight&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;compose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;verifier&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;signoz:provision&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;signoz:provision&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;signoz:verify&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The verifier checks the expected dashboard names, Query Builder version, panel IDs and counts, enabled alert routing, webhook-channel identity, and three representative telemetry queries. It deliberately failed when &lt;code&gt;sidechalk.ai.turn.duration.bucket&lt;/code&gt; had last been seen four hours earlier. That failure was useful: provisioning existed, but fresh traffic had not proved the path.&lt;/p&gt;

&lt;p&gt;After a new agent turn, this filter locates the workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service.name = 'sidechalk-api' AND name = 'sidechalk.ai.turn'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Incident investigation without write access
&lt;/h2&gt;

&lt;p&gt;Sidechalk also includes an Incident Copilot backed by the official &lt;a href="https://github.com/SigNoz/signoz-mcp-server" rel="noopener noreferrer"&gt;SigNoZ MCP server&lt;/a&gt;. The MCP server can expose metrics, traces, logs, alerts, dashboards, and services; Sidechalk deliberately allowlists only read operations.&lt;/p&gt;

&lt;p&gt;Each investigation is limited to four calls, 15 seconds per call, 45 seconds overall, and a maximum six-hour time window. Evidence cards retain the SigNoZ tool, time range, service, and deep link. Create, update, delete, and unrestricted execution tools are excluded.&lt;/p&gt;

&lt;p&gt;A development-only Fault Lab can inject bounded provider delay/failure, verifier contradiction, or worker delay. Faults are labeled &lt;code&gt;sidechalk.demo.fault.*&lt;/code&gt;, stay in memory, expire automatically, and are forbidden in production. Synthetic evidence must announce that it is synthetic.&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%2Fsdf6t0gtult9phkm54ax.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%2Fsdf6t0gtult9phkm54ax.png" alt="Sidechalk Incident Copilot showing a read-only SigNoZ MCP investigation with metric, trace, and log evidence cards" width="799" height="534"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The copilot queries read-only SigNoZ evidence and refuses to invent a root cause when no correlated fault or error is found.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Observability without copying the notebook
&lt;/h2&gt;

&lt;p&gt;The most important telemetry decision was what not to attach to the AI-specific spans, metrics, and logs. Sidechalk's manual instrumentation excludes prompts, responses, handwriting, page images, audio, transcripts, authorization data, and raw user/board IDs. Manually recorded error spans keep only an error class and a fixed content-free message. IDs intentionally used for AI-workflow correlation are HMAC-hashed before export.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;forbiddenKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(?:&lt;/span&gt;&lt;span class="sr"&gt;prompt|response|content|body|text|audio|transcript|document|authorization|cookie|secret|api.&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;key|access.&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;token|email|user.&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;name&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/iu&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;safeAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SafeAttributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}):&lt;/span&gt; &lt;span class="nx"&gt;Attributes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;forbiddenKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;value&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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those custom AI signals contain operation names, duration, provider/model, numeric token totals, proposal category/result, and safe correlations—not the learner's notebook content. I still treat generic auto-instrumentation as a separate privacy surface and avoid placing sensitive values in URLs or captured headers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell myself before building this
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Trace the path users actually take.&lt;/strong&gt; A beautifully instrumented typed route did nothing for my live-voice demonstration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instrument product invariants.&lt;/strong&gt; HTTP success cannot prove that an AI draft remained non-canonical.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Async agents outlive request spans.&lt;/strong&gt; WebRTC and sideband tools need explicit context propagation and lifecycle closure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat No Data as a diagnosis, not a cosmetic problem.&lt;/strong&gt; Expected absence, missing telemetry, and query failure are different states.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify provisioning with live queries.&lt;/strong&gt; A successful create/update response does not prove that panels can read real signals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep investigation agents least-privileged.&lt;/strong&gt; Evidence access does not require configuration write access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter before export.&lt;/strong&gt; Redaction after learner content reaches the backend is already too late.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Sidechalk's promise is not merely that an AI can answer a physics question. It is that the agent can see authorized work, reason across a conversation, propose a spatial correction, wait for explicit approval, publish exactly one canonical update, and undo it safely.&lt;/p&gt;

&lt;p&gt;SigNoZ made that promise testable. The temporary empty state showed where my first implementation was blind; the populated command center now shows that the real live-voice path is observable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI can work on the page, but it cannot silently control the page—and now the path enforcing that rule is observable too.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ladiesmans217/SideChalk" rel="noopener noreferrer"&gt;Sidechalk source code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/_Ihun7ITPOQ" rel="noopener noreferrer"&gt;Video demonstration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.wemakedevs.org/hackathons/signoz" rel="noopener noreferrer"&gt;Agents of SigNoZ hackathon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.wemakedevs.org/hackathons/signoz/blog-guide" rel="noopener noreferrer"&gt;Hackathon blog-writing guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://signoz.io/docs/instrumentation/javascript/opentelemetry-nodejs/" rel="noopener noreferrer"&gt;SigNoZ Node.js instrumentation guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://signoz.io/docs/instrumentation/manual-instrumentation/javascript/opentelemetry-nodejs/" rel="noopener noreferrer"&gt;SigNoZ manual Node.js instrumentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/docs/languages/js/" rel="noopener noreferrer"&gt;OpenTelemetry JavaScript documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/SigNoz/signoz-mcp-server" rel="noopener noreferrer"&gt;SigNoZ MCP server&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>signoz</category>
      <category>opentelemetry</category>
      <category>observability</category>
      <category>ai</category>
    </item>
    <item>
      <title>From Exit 139 to HTTP 402: Following One Payment Failure Across SigNoz</title>
      <dc:creator>Tyson</dc:creator>
      <pubDate>Sun, 19 Jul 2026 17:36:29 +0000</pubDate>
      <link>https://dev.to/tyson1234/from-exit-139-to-http-402-following-one-payment-failure-across-signoz-3196</link>
      <guid>https://dev.to/tyson1234/from-exit-139-to-http-402-following-one-payment-failure-across-signoz-3196</guid>
      <description>&lt;p&gt;&lt;em&gt;How I self-hosted SigNoz on WSL2, recovered a crashing telemetry pipeline, and followed one payment failure across metrics, traces, logs, and alerts.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Before I could debug a failed payment, I had to debug the observability system itself. The SigNoz interface loaded, but its telemetry path failed in stages: first ClickHouse Keeper, then the ingester and migrator, each exiting with code &lt;code&gt;139&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once ingestion worked, the payment service showed a 5.08% error rate. A later failed request lasted 38 ms, contained 74 spans, and ended in HTTP &lt;code&gt;402&lt;/code&gt; with an &lt;code&gt;insufficient funds&lt;/code&gt; event; the same trace filter also powered a firing alert. The traffic was synthetic. What I had to work out was how to prove the failure: the useful error log lived on &lt;code&gt;checkout&lt;/code&gt; rather than &lt;code&gt;payment&lt;/code&gt;, and Logs Explorer rejected the trace expression I copied into it.&lt;/p&gt;

&lt;p&gt;This article follows both investigations because the second was impossible until the first worked. The service, alert, and dashboard screenshots came from different rolling windows while the demo kept generating traffic. The trace and correlated logs below share the same trace ID.&lt;/p&gt;

&lt;h2&gt;
  
  
  My setup, briefly
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Lab environment:&lt;/strong&gt; Windows 11, Ubuntu 24.04.3 on WSL2, Docker Engine &lt;code&gt;29.6.2&lt;/code&gt;, Compose &lt;code&gt;v5.3.1&lt;/code&gt;, Foundry &lt;code&gt;v0.2.14&lt;/code&gt;, and about 7.6 GiB of RAM.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After verifying Docker with &lt;code&gt;hello-world&lt;/code&gt;, I followed the current &lt;a href="https://signoz.io/docs/install/docker/" rel="noopener noreferrer"&gt;SigNoz Docker installation guide&lt;/a&gt;. My initial casting file used the Compose deployment mode and enabled the MCP server. I cast it from &lt;code&gt;~/signoz-lab&lt;/code&gt;:&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;source&lt;/span&gt; ~/.bashrc

&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/signoz-lab
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/signoz-lab
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;The first cast pulled the stack, but did not produce a working telemetry path.&lt;/p&gt;

&lt;p&gt;Two setup details prevented later confusion. Windows-side &lt;code&gt;localhost:8080&lt;/code&gt; already belonged to Jenkins, so I opened SigNoz through the current IP address of my WSL2 instance. MCP appeared unhealthy because its container health check called a missing &lt;code&gt;wget&lt;/code&gt;, although &lt;code&gt;/readyz&lt;/code&gt; returned &lt;code&gt;ok&lt;/code&gt; with HTTP &lt;code&gt;200&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  I reproduced exit 139 outside the stack
&lt;/h2&gt;

&lt;p&gt;ClickHouse Keeper entered a restart loop with exit code &lt;code&gt;139&lt;/code&gt;, and its logs were empty. There was no error text to search. Testing the exact image by itself was the first step that narrowed the problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; clickhouse/clickhouse-keeper:25.12.5 &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"exit=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The image still exited &lt;code&gt;139&lt;/code&gt; outside the Foundry-generated stack and without its usual dependencies. I tested other tags independently; &lt;code&gt;25.12.11&lt;/code&gt; printed &lt;code&gt;ClickHouse keeper version 25.12.11.4&lt;/code&gt; and exited &lt;code&gt;0&lt;/code&gt;. The matching server image also ran.&lt;/p&gt;

&lt;p&gt;Keeper was now healthy, but the ingester and schema migrator began printing &lt;code&gt;Segmentation fault&lt;/code&gt;. The same isolation test reproduced exit &lt;code&gt;139&lt;/code&gt; with &lt;code&gt;signoz/signoz-otel-collector:latest&lt;/code&gt;. Running &lt;code&gt;v0.144.5&lt;/code&gt; with &lt;code&gt;--version&lt;/code&gt; did &lt;strong&gt;not&lt;/strong&gt; print a version—it returned &lt;code&gt;unknown flag&lt;/code&gt;. That modest result still mattered: the process reached argument parsing instead of segfaulting at startup.&lt;/p&gt;

&lt;p&gt;The isolated tests produced this diagnostic matrix:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clickhouse-keeper:25.12.5       → exit 139
clickhouse-keeper:25.12.11      → version 25.12.11.4, exit 0
signoz-otel-collector:latest     → exit 139
signoz-otel-collector:v0.144.5   → unknown flag: --version, exit 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An exit status of &lt;code&gt;139&lt;/code&gt; is conventionally read as &lt;code&gt;128 + 11&lt;/code&gt;, meaning the process received &lt;code&gt;SIGSEGV&lt;/code&gt;. That identified the failure class, not its cause: on this machine, the crashing tags died before normal startup while the candidate tags did not.&lt;/p&gt;

&lt;p&gt;Those tests narrowed the candidate tags; deployment still had to prove them. This is the casting file I used. These pins fixed &lt;strong&gt;this machine on July 18, 2026&lt;/strong&gt;; they are not universal recommendations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Installation&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;signoz&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;flavor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;compose&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;
  &lt;span class="na"&gt;ingester&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;signoz/signoz-otel-collector:v0.144.5&lt;/span&gt;
  &lt;span class="na"&gt;telemetrystore&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;clickhouse/clickhouse-server:25.12.11&lt;/span&gt;
  &lt;span class="na"&gt;telemetrykeeper&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;clickhouse/clickhouse-keeper:25.12.11&lt;/span&gt;
  &lt;span class="na"&gt;mcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I saved the file, cast the deployment again, and verified the generated images and final process states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;foundryctl cast &lt;span class="nt"&gt;-f&lt;/span&gt; casting.yaml
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; pours/deployment/compose.yaml config &lt;span class="nt"&gt;--images&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'{{.Name}} | image={{.Config.Image}} | status={{.State.Status}} | exit={{.State.ExitCode}}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  signoz-ingester-1 signoz-telemetrystore-migrator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated configuration used &lt;code&gt;signoz/signoz-otel-collector:v0.144.5&lt;/code&gt; for both the ingester and migrator. After initialization settled, the inspection returned:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/signoz-ingester-1 | image=signoz/signoz-otel-collector:v0.144.5 | status=running | exit=0
/signoz-telemetrystore-migrator | image=signoz/signoz-otel-collector:v0.144.5 | status=exited | exit=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pinned ingester stayed alive. The migrator finished with code &lt;code&gt;0&lt;/code&gt;, which I treated as successful completion in this run. The &lt;code&gt;ingester.spec.image&lt;/code&gt; override follows Foundry's version-matched &lt;a href="https://github.com/SigNoz/foundry/blob/v0.2.14/docs/reference/casting-file.md" rel="noopener noreferrer"&gt;casting-file reference&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  With ingestion working, I sent demo traffic
&lt;/h2&gt;

&lt;p&gt;I cloned &lt;a href="https://github.com/SigNoz/opentelemetry-demo-lite/tree/v0.0.6" rel="noopener noreferrer"&gt;SigNoz's OpenTelemetry Demo Lite repository&lt;/a&gt; at its &lt;code&gt;v0.0.6&lt;/code&gt; tag and reduced the request rate to one request per second. The tagged repository's Compose file pulls &lt;code&gt;signoz/opentelemetry-demo-lite:0.0.5&lt;/code&gt;, which is why both version numbers appeared in my terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~
git clone &lt;span class="nt"&gt;--branch&lt;/span&gt; v0.0.6 &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
  https://github.com/SigNoz/opentelemetry-demo-lite.git
&lt;span class="nb"&gt;cd &lt;/span&gt;opentelemetry-demo-lite

&lt;span class="nv"&gt;WSL_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;hostname&lt;/span&gt; &lt;span class="nt"&gt;-I&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;OTLP_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;WSL_IP&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:4317"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;OTLP_INSECURE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;RPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="se"&gt;\&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application first sent telemetry to the demo's local collector; that collector forwarded all three signals to SigNoz over &lt;a href="https://opentelemetry.io/docs/specs/otlp/" rel="noopener noreferrer"&gt;OTLP/gRPC&lt;/a&gt; at &lt;code&gt;${WSL_IP}:4317&lt;/code&gt;. After five minutes, I checked the containers and the forwarding path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker inspect otel-collector &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{range .Config.Env}}{{println .}}{{end}}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^(OTLP_ENDPOINT|OTLP_INSECURE)='&lt;/span&gt;

docker compose ps &lt;span class="nt"&gt;-a&lt;/span&gt;

&lt;span class="nv"&gt;LOG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/otel-collector-last5m.log
docker compose logs &lt;span class="nt"&gt;--since&lt;/span&gt; 5m otel-collector &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;&amp;amp;1
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"LOG_COMMAND_EXIT=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ein&lt;/span&gt; &lt;span class="s1"&gt;'error|failed|refused|unavailable|retry'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ein&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'export.*(error|fail)|failed.*export|sending_queue|retry.*(export|send)|connection refused|rpc error|context deadline exceeded|service unavailable'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"EXPORT_SCAN_EXIT=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ein&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'Starting GRPC server|Starting HTTP server|Everything is ready'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In that run, &lt;code&gt;OTLP_ENDPOINT&lt;/code&gt; resolved to &lt;code&gt;172.18.31.214:4317&lt;/code&gt;, insecure mode was enabled, and all three demo containers were up; the mock application and Redis were healthy. Log retrieval exited &lt;code&gt;0&lt;/code&gt;. The broad scan found one non-export warning: the configured Docker resource detector could not access &lt;code&gt;/var/run/docker.sock&lt;/code&gt;. That is consistent with the version-matched &lt;a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/v0.91.0/processor/resourcedetectionprocessor/README.md" rel="noopener noreferrer"&gt;resource detector documentation&lt;/a&gt;, which says the Docker detector queries the daemon and requires its socket. The collector continued, detected its host and OS, started OTLP receivers on &lt;code&gt;4317&lt;/code&gt; and &lt;code&gt;4318&lt;/code&gt;, and reported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Everything is ready. Begin running and processing data.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The focused scan returned &lt;code&gt;EXPORT_SCAN_EXIT=1&lt;/code&gt;: &lt;code&gt;grep&lt;/code&gt; found none of those exporter-failure patterns in the five-minute window. That was not proof of storage. SigNoz supplied that proof by showing active logs, traces, and metrics ingestion across 14 services.&lt;/p&gt;

&lt;h2&gt;
  
  
  The payment service had a 5.08% error rate
&lt;/h2&gt;

&lt;p&gt;In the selected 30-minute window, the payment service's &lt;code&gt;charge&lt;/code&gt; operation showed 1,790 calls and a 5.08% error rate. Its P50, P95, and P99 latencies were 0.81 ms, 1.40 ms, and 2.22 ms. The standout signal in that snapshot was failed work, not a long-running request.&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%2Fvamv7z1do0bgxboxai7c.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%2Fvamv7z1do0bgxboxai7c.png" alt="SigNoz payment metrics showing 1,790 charge calls and a 5.08 percent error rate" width="800" height="395"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The service view told me where to look, but not why the charges were failing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In a later window of the same continuing synthetic traffic, I moved to Traces Explorer and used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service.name = 'payment' AND name = 'charge' AND has_error = true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One result opened a distributed checkout path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trace ID &lt;code&gt;51b217b6cf309cd08cc87224e8712f57&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;timestamp July 19, 2026, 21:36:08 IST&lt;/li&gt;
&lt;li&gt;38 ms total duration&lt;/li&gt;
&lt;li&gt;74 spans&lt;/li&gt;
&lt;li&gt;two error spans&lt;/li&gt;
&lt;li&gt;an upstream checkout &lt;code&gt;HTTP POST&lt;/code&gt; with status &lt;code&gt;402&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;payment / charge&lt;/code&gt; span lasting 0.56 ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trace located the failure. The selected span's events explained it: &lt;code&gt;Payment failed: insufficient funds&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here, HTTP &lt;code&gt;402&lt;/code&gt; was the boundary outcome, the span event held the domain reason, and the checkout log later supplied the caller's interpretation.&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%2Ffskwyth4dq1uyboh0id3.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%2Ffskwyth4dq1uyboh0id3.png" alt="SigNoz trace showing the checkout HTTP POST span returning HTTP 402" width="799" height="394"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The checkout client span recorded the HTTP 402 boundary-level failure&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fopi135zh97hgm28wv4sr.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%2Fopi135zh97hgm28wv4sr.png" alt="SigNoz payment charge span showing an insufficient-funds exception" width="800" height="393"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The child payment span supplied the domain reason: insufficient funds.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The status code described the response at the service boundary; the span event supplied the domain reason.&lt;/p&gt;
&lt;h2&gt;
  
  
  The log was not where I first looked
&lt;/h2&gt;

&lt;p&gt;During the investigation, an earlier payment span's &lt;strong&gt;Logs&lt;/strong&gt; tab showed no attached records. I also copied its trace expression into Logs Explorer, where SigNoz rejected it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;key 'name' not found
key 'has_error' not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this dataset, &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;has_error&lt;/code&gt; were valid trace fields but were absent from the log schema. I had crossed a signal boundary without changing schemas. Trace context was the bridge: SigNoz correlates the signals through &lt;code&gt;trace_id&lt;/code&gt; and &lt;code&gt;span_id&lt;/code&gt;, as described in its &lt;a href="https://signoz.io/docs/traces-management/guides/correlate-traces-and-logs/" rel="noopener noreferrer"&gt;logs-and-traces correlation guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the 21:36:08 request shown above, I carried its trace context into Logs Explorer with &lt;code&gt;trace_id = '51b217b6cf309cd08cc87224e8712f57'&lt;/code&gt;. That found records from the distributed request, including &lt;code&gt;ChargeCard failed&lt;/code&gt; and &lt;code&gt;Payment failed&lt;/code&gt;. The relevant error came from the &lt;strong&gt;checkout&lt;/strong&gt; service and shared the trace's &lt;code&gt;trace_id&lt;/code&gt;; its &lt;code&gt;span_id&lt;/code&gt; identified the checkout span that emitted it:&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;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Payment failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&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;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payment service returned 402"&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;"resources"&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;"service.name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"checkout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deployment.environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"demo"&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;"severity_text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ERROR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"span_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"62c755a937ef91d4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"trace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"51b217b6cf309cd08cc87224e8712f57"&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;&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%2Fbehzrfd13133oz6xtf5e.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%2Fbehzrfd13133oz6xtf5e.png" alt="SigNoz Logs Explorer filtered by trace ID showing Payment failed and ChargeCard failed" width="800" height="392"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Filtering the request by trace ID exposed what checkout recorded.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That distinction is why this investigation was more useful than a feature tour. Metrics revealed a pattern. The trace reconstructed one request. A span event supplied the reason. A log showed how the failure was recorded by another service. The signals agreed without pretending they were interchangeable.&lt;/p&gt;
&lt;h2&gt;
  
  
  I reused the query instead of rebuilding it
&lt;/h2&gt;

&lt;p&gt;I saved the filtered trace view and reused its expression for the dashboard and trace-based alert &lt;strong&gt;Payment Charge Failures&lt;/strong&gt;. The rule counts matches over a rolling five-minute window and fires at &lt;code&gt;critical&lt;/code&gt; when any occur, using SigNoz's standard &lt;a href="https://signoz.io/docs/alerts-management/trace-based-alerts/" rel="noopener noreferrer"&gt;trace-based alert workflow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the required notification channel, I ran a temporary receiver on the generated Docker network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; signoz-webhook-receiver &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; signoz-network &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:9001:8000 &lt;span class="se"&gt;\&lt;/span&gt;
  python:3.12-alpine &lt;span class="se"&gt;\&lt;/span&gt;
  python &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'from http.server import BaseHTTPRequestHandler,HTTPServer; H=type("H",(BaseHTTPRequestHandler,),{"do_POST":lambda self:(lambda data:(print(data.decode("utf-8","replace"),flush=True),self.send_response(200),self.end_headers(),self.wfile.write(b"ok")))(self.rfile.read(int(self.headers.get("Content-Length","0"))))}); HTTPServer(("0.0.0.0",8000),H).serve_forever()'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My deployment called the network &lt;code&gt;signoz-network&lt;/code&gt;; another Compose project may differ. I gave the receiver no restart policy.&lt;/p&gt;

&lt;p&gt;Before configuring the channel, I sent it JSON directly:&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;-i&lt;/span&gt; http://localhost:9001 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"test":"working-after-restart"}'&lt;/span&gt;

docker logs &lt;span class="nt"&gt;--tail&lt;/span&gt; 20 signoz-webhook-receiver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The POST returned HTTP &lt;code&gt;200&lt;/code&gt; and appeared in the logs. I set the channel URL to &lt;code&gt;http://signoz-webhook-receiver:8000&lt;/code&gt;, recorded a UTC start time, clicked &lt;strong&gt;Test Notification&lt;/strong&gt;, and inspected only newer logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TEST_START&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%Y-%m-%dT%H:%M:%SZ&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# Click "Test Notification" in SigNoz.&lt;/span&gt;
docker logs &lt;span class="nt"&gt;--since&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TEST_START&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--timestamps&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  signoz-webhook-receiver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test returned HTTP &lt;code&gt;200&lt;/code&gt; and produced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"receiver":"Local Webhook Receiver","status":"firing",
 "alerts":[{"labels":{"alertname":"Test Alert (Local Webhook Receiver)",
 "severity":"critical"}}]}
"POST / HTTP/1.1" 200 -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The alert screenshot predates the selected 21:36 trace because the rule had already fired on earlier occurrences of the same repeating synthetic condition. The payload proves the channel test worked; the firing badge proves the real rule crossed its threshold. I did not capture an actual-rule webhook payload, so I will not claim one.&lt;/p&gt;

&lt;p&gt;I then removed the temporary receiver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; signoz-webhook-receiver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvo4zm499aq85t41jkmm9.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%2Fvo4zm499aq85t41jkmm9.png" alt="SigNoz Payment Charge Failures alert firing at critical severity" width="800" height="170"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The same filter used during debugging now evaluated automatically.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I also expanded the failed-count graph into a &lt;strong&gt;Demo Observability&lt;/strong&gt; payment-health dashboard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Failed Payment Charges Over Time&lt;/strong&gt; applies the error filter and plots &lt;code&gt;count()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total Payment Charge Operations&lt;/strong&gt; uses the same filter without &lt;code&gt;has_error = true&lt;/code&gt; and displays &lt;code&gt;count()&lt;/code&gt; as a number. In the captured rolling window, it showed 1,797.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payment Charge Error Rate Over Time&lt;/strong&gt; calculates &lt;code&gt;(A/B)*100&lt;/code&gt;, where &lt;code&gt;A&lt;/code&gt; is failed charges and &lt;code&gt;B&lt;/code&gt; is all charges. I hid &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;B&lt;/code&gt; and formatted the result as a percentage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;P95 Payment Charge Latency&lt;/strong&gt; uses &lt;code&gt;p95(duration_nano)&lt;/code&gt;; with my panel's unit configuration, SigNoz displayed it in milliseconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I left &lt;strong&gt;Fill gaps&lt;/strong&gt; disabled on the rate and latency panels because a missing interval is not automatically a measured zero, especially for a rate with a denominator. The service view showed 1,790 calls and the dashboard showed 1,797 because they were captured from different rolling 30-minute windows on July 19. Those values are not directly comparable; the dashboard is also bucketed, so its brief 60% peak is not a whole-window error rate.&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%2Foqy4ax2bnm4yr4pu0amn.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%2Foqy4ax2bnm4yr4pu0amn.png" alt="SigNoz payment-health dashboard showing failed charges, operations, error rate, and P95 latency" width="800" height="368"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The four panels answer different questions: when failures occurred, how much traffic was evaluated, what proportion failed, and whether latency changed.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would keep from this lab
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Verify beyond badges.&lt;/strong&gt; SigNoz served pages while its ingester restarted, and an empty error scan was not enough. Receiving-side telemetry was stronger evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduce crashes.&lt;/strong&gt; Testing the exact image separated startup failure from Compose dependencies and configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respect signal schemas.&lt;/strong&gt; Trace filters do not automatically apply to logs; &lt;code&gt;trace_id&lt;/code&gt; was the bridge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reuse queries and qualify workarounds.&lt;/strong&gt; One filter became a view, alert, and dashboard, while the pins remained explicitly machine-specific.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Tested on Windows 11 with WSL2 on July 18–19, 2026. Image tags are mutable; check the current &lt;a href="https://signoz.io/docs/install/docker/" rel="noopener noreferrer"&gt;SigNoz self-hosting documentation&lt;/a&gt; before reusing these pins.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The lab began with a working UI alongside exit &lt;code&gt;139&lt;/code&gt; crashes and ended with an HTTP &lt;code&gt;402&lt;/code&gt; I could trace into checkout logs and monitor afterward. That cross-signal context is what I would use SigNoz for again.&lt;/p&gt;

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