<?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: Nagarjuna Bolla</title>
    <description>The latest articles on DEV Community by Nagarjuna Bolla (@nagarjuna325).</description>
    <link>https://dev.to/nagarjuna325</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%2F4048287%2Fc09abd5e-488d-4157-a382-c57c5ab47c58.png</url>
      <title>DEV Community: Nagarjuna Bolla</title>
      <link>https://dev.to/nagarjuna325</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nagarjuna325"/>
    <language>en</language>
    <item>
      <title>I Built Four AI Agents That Fix Bugs by Themselves, Then Made Them Watch Themselves Work</title>
      <dc:creator>Nagarjuna Bolla</dc:creator>
      <pubDate>Sun, 26 Jul 2026 18:32:32 +0000</pubDate>
      <link>https://dev.to/nagarjuna325/-i-built-four-ai-agents-that-fix-bugs-by-themselves-then-made-them-watch-themselves-work-2dk2</link>
      <guid>https://dev.to/nagarjuna325/-i-built-four-ai-agents-that-fix-bugs-by-themselves-then-made-them-watch-themselves-work-2dk2</guid>
      <description>&lt;p&gt;We've gotten good at observing software. We're not yet good at observing the &lt;em&gt;agents&lt;/em&gt; that increasingly write and operate that software.&lt;/p&gt;

&lt;p&gt;When a service throws a 500, you open a trace and see exactly which span failed. When an AI agent does something wrong, you usually get… a log line saying it failed. Maybe a stack trace. Rarely the reasoning, the tool calls it made, what it cost, or — most importantly — &lt;em&gt;whether the failure was the agent's fault at all.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So for the Agents of SigNoz hackathon I built &lt;strong&gt;AgentOps&lt;/strong&gt;: four autonomous agents that detect, diagnose, fix, and verify a real bug in a real application with no human in the loop. The self-healing part is the demo. The actual project is that &lt;strong&gt;every thought those agents have is a span in SigNoz.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;I deliberately did not scaffold my own toy app to break. Fixing a bug you planted in code you wrote is not convincing.&lt;/p&gt;

&lt;p&gt;Instead I vendored &lt;strong&gt;&lt;a href="https://github.com/AutomationPanda/buggyboard-web-app" rel="noopener noreferrer"&gt;BuggyBoard&lt;/a&gt;&lt;/strong&gt;, an MIT-licensed bug tracker by Andrew Knight — Express, TypeScript, SQLite. Then I seeded exactly one defect into its not-found guard:&lt;/p&gt;

&lt;p&gt;const bug = getBug(id);&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if (!bug) {&lt;/li&gt;
&lt;li&gt;if (!bug.id) {
res.status(404).json({ error: "not_found", message: "Bug not found." });&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now &lt;code&gt;GET /api/bugs/999999&lt;/code&gt; dereferences &lt;code&gt;null&lt;/code&gt;, throws a &lt;code&gt;TypeError&lt;/code&gt;, and Express  returns &lt;strong&gt;500&lt;/strong&gt; where it should return &lt;strong&gt;404&lt;/strong&gt;. A boring, extremely realistic bug.&lt;/p&gt;

&lt;p&gt;There's a nice symmetry to it: the agents fix a bug in a bug tracker.&lt;/p&gt;




&lt;h2&gt;
  
  
  The loop
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Monitor  →  Diagnosis  →  Fix  →  Verify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Monitor&lt;/strong&gt; polls SigNoz every four seconds asking "any error spans from &lt;code&gt;demo-api&lt;/code&gt; since my last check?" It has &lt;strong&gt;no privileged backchannel&lt;/strong&gt; to the application. It finds out the same way a human on-call engineer would — by querying the observability backend. It makes zero LLM calls; the threshold is deterministic. Its agenthood is the autonomous loop, not cleverness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis&lt;/strong&gt; pulls the ERROR logs and the full trace from SigNoz, hands &lt;em&gt;only that evidence&lt;/em&gt; to an LLM, and demands strict JSON back: file, line, cause. It answers &lt;code&gt;src/index.ts:87&lt;/code&gt;. It never reads the source code, and it certainly never reads the answer key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt; reads the suspect file through a sandboxed filesystem MCP server, makes one LLM call for a minimal patch, validates it locally, writes it, and restarts the service — rolling back to the original bytes if the patched service won't come up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify&lt;/strong&gt; replays the exact failing request and demands exactly a &lt;code&gt;404&lt;/code&gt;, then re-queries SigNoz to confirm no new errors.&lt;/p&gt;

&lt;p&gt;Total: &lt;strong&gt;30 to 60 seconds&lt;/strong&gt;, unattended.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📸 &lt;em&gt;Screenshot: mission-control with the four agents mid-pipeline&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&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%2F8gv1yjsr013eekgzoub4.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%2F8gv1yjsr013eekgzoub4.png" alt=" " width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The part that actually matters: the agents query SigNoz as a tool
&lt;/h2&gt;

&lt;p&gt;This is the design decision I'd defend hardest.&lt;/p&gt;

&lt;p&gt;SigNoz isn't where these agents &lt;em&gt;report&lt;/em&gt; — it's where they &lt;em&gt;perceive&lt;/em&gt;. Through the&lt;br&gt;
&lt;strong&gt;SigNoz MCP server&lt;/strong&gt; (which Foundry installs alongside SigNoz in one step), the agents call real tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;signoz_search_traces&lt;/code&gt; — Monitor's detection loop, and Verify's post-fix check&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;signoz_search_logs&lt;/code&gt; — Diagnosis's primary evidence, because the stack trace lives in the log body&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;signoz_get_trace_details&lt;/code&gt; — the full span tree for the failing request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And because I wrapped every one of those calls in a span, &lt;strong&gt;the agents' use of SigNoz is itself observable in SigNoz.&lt;/strong&gt; You can see how long each tool call took and whether it succeeded.&lt;/p&gt;

&lt;p&gt;That recursion is the whole thesis of the project.&lt;/p&gt;


&lt;h2&gt;
  
  
  One incident = one trace
&lt;/h2&gt;

&lt;p&gt;Two OTel services exist: &lt;code&gt;demo-api&lt;/code&gt; (the app) and &lt;code&gt;sdlc-agents&lt;/code&gt; (the swarm). A single incident produces one continuous trace of about 14 spans:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agent.orchestrator.incident              ← root: incident.id, phase events, span link
├── agent.diagnosis.run
│   ├── gen_ai.tool.signoz_search_logs
│   ├── gen_ai.tool.signoz_get_trace_details
│   └── agent.diagnosis.llm              ← model + input/output tokens
├── agent.fix.run
│   ├── gen_ai.tool.read_text_file
│   ├── agent.fix.llm
│   ├── gen_ai.tool.write_file
│   └── agent.fix.restart
└── agent.verify.run
    ├── agent.verify.replay              ← http.request.method, status 404
    └── gen_ai.tool.signoz_search_traces
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;📸 &lt;em&gt;Screenshot: the SigNoz trace waterfall for one incident&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A few conventions I locked down early and never broke:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent steps are &lt;code&gt;agent.&amp;lt;name&amp;gt;.&amp;lt;step&amp;gt;&lt;/code&gt;. MCP tool calls are &lt;code&gt;gen_ai.tool.&amp;lt;literal tool name&amp;gt;&lt;/code&gt;, always as a child of the step that invoked them.&lt;/strong&gt; The &lt;code&gt;gen_ai.tool.*&lt;/code&gt; namespace is strictly for MCP calls. When the Fix agent restarts a process or the Verify agent makes an HTTP request, those are &lt;em&gt;steps&lt;/em&gt; — &lt;code&gt;agent.fix.restart&lt;/code&gt;, &lt;code&gt;agent.verify.replay&lt;/code&gt; — never dressed up as tool calls. Keeping that boundary clean is what makes the trace readable at a glance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every LLM call carries &lt;code&gt;gen_ai.request.model&lt;/code&gt;, &lt;code&gt;gen_ai.usage.input_tokens&lt;/code&gt;, and &lt;code&gt;gen_ai.usage.output_tokens&lt;/code&gt;,&lt;/strong&gt; per the OTel GenAI semantic conventions. Token spend per agent falls straight out of that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;incident.id&lt;/code&gt; is propagated through OTel Context, not function parameters.&lt;/strong&gt; This one paid off. Threading an ID through every agent signature would have touched every function and silently missed any span whose caller forgot to pass it. Instead one Context key, read inside the two span-opening wrappers, stamps all 14 spans — including code I never modified. If you've used a &lt;code&gt;ThreadLocal&lt;/code&gt; for a request ID in Java, it's exactly that, except AsyncLocalStorage keeps it correct across &lt;code&gt;await&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The root span carries a span &lt;em&gt;link&lt;/em&gt; to the Monitor poll that detected the incident&lt;/strong&gt; — "caused by, not parented by." The poll is its own root trace; it existed before the incident did, so parenting would be a lie about causality.&lt;/p&gt;




&lt;h2&gt;
  
  
  Failure honesty, and why it's the best thing I built
&lt;/h2&gt;

&lt;p&gt;Early on, a failed run just said "failed." Useless. I couldn't tell a bad hypothesis from an API outage.&lt;/p&gt;

&lt;p&gt;So failures became explicit, on the span:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;llm_error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Model failed or returned junk, or a pre-write guard rejected the patch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;wrong_hypothesis&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Process is up but still misbehaving&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;patch_broke_process&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Patch wouldn't boot; rollback succeeded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rollback_failed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Even rollback wouldn't boot — stop, alert a human&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;telemetry_unavailable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SigNoz/MCP is down — infrastructure, not reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fs_mcp_unavailable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The filesystem MCP transport died&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The rule that makes it work: &lt;strong&gt;a dead telemetry pipeline must never be reported as an agent reasoning badly.&lt;/strong&gt; Those are completely different problems with completely different fixes, and collapsing them into "failed" destroys the only signal that matters.&lt;/p&gt;

&lt;p&gt;There are also &lt;strong&gt;no retries anywhere&lt;/strong&gt;. One attempt. If it fails, it fails loudly in the trace. Retries would have hidden exactly the failures I most wanted to see.&lt;/p&gt;

&lt;p&gt;This paid for itself twice in one afternoon. Two runs failed; the spans told me instantly that one was a Gemini &lt;code&gt;503&lt;/code&gt; (transient, provider-side) and the other was a telemetry ingest race (mine). Same symptom, completely different causes, zero debugging time.&lt;/p&gt;




&lt;h2&gt;
  
  
  SigNoz caught a bug in my own agent
&lt;/h2&gt;

&lt;p&gt;My favourite moment.&lt;/p&gt;

&lt;p&gt;The Verify agent waited a flat 5 seconds after the restart, then queried SigNoz once to confirm no new errors. A run failed with &lt;code&gt;telemetry_unavailable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The agent was right and my tuning was wrong. Spans take &lt;strong&gt;5 to 10 seconds&lt;/strong&gt; to become queryable after export. Verify was racing ingest, finding an empty window, and —  correctly, by its own logic — reporting that the pipeline looked dead.&lt;/p&gt;

&lt;p&gt;The fix was to separate &lt;em&gt;waiting for data to arrive&lt;/em&gt; from &lt;em&gt;asserting what the data says&lt;/em&gt;: poll the same fixed window every 3 seconds up to 21 seconds until it's non-empty, then evaluate the assertion exactly once. It's the difference between &lt;code&gt;Thread.sleep(5000)&lt;/code&gt; before an assert and &lt;code&gt;await().atMost(21, SECONDS).until(...)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I only found it because the failure was visible in the trace instead of swallowed by a retry.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dashboards, and a lesson about metric labels
&lt;/h2&gt;

&lt;p&gt;Two dashboards, version-controlled as JSON and provisioned idempotently through the SigNoz API:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent Fleet Health&lt;/strong&gt; — per-agent invocations, p50/p95 latency, LLM token spend by agent, MCP tool-call breakdown, failure rate, and failures by reason.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo API Health&lt;/strong&gt; — request volume, 5xx count, and a per-status-code breakdown of the failing route that visibly goes red at trigger and green after the fix.&lt;/p&gt;

&lt;p&gt;Plus a threshold alert on agent failure rate.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📸 &lt;em&gt;Screenshot: the Agent Fleet Health dashboard&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&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%2Fc1uaekg6su9m8exomwvs.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%2Fc1uaekg6su9m8exomwvs.png" alt=" " width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The non-obvious part was &lt;strong&gt;which data source each panel needs.&lt;/strong&gt; SigNoz's span metrics do cover internal spans — the span name shows up as an &lt;code&gt;operation&lt;/code&gt; label, which I didn't expect. But they carry only &lt;code&gt;service.name&lt;/code&gt;, &lt;code&gt;operation&lt;/code&gt;, &lt;code&gt;span.kind&lt;/code&gt; and &lt;code&gt;status.code&lt;/code&gt;. Every custom attribute I'd carefully added — token counts, &lt;code&gt;gen_ai.agent.name&lt;/code&gt;, &lt;code&gt;incident.id&lt;/code&gt;, failure reason, even &lt;code&gt;http.route&lt;/code&gt; — exists &lt;strong&gt;only&lt;br&gt;
on spans&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So panels are split per-panel: latency and invocation counts come from metrics (cheap, pre-aggregated), while anything needing a custom dimension queries traces.&lt;/p&gt;

&lt;p&gt;The general lesson: &lt;em&gt;pre-aggregated metrics only carry the dimensions someone declared in advance.&lt;/em&gt; Assuming your custom span attributes are queryable as metric labels gets you panels that render empty rather than erroring — which is worse, because empty looks like "nothing happened."&lt;/p&gt;

&lt;p&gt;Two more traps worth writing down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;signoz_calls_total&lt;/code&gt; is a &lt;strong&gt;monotonic delta counter&lt;/strong&gt;, so it needs
&lt;code&gt;timeAggregation: "increase"&lt;/code&gt;. The default &lt;code&gt;rate&lt;/code&gt; silently returns nothing against sparse demo traffic.&lt;/li&gt;
&lt;li&gt;Metric labels are &lt;strong&gt;dotted&lt;/strong&gt; (&lt;code&gt;service.name&lt;/code&gt;). The underscore form doesn't error — it returns empty group keys with a non-zero aggregate. Silently wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And because "the API returned 200" proves nothing, every panel is validated by committed script that runs &lt;strong&gt;the panel's own query&lt;/strong&gt; and fails any panel returning empty or all-zero data. I run it over a narrow 15-minute window containing only a fresh incident, so the panels have to populate from live data rather than accumulated history.&lt;/p&gt;




&lt;h2&gt;
  
  
  Does it generalise, or did I just special-case my own bug?
&lt;/h2&gt;

&lt;p&gt;The obvious challenge. So I tested it.&lt;/p&gt;

&lt;p&gt;I injected a &lt;strong&gt;completely different&lt;/strong&gt; defect the system had never seen — a different route (&lt;code&gt;GET /api/bugs&lt;/code&gt;, the list endpoint), a different error class (calling a method that doesn't exist rather than dereferencing null), at a different line:&lt;/p&gt;

&lt;p&gt;const bugs = listBugs();&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;res.json(bugs);&lt;/li&gt;
&lt;li&gt;res.json(bugs.sortByPriority());&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No code changes. No prompt changes. I just broke it and watched.&lt;/p&gt;

&lt;p&gt;Monitor detected it. Diagnosis reported &lt;strong&gt;&lt;code&gt;src/index.ts:77&lt;/code&gt;&lt;/strong&gt; with the cause &lt;em&gt;"attempts to invoke &lt;code&gt;bugs.sortByPriority()&lt;/code&gt;, but &lt;code&gt;sortByPriority&lt;/code&gt; is not a defined function on &lt;code&gt;bugs&lt;/code&gt;"&lt;/em&gt; — exactly right. Fix repaired it. Verify confirmed it. Incident closed &lt;strong&gt;resolved&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Nothing about that bug was anticipated anywhere in the code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Guardrails
&lt;/h2&gt;

&lt;p&gt;An agent that rewrites source and restarts a live service needs hard limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Least privilege.&lt;/strong&gt; The Fix agent reaches the codebase only through a filesystem MCP server rooted at the backend directory. It cannot resolve a path outside it — a committed test asserts that relative traversal, absolute paths, directory listing, and search are all refused.&lt;/p&gt;

&lt;p&gt;One detail I nearly got wrong: the filesystem MCP server &lt;em&gt;replaces its own allow-list with client-supplied roots&lt;/em&gt; if the client advertises the &lt;code&gt;roots&lt;/code&gt; capability. My client declares no capabilities, deliberately. A server-side restriction is only as strong as the negotiation that can rewrite it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pre-write validation.&lt;/strong&gt; A patch must match exactly once, must actually change something, and is capped at 300 characters per side and a 200-byte net delta. Violations abort before any write.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automatic rollback.&lt;/strong&gt; Original bytes captured before writing; restored if the patched service fails its health check. Mechanical reversion, not a second guess.&lt;/p&gt;




&lt;h2&gt;
  
  
  Things that cost me time
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A model in ListModels is not a model you can call.&lt;/strong&gt; &lt;code&gt;gemini-2.5-flash&lt;/code&gt; appeared in the listing and returned 404 "no longer available to new users" on the actual call.&lt;br&gt;
A listing endpoint is not an entitlement check — verify against &lt;em&gt;your&lt;/em&gt; credentials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free-tier quota is per model, per day.&lt;/strong&gt; 20 requests/day/model, and each incident spends 2.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini flash models think by default, and thinking tokens come out of&lt;br&gt;
&lt;code&gt;maxOutputTokens&lt;/code&gt;.&lt;/strong&gt; With a 1024-token budget, the entire allowance was consumed thinking before any JSON was emitted — &lt;code&gt;finishReason: MAX_TOKENS&lt;/code&gt;, empty text. An empty response at MAX_TOKENS is a budget symptom, not a prompt-quality problem. Don't start by rewriting the prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication succeeding is not authorization working.&lt;/strong&gt; A fresh SigNoz&lt;br&gt;
service-account key returned 200 on &lt;code&gt;/api/v1/version&lt;/code&gt; and on the identity endpoint while every data route returned 403 — because the account had no role attached. Test a &lt;em&gt;privileged&lt;/em&gt; route, not a version route.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instrumenting an ESM app needs more than &lt;code&gt;--require&lt;/code&gt;.&lt;/strong&gt; Node's ESM→CJS path bypasses require-in-the-middle, so a preload alone patched &lt;code&gt;node:http&lt;/code&gt; but silently left Express unpatched. Spans still appeared — just named bare &lt;code&gt;GET&lt;/code&gt; with no route. When verifying OTel setup, check span &lt;em&gt;names and route attributes&lt;/em&gt;, not just span presence.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd do next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Detection is error-shaped.&lt;/strong&gt; Monitor triggers on 5xx and exception spans. A silent logic bug returning HTTP 200 with a wrong answer would never be noticed. Catching that needs assertions about correctness, not just errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify's replay is bug-specific.&lt;/strong&gt; It re-requests the known failing URL. For the novel bug, the general safety net was the "zero error spans after restart" check. A more general Verify would derive its replay from the incident.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;The self-healing loop is a good demo. But the thing I'd actually want in production is the boring part: &lt;strong&gt;when an agent fails, I want to know whether it was the agent's fault&lt;/strong&gt;, and I want to answer that from a trace rather than from a hunch.&lt;/p&gt;

&lt;p&gt;That means treating agents like services — spans for reasoning steps, child spans for tool calls, token usage as a first-class metric, and failure reasons specific enough to act on. SigNoz's MCP server made the interesting half possible, because the agents could consume observability rather than just emit it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/Nagarjuna325/Agent-Observability" rel="noopener noreferrer"&gt;https://github.com/Nagarjuna325/Agent-Observability&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Stack: TypeScript, OpenTelemetry, SigNoz OSS (deployed via Foundry), SigNoz MCP server, Google Gemini, Express, React. Demo app: BuggyBoard by Andrew Knight.&lt;/em&gt;&lt;/p&gt;

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