{"slug": "we-instrumented-an-ai-agent-swarm-with-signoz-and-its-own-telemetry-told-us-we", "title": "We instrumented an AI agent swarm with SigNoz, and its own telemetry told us we were wrong about almost everything", "summary": "A developer team built DevSwarm, an AI agent swarm that turns prompts into full-stack apps using open-weight models, and instrumented it with SigNoz for observability. The telemetry revealed that their assumptions about the system were wrong in six key areas, including misattributing failures to models and providers, and discovering that their review agent was the weakest link. The team found that every insight came from span events, dashboard rows, or benchmarks, not from re-reading code.", "body_md": "Built for the WeMakeDevs Agents of SigNoz hackathon, July 2026.\n\n*Mission Control. The graph is the swarm, the river underneath it is the live span stream, and every bar deep-links into that trace in SigNoz.*\n\nDevSwarm turns one prompt into a working full-stack app. Five open-weight models plan it, build it, review it and repair their own routing. Nothing it produces is trusted blindly, and every step is an OpenTelemetry span in SigNoz, including the steps that go wrong.\n\nWe built the observability first, expecting it to prove the thing worked.\n\nIt did something more useful. It spent a week proving that almost everything we believed about our own system was wrong. We blamed a model for a limit we had set ourselves. We blamed a provider outage on the model. We assumed our review agent was our strongest link when it was measurably the weakest. And we spent days writing a design system that, when we finally measured it, was making the output worse.\n\nNot one of those was found by reading the code again. Every single one came off a span event, a dashboard row or a benchmark that disagreed with us.\n\nSo this is not an architecture post. It is six times the telemetry told us we were wrong, with the queries.\n\nThe current numbers, all read live out of SigNoz rather than typed into a slide: 22 generations, 188 traced model calls across 8 models, 2.84 million tokens, 225 critic catches, 19 fallback promotions and 24 generated apps each reporting under their own service name.\n\nFive roles, each on the open-weight model that measured best for that job:\n\n| role | model | job |\n|---|---|---|\n| planner | GLM-5.2 | turn a prompt into a typed build plan and a locked API contract |\n| frontend | GLM-5.2 | one self-contained index.html against that contract |\n| backend | Qwen3-Coder-480B | one Express server against the same contract |\n| critic | Kimi-K2.7-Code | review both, gate the merge, route catches back to their owner |\n| doctor | GLM-5.2 | read the swarm's own traces and repair its model routing |\n\n*Every number on our own landing page is a live ClickHouse query against the trace store. Marketing copy that drifts from the telemetry is impossible by construction.*\n\nEverything is served through Hugging Face Inference Providers. There are zero closed-model API calls in the system, which turned out to matter for reasons we did not anticipate (see the provider section below).\n\nThe critic is the load-bearing part. Frontend and backend are generated in parallel from the same contract, then an independent model reviews both for contract conformance, security and runtime bugs. Real catches route back to the agent that owns them, that agent patches its own file and the critic re-reviews only the delta. Two regeneration rounds, then it ships with an honest verdict either way.\n\nA multi-agent system fails in ways a single-model tool does not. A call can succeed while producing an unusable artifact. A fallback can rescue a request so smoothly that nobody notices the primary is dead. Latency can triple because one role silently started thinking twice as long. None of that shows up in a request log.\n\nSo the very first thing that worked in this project was not code generation. It was a trace.\n\nSigNoz is self-hosted through Foundry, which is a single-config install. Our `casting.yaml`\n\nand `casting.yaml.lock`\n\nare committed to the repo so the deployment is reproducible by anyone, judges included.\n\n**Traces.** Every model call is a span named `llm.<role>`\n\ncarrying GenAI semantic conventions:\n\n```\nspan.setAttributes({\n  'gen_ai.operation.name': 'chat',\n  'gen_ai.request.model': model,\n  'gen_ai.usage.input_tokens': usage.prompt_tokens,\n  'gen_ai.usage.output_tokens': usage.completion_tokens,\n  'devswarm.role': role\n});\n```\n\nTwo span events do the heavy diagnostic lifting. `fallback_promotion`\n\nrecords that a primary failed, which model took over and the verbatim reason. `critic_catch`\n\nrecords every issue the review agent found, with its target and severity. Both are events rather than separate spans on purpose: they belong to the call they describe, and they survive in the trace even when the call ultimately succeeds.\n\n**Metrics.** Six counters and a histogram, because some questions are time series questions rather than trace questions: `devswarm.tokens`\n\n, `devswarm.llm.calls`\n\n, `devswarm.llm.duration`\n\n, `devswarm.fallback.promotions`\n\n, `devswarm.critic.catches`\n\n, `devswarm.generations`\n\n, `devswarm.refinements`\n\n. Labelled by role, model and outcome.\n\n**Logs.** Structured records for the things a human reads during an incident: a fallback promoting, a doctor diagnosis, a generation completing with its verdict and catch count. Same resource attributes as the traces, so a log line and a span line up.\n\nThe whole trace layer is now extracted into a small library, [otel-swarm](https://github.com/himanshu748/otel-swarm), that any multi-agent system can drop in. DevSwarm consumes it as a real dependency, which means if the library breaks, our own dashboards go dark first. That felt like the honest way to ship it.\n\n*One generation as a flame graph. Planner, then frontend and backend in parallel, then the critic.*\n\n*The log stream during a rough run. WARN lines are fallback promotions, each naming the model that failed and why.*\n\nFour dashboards, all committed as JSON in `observability/dashboards/`\n\n. The one we actually live in is Command Center: is the swarm healthy, and if not, which role.\n\nTwo queries worth sharing, because span events in ClickHouse are not obvious the first time.\n\nRole health, straight off the trace store:\n\n```\nSELECT attributes_string['devswarm.role'] AS role,\n       count() AS calls,\n       countIf(statusCode = 2) AS errors,\n       round(quantile(0.95)(durationNano) / 1e9, 1) AS p95_s,\n       sum(attributes_number['gen_ai.usage.input_tokens']\n         + attributes_number['gen_ai.usage.output_tokens']) AS tokens\nFROM signoz_traces.distributed_signoz_index_v3\nWHERE serviceName = 'devswarm' AND name LIKE 'llm.%'\nGROUP BY role ORDER BY calls DESC\n```\n\nFallback promotions over time, which requires reaching into the events array:\n\n```\nSELECT toStartOfInterval(timestamp, INTERVAL 30 MINUTE) AS ts,\n       attributes_string['devswarm.role'] AS role,\n       count() AS value\nFROM signoz_traces.distributed_signoz_index_v3\nWHERE serviceName = 'devswarm'\n  AND arrayExists(e -> e LIKE '%fallback_promotion%', events)\nGROUP BY ts, role ORDER BY ts\n```\n\nOne design decision we are glad about: the marketing numbers on our own landing page are fetched from these same queries at request time. The page cannot drift from the telemetry, because there is only one source of both.\n\n*Command Center. Top row answers \"is the swarm healthy\", the role-health table answers \"which role\", while the fallback chart should trend to zero.*\n\n*LLM Economics: tokens and latency per role and per model, which is how we caught the frontend role burning two thirds of its budget on reasoning.*\n\nEvery token in the table below came off a span. This is one real run, the letterpress site above, priced at the rates the Hugging Face router itself reports for the providers we use.\n\n| role | model | in | out | cost |\n|---|---|---|---|---|\n| frontend | GLM-5.2 | 27,855 | 38,888 | $0.2101 |\n| critic | Kimi-K2.7-Code | 50,996 | 14,539 | $0.1066 |\n| planner | GLM-5.2 | 670 | 4,090 | $0.0189 |\n| backend | Qwen3-Coder-480B | 2,800 | 3,778 | $0.0069 |\n82,321 |\n61,295 |\n$0.34 |\n\nThirty four cents for a designed marketing site with a working Express backend, a waitlist that validates email and rejects duplicates, plus its own OpenTelemetry wiring. Across passing runs the range is about 18 cents to 56 cents.\n\nTwo things in that table surprised us.\n\nThe critic costs fifteen times what the backend author costs. Reviewing the code is dramatically more expensive than writing it, because review means reading both artifacts in full, twice, while the backend agent writes one file once. Nobody budgets for that. If you are building a review gate into an agent system, it is not a rounding error on top of generation, it is a third of your bill.\n\nAnd a failed run costs more than a successful one. Our worst generations burned 232,000 tokens hitting the regeneration ceiling, against 74,000 for the cleanest pass. So convergence is not only a quality metric, it is the cost metric. Fixing the contract-format bug in finding five did more for our unit economics than any model swap we made.\n\nThis is the part of the build we are proudest of, and it is a genuinely small amount of code.\n\nTwo alert rules live in `observability/alerts/`\n\n: a fallback-usage spike and a critic catch-rate flatline. Both notify a webhook channel called `swarm-doctor`\n\n, which points at `POST /api/doctor/webhook`\n\non the swarm itself.\n\nWhen an alert fires, the Doctor wakes up, queries the swarm's own traces for the last hour and decides what to do about the routing table. It promotes a backup model, resets a recovered primary or does nothing, then explains itself in plain English using the numbers it just read. Its first real diagnosis, verbatim:\n\n\"The critic role is the clear problem area: its primary triggered 6 fallback promotions out of 9 calls (67%) with a 22% error rate and p95 latency of 242s. Backend, planner and doctor are healthy.\"\n\nThe Doctor's own model calls are traced too, so the healer is exactly as observable as the patient.\n\n*The Doctor reporting a healthy swarm. It read 180 minutes of its own traces to say so, and it is honest about sample size: \"call volume is very low, so latency figures are not statistically meaningful\".*\n\nTwo hard-won SigNoz API notes, since we lost hours to both:\n\nAlert rules must be created against `/api/v2/rules`\n\nwith `schemaVersion: v2alpha1`\n\n, a `notificationSettings`\n\nblock and at least one channel. The v1 endpoint accepts the request and returns `\"alert rule is not valid\"`\n\nwith no indication of which field is wrong. Dashboards, by contrast, go to `/api/v1/dashboards`\n\nwith a `SIGNOZ-API-KEY`\n\nheader and behave exactly as documented.\n\nAlso: a cold Docker restart can leave ClickHouse replicas read-only until Keeper reconnects. It usually self-heals within a minute. If it does not, `SYSTEM RESTORE REPLICA`\n\nper table clears it.\n\nEvery app the swarm generates ships instrumented. Alongside `index.html`\n\nand `server.js`\n\n, each generated folder gets an `otel.mjs`\n\nbootstrap, a `package.json`\n\n, and a `signoz-dashboard.json`\n\nscoped to that app's own service name. If a `SIGNOZ_API_TOKEN`\n\nis configured, the dashboard is created in SigNoz at generation time, before the user has opened the preview.\n\nSo the generated app appears in SigNoz as its own service, with RED metrics and a routes table, seconds after it exists. Twenty four of them are in our instance right now.\n\nWorth saying because people assume otherwise: there is no image model anywhere in this pipeline. The swarm generates 227 inline SVG elements across the 26 apps it has built, an average of 8.7 per app, and every one of them was written as markup by a language model. The Vandercook press in the screenshot above is hand-drawn SVG, not a generated image. The only assets we ever image-generated are DevSwarm's own favicon and social card, which are branding for the tool rather than anything the swarm produces.\n\n*One of the outputs. The Vandercook press is inline SVG the model drew itself, and this app reports to SigNoz under its own service name from the moment it boots.*\n\nOne caveat we had to learn the hard way: an app only emits spans while its backend is actually running. Early on our preview served the frontend statically, so the generated Express server never booted and the app silently fell back to localStorage. The preview looked perfect and the service page was almost empty. That mismatch, two spans where there should have been dozens, is what gave the bug away. Previews now spawn the real server as a child process and proxy to it.\n\nThis is the section I would want to read, so it is the longest one.\n\n**1. A \"model limitation\" was a stale constant we wrote ourselves.**\n\nTraces showed every frontend failure as `finish: length`\n\n, truncating full-page HTML. We concluded GLM-5.2's provider capped completions at 16384 tokens and moved the role to another model. The cap was real when we found it. It was also in our own config, and when the provider limit later lifted, our constant kept enforcing a limit that no longer existed. The average frontend artifact needs about 19,000 output tokens. We had guaranteed truncation and blamed the model for a fortnight.\n\n**2. The real cause was provider roulette, visible only in the span event text.**\n\nAfter removing our own cap, GLM still failed intermittently with a 400: `max_completion_tokens is limited to 16384 for glm-5.2`\n\n. The Hugging Face router load-balances a model across every provider serving it, and their limits disagree. We probed all seven: scaleway caps at 16384, featherless at 32768, novita and zai-org at 131072, while together, fireworks-ai and deepinfra accept 200000 or more. Unpinned, roughly one request in seven hit the strict provider and died instantly. Pinning the model to one provider produced our first ever generation with zero fallbacks. That entire diagnosis came out of the `reason`\n\nattribute on a `fallback_promotion`\n\nevent.\n\n**3. Our span attribute was hiding the failures we most wanted to see.**\n\nWhen a primary failed, our code overwrote `gen_ai.request.model`\n\non the span with the fallback's name. It seemed tidy. It meant every dashboard row attributed the primary's failure, and its wasted latency, to the fallback that cleaned up after it. We spent an afternoon convinced the critic's backup model was slow and error-prone. Isolating them in a benchmark showed the opposite: the backup was fine at 7 seconds, and the primary was the problem. If you take one implementation detail from this post, take this one. Record the model you attempted, and put the promotion in an event, not on top of the attribute you will later group by.\n\n**4. Our review gate was the weakest model in the swarm.**\n\nWe had never benchmarked the critic, so we built one: a generated app with three documented contract defects, three runs per model, scored on defect recall. DeepSeek-V4-Pro, our incumbent primary, found 2 of 9. One run burned its entire 32768-token budget and returned nothing parseable. Kimi-K2.7-Code found 8 of 9 and was consistent across runs. The clearest pattern in the data was that on a review task, reasoning volume tracks defect recall: the terse models answered in under 250 output tokens and missed real bugs.\n\n**5. A 20 percent pass rate was one missing sentence in the contract.**\n\nOur plans specified field names and types but never formats, ranges or nullability. So the backend rejected `rating: 0`\n\nwhile the frontend sent 0 as its default, and the backend demanded `YYYY-MM-DD`\n\nwhile the frontend sent full ISO strings. Three consecutive generations hit the regeneration ceiling on exactly this class of disagreement. The fix was making the planner write a binding rules string per field, for example `\"integer 0 to 5 inclusive, where 0 means unrated and is a valid value\"`\n\n. Both builders now read the same sentence. Catches dropped from 9 to 3 and the next run passed.\n\n**6. Our design system was making the output worse.**\n\nWe wrote a careful design guide so generated apps would not look like generated apps. Then we measured it: same model, same prompt, the only variable being whether the guide was attached. Without it, 14 inline SVGs, 3 animations and a deliberate typeface pairing. With it, 5 SVGs, 1 animation and Courier New. Three of our own rules did that. \"System font stack is fine\" told the model not to bother choosing type. \"Cut any animation that does not serve the subject\" read as licence to strip ornament. And our frontend prompt banned all external requests, which silently banned Google Fonts, so it could not have chosen a real typeface even if it wanted to. We had written a list of prohibitions, which is good at preventing bad output and bad at producing good output.\n\n*Before. Mono labels from a system stack, and card colours the backend invented at random.*\n\n*After. Same model, same prompt, three rules removed from our guide: Fraunces display type, a drawn logo mark, filter chips carrying live counts, plus the books rendered as spines on a shelf.*\n\nThere was a second layer to that one. The guide had a generous section for marketing sites, licensing scroll reveals, entrance sequences and layered depth, plus a stingy section for apps. Every app the swarm built was being held to a deliberately plainer standard than every site, and nobody had noticed because the sites looked great.\n\nDevSwarm was built with Claude Code, which is worth stating plainly rather than leaving as an inference. An AI coding agent helped build an AI coding agent, and the hackathon rules ask entrants to declare assistant use, so here it is.\n\nIt is also relevant to the point of this post. Every finding in the section above started as a confident, wrong belief held by both of us, human and assistant alike. The stale token cap, the model we blamed for a provider's limit, the review gate we assumed was our strongest link, the design system we were sure was helping. None of those were resolved by reasoning harder about the code. They were resolved by a span event, a dashboard row or a benchmark disagreeing with us.\n\nThat is the argument for instrumenting an agent system early. When you are building with agents, and with an agent, the telemetry is the only participant in the conversation with no opinion to defend.\n\nIf you are instrumenting an agent system, three things paid for themselves immediately.\n\nPut the reason text in the span event. Not a code, not an enum, the actual provider error string. Both of our worst bugs were solved by reading that field, and neither would have been visible in a metric.\n\nNever overwrite an attribute you intend to group by. Add, do not replace.\n\nMake your product read its own telemetry. Our landing page statistics, our Doctor's diagnosis and our dashboards all run the same queries against the same trace store. It removes a whole category of drift, and it turns your observability stack from a debugging tool into a feature.\n\nThe through line of every finding above is the same, and I have thought about it more than I expected to.\n\nNot one of these was a hard problem. A stale constant. A provider with a different limit. An attribute overwritten in the wrong place. A missing sentence in a contract. Three over-cautious lines in a style guide. Any of them would have been a five minute fix if we had known. Together they cost us most of a week and made the system look, from the outside, like the models were letting us down.\n\nThey were not. Every single time, the model did exactly what our configuration told it to do. The failure was always upstream of the model, in something we had written and then stopped looking at.\n\nI think that is the actual lesson of building with agents, and it is not a comfortable one. The debugging skill is not prompt engineering. It is being willing to believe your instrumentation over your own memory of what you configured three days ago. We only got there because the telemetry kept producing numbers that made our explanations impossible.\n\nIf you are building something similar, I would genuinely like to know whether your experience matches. My suspicion is that a lot of \"the model is not good enough\" is actually \"my config is stale and I have no way to see it\".\n\nDashboards, alert rules and the Foundry `casting.yaml`\n\nare all in the repo under `observability/`\n\n, so the whole SigNoz side of this is reproducible rather than described.", "url": "https://wpnews.pro/news/we-instrumented-an-ai-agent-swarm-with-signoz-and-its-own-telemetry-told-us-we", "canonical_source": "https://dev.to/himanshu_748/we-instrumented-an-ai-agent-swarm-with-signoz-and-its-own-telemetry-told-us-we-were-wrong-about-3fip", "published_at": "2026-07-25 15:01:20+00:00", "updated_at": "2026-07-25 15:34:18.967814+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-agents", "developer-tools", "ai-infrastructure"], "entities": ["SigNoz", "WeMakeDevs", "DevSwarm", "GLM-5.2", "Qwen3-Coder-480B", "Kimi-K2.7-Code", "Hugging Face", "Foundry"], "alternates": {"html": "https://wpnews.pro/news/we-instrumented-an-ai-agent-swarm-with-signoz-and-its-own-telemetry-told-us-we", "markdown": "https://wpnews.pro/news/we-instrumented-an-ai-agent-swarm-with-signoz-and-its-own-telemetry-told-us-we.md", "text": "https://wpnews.pro/news/we-instrumented-an-ai-agent-swarm-with-signoz-and-its-own-telemetry-told-us-we.txt", "jsonld": "https://wpnews.pro/news/we-instrumented-an-ai-agent-swarm-with-signoz-and-its-own-telemetry-told-us-we.jsonld"}}