cd /news/ai-agents/building-an-autonomy-error-budget-ga… · home topics ai-agents article
[ARTICLE · art-74005] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Building an Autonomy Error Budget Gateway with SigNoz and OpenTelemetry

A developer built LEASH, an error budget gateway for AI agents that dynamically adjusts permissions based on real-time telemetry from SigNoz and OpenTelemetry. The system demotes an agent's access tier when failures exceed a threshold, preventing autonomous agents from causing damage during hallucinations or tool failures. The project was completed in about a day for the Agents of SigNoz hackathon.

read6 min views1 publishedJul 26, 2026

I built an AI agent that's allowed to run database migrations and clean up staging tables on its own. Then I built a second system whose only job is to take that permission away the moment things start going wrong. That second system is LEASH, and this is the story of building it in about a day for the Agents of SigNoz hackathon.

Everyone talks about AI agents "acting autonomously" like it's a settled, safe thing. It isn't. If you give an agent write access to a database and it hallucinates, or one of its tools starts failing silently, nothing stops it from doing that ten more times before a human even notices.

The usual fix people reach for is "human in the loop." Sounds responsible. In practice it doesn't scale. Nobody is reviewing every single tool call an agent makes at 3am. You either become a rubber stamp clicking approve without reading, or the whole pipeline grinds to a halt waiting on you.

So I asked a dumber, more mechanical question instead: what if the agent's permissions weren't fixed, and instead moved up and down based on whether its recent actions were actually working?

That's basically an error budget. SRE teams have used this idea for years for deployments, if a service is burning through its error budget, you freeze releases. I just pointed the same idea at an AI agent's tool calls instead of at deploys.

LEASH sits between an agent and the tools it's allowed to call. The agent doesn't get to decide for itself if it's trustworthy right now, SigNoz decides, based on real telemetry, and LEASH enforces it.

There are three tiers:

The agent starts at T3 by default. It stays there as long as its tool calls keep succeeding. The moment failures pile up past a threshold in a 5 minute window, SigNoz's alert fires, hits a webhook on my broker, and the agent gets demoted before it can do anything else destructive.

I split this into four small services instead of one big one, mostly because I wanted the "agent" and the "thing enforcing policy on the agent" to be genuinely separate processes, not just two functions in the same file pretending to be separate.

agent-runner

  • the fake release agent. Calls tools like read_release_notes

, apply_migration

, delete_staging_table

.leash-broker

  • the actual gateway. Every tool call from the agent goes through here first. It checks the current tier, decides allow or deny, and exposes the webhook endpoint SigNoz hits.migration-tool

and resource-tool

  • dumb downstream services doing the actual work, with a flag I can flip to make them start failing on demand.Every one of these is instrumented with the OpenTelemetry Python SDK, shipping spans, metrics and logs straight to SigNoz over OTLP.

The part I actually spent the most time getting right wasn't the policy logic, it was making sure the span names and attributes told a story on their own. leash.policy.decision

carries the current tier, the required tier, and the verdict. leash.tool.execute

carries the actual call. If you open a trace in SigNoz without reading any of my code, you should be able to tell exactly why a call was allowed or denied just from the span attributes. That's the whole point of doing this with SigNoz instead of just writing decisions to a log file somewhere, the trace itself becomes the evidence.

This is the one piece where I think a lot of hackathon projects fake it, so I want to be specific about what's real here.

I set up a metric alert on leash_tool_calls_total

, filtered to tool_name = apply_migration

and outcome = error

, aggregated with sum, threshold >= 3, over a 5 minute window. Nothing fancy. When it fires, SigNoz POSTs straight to http://<host>:18001/webhooks/signoz/demote

with a token header I check on my end, and a payload with the agent id and target tier.

Getting the webhook payload shape right on the first try didn't happen. I had to actually trigger a real failure in my migration tool, watch the alert fire in the SigNoz UI, and look at what actually landed on my endpoint before I could write the handler properly. Reading the docs told me alerts could call webhooks. It did not tell me exactly what json I'd get on the other end for my specific alert type. That's the kind of detail you only get by doing it.

Once the failure budget is burned, the agent tries to call delete_staging_table

, which needs T3. LEASH intercepts it and sends back a 403 with a body like this:

{
  "error": "AUTONOMY_TIER_DENIED",
  "agent_id": "release-agent-01",
  "current_tier": "T1",
  "required_tier": "T3",
  "reason": "migration_error_budget_exhausted",
  "trace_id": "de4b97f1896593b813ca4cac9e280584"
}

That trace_id isn't decoration. You can take it, paste it into SigNoz, and pull up the exact three failed migration spans that caused the demotion. The agent isn't refused because a prompt told it to behave. It's refused because there's a paper trail of real failures sitting in SigNoz that says it shouldn't be trusted right now.

If you're instrumenting something similar, don't skip emitting your own decision as a span. It was tempting to just log allow/deny to stdout and move on. But once leash.policy.decision

became a proper span with its own attributes, I could actually see the whole tier history of an agent run just by looking at one trace waterfall in SigNoz. That single choice made debugging my own policy logic about five times faster, because I wasn't guessing why a call got denied, I was reading it off the span.

Also, test your alert threshold with real failing calls before you trust it. I originally set my window to 60 seconds and it fired way too eagerly during normal jitter. Bumping it to 5 minutes and requiring 3 consecutive failures instead of 1 made the difference between an alert that's actually meaningful and one that's just noisy.

Right now the tiers are hardcoded per tool. In a real deployment you'd want the risk classification of a tool call to be configurable without redeploying the broker. I also only wired up one alert rule for one failure mode (migration errors). The interesting next step is generalizing this so any metric crossing any threshold can demote any agent, not just this one hardcoded path.

Prompt instructions telling an agent "don't do destructive things if errors happen" are not enforcement, they're a suggestion the model can ignore, misread, or get talked out of. What actually enforces something is a system outside the model's control, reading real telemetry, and physically blocking the call. That's what SigNoz's alerts and OpenTelemetry's traces gave me here that a prompt never could.

Code's here if you want to poke at it: github.com/Vaibhav13Shukla/LEASH

Built for the Agents of SigNoz hackathon, Track 1.

── more in #ai-agents 4 stories · sorted by recency
── more on @signoz 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/building-an-autonomy…] indexed:0 read:6min 2026-07-26 ·