Guard on Traza Cloud
Guard is the engine’s run fuses, running on your project’s cell. This page covers what Traza Cloud adds around them; the model itself (runs, usage, verdicts, what is durable) is in the engine’s run fuses guide.
Policies
A policy holds the runs of one service to a set of limits. Create, list and
delete them with a full key, or in the console’s Guard tab:
curl -s https://api.trazadb.com/v1/fuses/policies \
-H "Authorization: Bearer $TRAZA_API_KEY" \
-d '{"name": "support", "match": {"service": "support-agent"},
"limits": {"max_cost_usd": 2.0, "max_steps": 40, "max_repeats": 4, "max_error_streak": 3},
"action": "stop"}'
A run gets the policy whose match.service is the service of its first span;
else your project’s policy named default, which takes no match; else the
cell’s global policy. Create a default policy so no run of yours depends on
the cell’s defaults. Limits do not inherit: set every limit you want on the
policy you mean.
max_cost_usd counts cost your spans carry in llm.cost_usd (or
gen_ai.usage.cost). If your instrumentation does not meter cost, hold runs to
max_tokens instead.
Predictive limits
A policy may also hold a run to its odds: min_success_odds stops (or warns)
a run whose path has historically failed, judged by the upper bound of a 95%
interval over at least 30 earlier runs of its agent that took the same path.
See Foresight on Traza Cloud.
And max_handoff_cycles stops a run whose agents keep handing control
straight back to each other; see Steer.
Ask before each step
The check is the only surface that refuses a step before it runs. Call it from
your agent loop, a framework hook or a tool wrapper, with an estimate of the
next step if you have one. Any key can ask, ingest included.
import os, httpx
def may_continue(run_id: str, est_cost_usd: float = 0.0, tool: str | None = None) -> bool:
nxt = {"est_cost_usd": est_cost_usd} | ({"tool": tool} if tool else {})
r = httpx.post(
"https://api.trazadb.com/v1/fuses/check",
headers={"Authorization": f"Bearer {os.environ['TRAZA_API_KEY']}"},
json={"run": run_id, "next": nxt},
timeout=5,
)
r.raise_for_status() # an error is not a verdict: decide whether to fail open or closed
verdict = r.json()
if verdict["verdict"] == "stop":
raise RuntimeError("; ".join(verdict["reasons"]))
return True
A stop is a 200: the verdict is the answer. An error response means Guard
could not decide (for example, a very long run it could only partly rebuild),
so choose deliberately whether your agent stops or proceeds.
A check that names the next step ("next": {...}) also delivers the run’s
pending nudges, once, in its nudges field: words a person queued for the
agent, or a block that answers stop. Hand them to your agent; see
Steer.
Stop and approve
- Kill switch:
POST /v1/fuses/runs/{run}/tripwith an optional{"reason": "…"}stops a run at once, whatever its usage. The console’s Guard tab and Mission Control have it on every run, and so do push notifications and Slack alerts. - Approve to continue:
POST /v1/fuses/runs/{run}/resetclears a stop. It does not refill cumulative usage, so a run over its cost limit stops again; pass{"grant": {"max_cost_usd": 5}}to raise that run’s limits.
Both need a full key. A run id containing / travels as %2F.
Alerts
In the console, under Alerts, add destinations and rules.
| Event | When |
|---|---|
fuse.stop |
A policy stopped a run |
fuse.warn |
A run entered warn (80% of a limit, or a limit under a warn policy) |
fuse.manual_stop |
Someone pulled the kill switch |
quota.warning |
The organization used 80% of its period’s spans |
quota.capped |
The organization reached its cap and ingest paused |
billing.payment_failed |
A payment failed |
A rule can cover every project or one. Destinations depend on the plan (see
pricing): email and push to your own devices on every plan, Slack
and webhooks from Pro. Rules can also follow a run’s foresight reaching
likely_failure (Foresight).
Send test on a destination delivers a sample at once, and the delivery
history lists it with what the destination answered. Failed deliveries are retried
with exponential backoff, from 30 seconds up to six hours apart, for up to ten
attempts.
Each event is delivered once per rule, and a rule is unique per event, project and destination; an organization can have up to 200 rules. If 1,000 deliveries are already waiting for an organization (a destination that is down or slow while many runs stop), new fuse alerts for it are skipped until the queue drains; quota and billing alerts are always sent. The delivery history keeps 30 days, up to the newest 10,000 deliveries.
Email carries the run, the verdict and a link to it in the console. Push notifications carry Approve, Kill and Open, each with a one-time token that expires after 15 minutes; see Steer. Slack messages carry the run, verdict, reasons and usage, with Approve, Kill and Nudge… buttons once the Traza app is installed; see Steer.
Webhooks
A webhook delivery is a JSON POST:
{"id": "dlv_…", "type": "fuse.stop", "created_at": …, "org": …, "project": …, "data": {…}}
type is the event from the table above and data carries the event: for a
fuse event, the run, its verdict, reasons and usage.
Each delivery carries a Traza-Signature: t=<unix seconds>,v1=<hex> header,
where v1 is the HMAC-SHA256, keyed with the destination’s secret, of the
timestamp, a ., and the raw body. Verify it before trusting the payload:
import hashlib, hmac, time
def verify(secret: str, header: str, body: bytes, tolerance_s: int = 300) -> bool:
fields = dict(part.split("=", 1) for part in header.split(","))
t, sig = fields.get("t", ""), fields.get("v1", "")
if not t.isdigit() or abs(time.time() - int(t)) > tolerance_s:
return False
want = hmac.new(secret.encode(), t.encode() + b"." + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(want, sig)
Webhook and Slack URLs must be HTTPS and may not resolve to private, loopback or link-local addresses.
Claude Code
The engine’s hook pack turns every Claude Code tool call into a guarded step. See Claude Code.