From the open engine’s docs (traza 0.28.0). View source Engine routes reach a Traza Cloud project through the gateway.
Run fuses
A fuse holds an agent run to limits — cost, tokens, steps, LLM calls, tool calls, duration, repeated steps, consecutive failures — and gives the run a verdict:
| Verdict | Means |
|---|---|
continue |
Nothing is near a limit. |
warn |
The run is at 80% or more of some limit (or reached one under a warn policy). It may go on. |
stop |
The run reached a limit under a stop policy, or an operator stopped it. Sticky: it stays stop until the run is reset. |
The store computes verdicts from the spans it ingests. Nothing here calls a model or runs a judge: a fuse is arithmetic over what was recorded.
The ack is the brake — and it brakes the next step
Spans arrive when operations end. By the time an LLM call’s span reaches Traza, the call has been made and paid for; by the time a tool span arrives, the tool has run. So the verdict an ingest acknowledgement carries can only stop what comes after the batch it acknowledges:
step N runs ─▶ its span ends ─▶ exporter sends it ─▶ ack says "stop" ─▶ step N+1 must not start
And exporters batch: the OpenTelemetry SDK’s batch processor sends every 5 seconds by default, so an agent can take several more steps between crossing a limit and hearing about it. Two consequences follow, and the API is shaped around both:
- The pre-call gate is
POST /v1/fuses/check. Asked before a step — optionally with the step’s estimated cost, tokens and tool — it answers whether the step may run. A step that would go past a stop limit answersstop. This is the only surface that can refuse a step before it happens, and it is what an agent framework or a hook (see the Claude Code hook pack) should call. - The post-ack verdict is a backstop. Every ingest response names the
runs in its batch whose verdict is not
continue, so a client that does nothing but export still learns its run was stopped — at least one batch late.
The two agree on where a limit is. Recorded usage at a limit has reached
it: a run holding max_steps: 50 whose fiftieth step is recorded is stopped,
so the fifty-first should not start. A hypothetical next step reaches a limit
only by going past it: a run at 49 of 50 may take its fiftieth. So the two
never disagree about step 51 — the check refuses it, and the acknowledgement
of step 50 says stop — though a client relying on acknowledgements alone may
already have started it.
Quick start
Give a service a policy, then ask about a run. Every response below is real
output from a server started with --fuse-max-steps 50.
curl -X POST http://127.0.0.1:8080/v1/fuses/policies -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"}'
{"action":"stop","created_at_ns":1792062221773128000,"id":"fp_58ed88d43fc762ba","limits":{"max_cost_usd":2.0,"max_error_streak":3,"max_repeats":4,"max_steps":40},"match":{"service":"support-agent"},"name":"support","tenant":""}
An agent of that service calls search_orders with the same arguments four
times. The batch carrying the fourth call is acknowledged with the verdict,
in the body and in a header:
HTTP/1.1 200 OK
Traza-Fuse: stop; run=sess-7f3a
{"accepted":1,"durability":"wal","fuses":[{"reasons":["max_repeats 4 reached: tool:search_orders#465a6bab9a1c447d repeated 4 times in the last 50 steps"],"run":"sess-7f3a","tenant":"","verdict":"stop"}]}
Before its next step, another run of the same service asks the gate, with an estimate:
curl -X POST http://127.0.0.1:8080/v1/fuses/check \
-d '{"run": "sess-9c21", "next": {"est_cost_usd": 0.40}}'
{"evidence":[{"span_id":"b1","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"}],"grant":null,"limits":{"max_cost_usd":2.0,"max_error_streak":3,"max_repeats":4,"max_steps":40},"policy":"support","reasons":["max_cost_usd 2 would be exceeded by the next step: $1.7 spent + $0.4 estimated"],"run":"sess-9c21","source":"live","tenant":"","tripped_at_ns":null,"tripped_by":null,"usage":{"cost_usd":1.7,"duration_ns":900000000,"error_streak":0,"errors":0,"llm_calls":1,"steps":1,"tokens":1920,"tool_calls":0,"top_repeat":null},"verdict":"stop"}
The check changed nothing: usage is what the run has recorded, and the run
itself is still warn (85% of its budget). Every route is in the
HTTP reference.
See it run
examples/guard/run.sh drives this page
against a real server in a few seconds. A scripted agent loops on a failing
tool until max_repeats stops it: the acknowledgement of the step that
crossed the limit carries Traza-Fuse: stop, the verdict names the reason
and points at the repeated span, and the pre-call check refuses the next
step. The kill switch stops a second, healthy run. A reset of the first run
answers warn, because it does not refill what the loop spent, and the
pre-call check refuses the next step under max_cost_usd: what the run has
spent, priced by the server from a --pricing file, plus the agent’s estimate
for the step would pass the limit. A grant then approves the run to finish.
The last beat runs the
Claude Code hook pack
against the same server. The verdicts, reasons, usage and run costs it prints
are the server’s answers. The per-step prices are the agent’s own arithmetic
on the same pricing file, and the demo asserts that the server’s cost for the
run equals their sum. It exits non-zero if any claim fails.
examples/foresight/run.sh runs a
predictive fuse: a policy’s
min_success_odds stopping a run at the pre-call check because earlier runs
on its path mostly failed, while a run on a healthy path is allowed.
Runs
A run is one unit of agent work within a tenant. Its id is the span’s
session id (session.id, then gen_ai.conversation.id, then the Traceloop
association properties) or, for a span with none, trace:<trace_id>. So a
conversation spread over many traces is one run, and an uninstrumented trace
is a run of its own. The full rules are in
LLM semantics.
Membership is decided per span. A trace whose root carries session.id
but whose children do not puts the children in trace:<id>, not in the
session — instrument every span of a run with its session id, or the run
the fuse sees is smaller than the one you meant.
What a run has used:
| Usage | Counted from |
|---|---|
steps |
Spans whose step kind is LLM, tool or agent. Retrievals, embeddings and wrapper spans are context, not steps. |
llm_calls, tool_calls |
Steps of those kinds. |
tokens |
Every span’s reported total, else prompt plus completion — the same sum the session analytics report. A framework that reports usage on both a model call and the agent span around it is counted twice here, exactly as it is there. |
cost_usd |
Every span’s priced cost: a metered llm.cost_usd, else one derived from --pricing. |
errors |
Spans with status error, of any kind. |
error_streak |
Consecutive failed steps at the end of the run. A successful step ends it; a non-step span neither extends nor ends it. |
duration_ns |
First span start to last span end, by the spans’ own clocks. An idle gap counts once the next span arrives. |
top_repeat |
The step fingerprint seen most often, its count and an example span. |
top_handoff_cycle |
The pair of named agents control bounced between most — A → B → A returns, since the last reset — and how often; present only once there is one. See handoff cycles. |
A fingerprint is a step’s kind and name plus a hash of its input —
tool:search_orders#465a6bab9a1c447d — so two steps share one exactly when
they made the same call with the same input. That is what a loop looks like.
The definition is in LLM semantics.
A span that is ingested again is an upsert here, as it is in the store: a
client retrying a batch the store already acknowledged changes no count —
not even the error streak — and a span re-sent with different content
replaces its earlier contribution. One re-sent with only a later end time
extends the run’s duration_ns (a re-sent span that ends earlier leaves
it where it was: the bounds only widen, which errs toward the limit). When
two requests write the same span concurrently, the version the store kept is
the one counted, whichever request’s acknowledgement happens to be observed
last — including while the run is being rebuilt from the store. And when one
batch carries two versions of a span, only the later — the one the store
keeps — is counted, at the place its own start time gives it among the
batch’s steps. A batch that re-sends several spans has every replaced
contribution taken out before any replacement is counted, so it is never
judged on new versions of some spans beside old versions of others: one
that moves $5 of cost (or tokens, or a repeated step) from one span to
another is judged on the totals the store’s versions reach, never on one
that counts the moved amount twice, and stops the run on a cumulative limit
exactly when a replay of the store would.
Policies
A policy is a name, which runs it is for, a set of limits, and an action:
| Limit | Reached when |
|---|---|
max_cost_usd |
cost_usd >= limit |
max_tokens |
tokens >= limit |
max_steps |
steps >= limit |
max_llm_calls |
llm_calls >= limit |
max_tool_calls |
tool_calls >= limit |
max_duration_seconds |
duration_ns >= limit × 10⁹ |
max_repeats |
top_repeat.count >= limit: one step fingerprint occurred that often among the run’s last repeat_window_steps steps — see repeats |
max_error_streak |
error_streak >= limit |
min_success_odds |
Runs that took this run’s path succeeded so rarely that the 95% interval’s upper bound is below the limit — see predictive limits |
max_handoff_cycles |
top_handoff_cycle.count >= limit |
Every limit is optional and an absent one is unbounded. A present one must be
positive, and max_repeats at least 2 — one occurrence is not a repeat.
repeat_window_steps (default 50, at most 1,000) is not a limit but the
window max_repeats counts over, and must be at least max_repeats when both
are set. action is stop or warn (default warn, matching --fuse-action).
Repeats count within a window
A step’s fingerprint is kind:name#hash of its input: the same call with
the same input. max_repeats counts how often one fingerprint occurs among
the run’s last repeat_window_steps steps (default 50), in step order
(start time, then span), since its last reset — not over the whole run. A
loop repeats within a few steps of itself and trips; a long interactive
session that takes the same screenshot once an hour for three days spreads
its repeats over thousands of steps and never does, however long it runs.
Widen the window (up to 1,000 steps) to catch slower loops; usage.top_repeat
reports the count, the fingerprint, its latest occurrence and the
window_steps it was counted over, and the reason says so:
max_repeats 4 reached: tool:search_orders#465a6bab9a1c447d repeated 4 times in the last 50 steps.
A warn is about now: once the loop’s steps leave the window the warning
clears. A stop latches where the window first filled, and live state and a
rebuild agree on it whatever order the spans arrived in: a fold of spans that
arrive in step order judges each step’s window as it lands, exactly as a
replay of the store does; a batch that changes the run any other way — a
span starting before the latest one, a re-sent span that moved, changed its
input or stopped being a step, two steps starting at the same instant — is
judged by the fullest window anywhere in the run once the batch is folded,
which is what a replay latches on. Such a stop is timed by the batch rather
than by the step that filled the window.
Which policy a run gets. Exactly one, resolved at every evaluation, so an edit applies to live runs at once:
- the run’s tenant’s policy whose
match.serviceis the service of the run’s first span (several: the most recently created). First by start time, not arrival: a root agent span ends, and so arrives, after its children, and the policy must not depend on which arrived first — a rebuild, which reads spans in start order, picks the same one. The run’s earliest span, counting a batch’s own, is found before any span of the batch is judged, so a root sent behind its children in the same batch sets the policy they are judged under. A root arriving in a later batch cannot lift a stop its children already latched under their own policy (see limitations); - else the tenant’s policy named
default; - else the global policy from the server’s
--fuse-*flags, reported as"policy": "global".
A non-default policy must name a match.service, and default must not —
otherwise one would never be chosen and the other would be ambiguous. The
name global is reserved. A tenant keeps at most 256 policies (creating
another is 409), and a match.service is at most 256 bytes: a run’s
policy is resolved among its tenant’s policies on every acknowledged batch,
so one tenant’s policy count must not reach into every tenant’s ingest.
Limits do not inherit. A tenant policy that sets only max_cost_usd
holds its runs to that alone; the global policy’s repeat limit does not
reappear underneath it. Set every limit you want on the policy you mean.
Out of the box the global policy warns on 12 repeats within 50 steps and nothing else: the one limit that is safe without knowing the workload, and a warning, because a default that stopped someone’s agent would be a decision nobody made.
Predictive limits
The limits above are arithmetic over what a run has used. One limit looks
the other way — at how runs like it have ended. min_success_odds (with
foresight_min_support, default 30, and foresight_after_steps, default 3)
walks the run’s path so far down its agent’s Fate Map, and
when at least that many earlier runs took the same path, the run has at least
that many steps, and the upper bound of their success rate’s 95% interval —
exact, from the counts, not the four-place ci95 the verdict shows — is
below the limit, the policy’s action applies, with a reason like
foresight: runs that took this path succeeded 5 of 50 times (≤21.4%). On
the pre-call check, a named next tool is judged as the next step of the path.
The run is read as still going, so its last step is open: a run that has
issued one refund is compared with every run that issued at least one there.
It differs from every other limit in four ways, each on purpose: it is
evaluated on the check and verdict queries only, never on ingest; it reads
only a cached map, so a cold cache gives no foresight stop; it is not
latched — a prediction is made again at every question, and lifts if the
path moves on to better odds — and it never warns at 80%. The verdict
document carries the run’s foresight whenever the policy sets the limit.
The whole model is in the foresight guide.
Its memory. The guard keeps every live run’s behaviour path beside its
usage — what the predictive limits judge, and what the
live view shows foresight from: the
earliest 192 steps, 48 bytes each, maintained as usage is — each version of
a span that lands replaces the last, a rebuild replays it, eviction and
erasure drop it — and counted against the same 1,000,000-record budget as
span contributions (so step-heavy runs are evicted somewhat sooner than
they would be without it). The check reads that path from memory rather than
reading the run back from the store. --fate-cache-seconds 0 stops keeping
paths, since no map would read them; an embedder sets
FuseConfig::live_paths to false for the same, after which only runs a
predictive policy could govern keep one. A run whose state started before
paths were kept keeps none until the guard next rebuilds it, and is read
from the store meanwhile.
Stopping and resetting
A stop from a policy latches at the span that reaches the limit, wherever
that span falls in a batch: three failed steps and then a successful one,
sent together, stop a run held to max_error_streak: 3 exactly as they
would sent one at a time, or replayed from the store after a restart — the
success ends the streak, not the stop. A batch’s spans are judged in the
order a replay reads them — by start time, then trace and span id — not in
the order they were sent (across batches, see
limitations). And it is sticky. Later spans of the
run, a raised limit, a deleted policy, eviction from memory — none of them
lift it (but see
what is durable for the bound on
stopped runs held, and for restarts). Two things do:
- Reset clears the stop, the kill switch, the error streak and the repeat counts. It does not refill cumulative usage: a budget does not grow because someone pressed reset. A run reset while still over a cumulative limit stops again immediately, and the reset’s answer says so.
- A grant —
{"grant": {"max_cost_usd": 5}}on a reset — raises that run’s limits to the absolute values named, over whatever its policy says. Grants accumulate per limit, later ones winning, and survive restarts.
The kill switch stops any run at once, whatever its usage and policy, until it is reset. It is durable, and a killed run stays listed as stopped across restarts.
Durable controls are bounded per tenant: at most 10,000 runs with a kill
switch, a reset or a grant. Past that, a trip or reset of another run drops
the controls of the run reset longest ago — its grant and its reset stop
applying, which can only make it stricter — and when all 10,000 are active
kill switches, the trip or reset is refused with 409. A kill switch is
never dropped to make room.
What is durable, and what is rebuilt
Live usage is an in-memory cache over the store, folded from every
acknowledged batch on the ingest path. It is bounded, with no exceptions: at
most --fuse-max-runs live runs (default 100,000, shared by every tenant),
at most 1,000,000 span contributions across them (each a 48-byte record under
an 8-byte key, before hash-table overhead; the steps of the runs’ behaviour
paths count against it too — and each run also keeps its latest steps, at
most 1,000 and never more than it has, 24 bytes each, for the repeat
window), least recently active evicted
first, and any run idle for six hours evicted. A run is followed from its
first step: a trace of HTTP and database spans costs the observer a read
of each span and holds no state. A run id longer than 1,024 bytes is not
followed at all, and asking about one is 400.
A run evicted while stopped — by a policy or by hand — is parked:
its stop (reasons, time, evidence) and its usage as last seen stay in memory,
its per-span state does not. A later span of the run is answered stop, a
rebuild keeps the stop whatever the policy now says, and the run stays in
the stopped listing. Up to --fuse-max-runs runs are parked; past that the
oldest are dropped, and a policy stop dropped that way is recomputed by the
next rebuild under the policy then in force — which, if the policy was raised
or deleted since, no longer stops it. A dropped kill switch loses only its
place in the listing: the switch itself is durable.
The first time a verdict is asked for a run, the run is rebuilt from
its stored spans and the answer says "source": "rebuilt"; after that it is
answered from memory ("live") and kept current by ingest. The process
cannot know whether a run had spans before it began following it — the run
may predate a restart, or have been evicted — so it never assumes it did
not. Asking about a run no span carries — a fresh run, or a made-up id —
answers continue and holds nothing.
Part of a history answers only stop. What part of a run’s history
used is a lower bound on what the run used: enough to establish that it
must stop, never that it may go on. Two things leave the guard with part:
- A rebuild reads and extracts the run’s spans under the same budget as any
query,
--query-deadline-ms. If it runs out, what memory holds of the run — live state followed since some point, a parked stop, a kill switch — is all there is. If that stops the run (a kill switch, a stop latched live or parked, or a checked next step that would go past a limit on what is known), the answer isstop, markedlive; otherwise the question is refused with the deadline’s400, and the rebuild is retried next time. - A rebuild reads at most a run’s earliest 100,000 spans. If the run has
more, and those already stop it, the answer is
stop(exactly: a replay of the whole run stops at the same span). If they do not, the question is refused with400— and every later one too, without reading the run again, until the run is evicted, since its history only grows. Such a run answersstoponce something stops it — what arrives live is added to what was read, and a kill switch always works — but nevercontinueorwarn.
Either way a refusal is an error, not a 200: a hook configured to fail
closed (the Claude Code hook pack’s TRAZA_FAIL_CLOSED=1) blocks the
step, and one that fails open lets it through — the operator’s choice,
rather than a continue that would have let the step through either way.
fuses.jsonl in the data directory holds what spans cannot reconstruct:
policies, kill switches, resets and grants, one fsynced line each, replayed
at startup (a torn final line from a crash is healed; damage anywhere else
refuses startup rather than silently dropping a later kill switch). A change
whose append fails part-way — a full disk — is refused and its fragment cut
back out of the file, so the next change cannot be written onto it; if even
that cut fails, every further change is refused with 503 until a restart
heals the torn tail. Most of
its lines are superseded sooner or later — a policy replaced, a kill switch
reset — so once it holds at least twice the records that reproduce the state
(and at least 1,024 lines) it is rewritten to just those, at startup or on the
next change; an erasure rewrites it too, without the erased subject. Both
rewrites are atomic, by rename. It lives
outside the generation layout, beside the store rather than in it: a
backup does not carry it, and a restore neither replaces nor resurrects it.
Fuse settings are operator configuration about the running service, and a
restore that reinstated last month’s limits — or lifted a kill switch pulled
since — would be the more surprising outcome.
What a restart loses, and what it does not:
| After a crash restart | |
|---|---|
| Policies, kill switches, resets, grants | Kept (fuses.jsonl). |
| A run’s usage | Rebuilt from the store on first query. |
| A policy stop | Recomputed by the rebuild under the policy in force then; tripped_at_ns becomes the end time of the span that crossed the limit, since the original server-clock moment was in memory. |
| The repeat counts and error streak after a reset | Recomputed from spans that ended after the reset, comparing span clocks with the server’s reset time. |
| The event feed | Starts empty; a cursor from before the restart reads as a gap. |
| “This warn was already announced” | Lost: a rebuilt run that is still warning is announced again on its next transition. |
Watching
- Live runs —
GET /v1/runs/liveand its stream — show the runs working now, with the step each is on and its verdict, from this same live state; and an operator can nudge a run, or veto its next step, through the pre-call check it already makes — a check naming itsnextstep; one without is a verdict read and takes no nudge. - The event feed records every
transition as it happens — a run entering
warn, a policystop, amanual_stop, areset— in a ring of 4,096, read by cursor with an optional long-poll of up to 30 seconds. An alert dispatcher holding an unbound credential polls it for every tenant. A run’s warn is announced when it enterswarn, and once more if awarnpolicy’s limit is then reached — the moment astoppolicy would have stopped it — and not again until the run returns tocontinueor is reset. GET /v1/fuses/runslists the runs the guard holds — live and parked — by verdict, most recently active first, from memory alone: a partly followed run lists with what this process has seen, a parked one with its usage as last seen live./v1/metricsreportstraza_fuse_trips_total(policy and manual stops),traza_fuse_warnings_totaland thetraza_fuse_live_runsgauge (live runs only; parked ones are not counted).- MCP:
check_run_guardlets an agent read its own run’s verdict. It cannot reset or trip anything — the MCP surface has no actuators.
Tenancy
Policies, runs, kill switches and events are all per tenant. A
tenant-bound credential reads and writes only its own: its policies are
created under its tenant, naming another tenant anywhere is 403, and
another tenant’s policy id is a 404. The same run id in two tenants is two
runs under two policies. An unbound credential naming no tenant addresses the
default tenant on the single-run routes and every tenant on the listings.
POST /v1/fuses/check needs only the ro scope, though it is a POST: an
agent holding a read-only token must be able to ask whether it may proceed.
Every other POST here needs rw.
Limitations
Stated plainly, so none of them is discovered in production:
- A post-ack stop is late by construction — at least one step, more with exporter batching. Only the pre-call check refuses a step before it runs, and only if the agent asks.
- A step is never interrupted. A single LLM call or tool run that runs away on its own is not stopped mid-flight; the fuse acts between steps.
- The check sees recorded usage only. Steps in flight — started, not yet exported — are invisible to it; parallel tool calls can all pass the gate before any of them is recorded.
- A run with no spans yet has no service, so a check before its first
span resolves the tenant’s
default(or the global) policy, not a service-matched one. - Across batches, arrival order counts. Within a batch, spans are
judged in step order under the policy of the earliest span known, as a
replay judges them; spans in later batches can only be judged as they
arrive. Children that reach a stop limit of their own service’s policy in
a batch before their root’s therefore stay stopped — sticky like any stop
— although a rebuild after a restart, reading the root first, judges them
under the root’s policy. Likewise a failed step arriving after steps that
started later extends the error streak where it arrived, not where it
started, and a named step arriving after later-starting ones is judged
against
max_handoff_cyclesas it lands — counted where it started, but possibly on a cycle a step still in flight would have broken. Where a run’s services have different policies, keep the child services’ limits no stricter than the root’s. - Concurrent re-sends that move usage between steps can stop a run on a total it never held. Within one batch the guard withdraws every replaced version before landing the new ones, so a batch that moves cost from one step to another is never judged half-moved. Two separate requests are observed after their acknowledgements, in whichever order their threads finish: if one request re-sends step A with less usage and a concurrent one re-sends step B with more, the guard can briefly hold B’s new usage beside A’s old and latch a stop on that sum, which neither the store nor a rebuild ever sees. It takes concurrent re-ingestion of already-sent spans of one run with different usage; ordinary first sends only add, and only add once. Send such corrections in one batch, or reset the run if it happens.
- After a restart or an eviction, ingest verdicts for a continuing run see only what arrived since, until something asks for the run’s verdict and it is rebuilt. The pre-call check is such a question, so an agent that checks before each step is judged on its whole history from its first step after the restart. A stop survives eviction (parked), but not a restart: a rebuild after one recomputes it under the policy then in force, and does not re-announce it on the event feed.
- A run of more than 100,000 spans gets no verdict short of
stop. A rebuild reads its earliest 100,000 only, which cannot clear it (see part of a history). A run first asked about while still shorter — as the run of an agent that checks before every step is — is rebuilt whole and then followed live past that length, in full, until it is evicted or the server restarts. - Tenants share the live bound. One tenant with many active runs can evict another’s; those are rebuilt when asked about, and their stops are parked, but their ingest verdicts see only new spans until then.
- Clocks. Duration uses the spans’ own timestamps. A rebuild compares span end times with the server’s reset time, so a client clock far off the server’s can put a span on the wrong side of a reset.
- Fingerprints depend on how input was stored. An input large enough to
be offloaded hashes by its content address, an inline one by
its canonical JSON, so the same input stored both ways is two
fingerprints. Under one
--payload-threshold-bytesthe same input is always stored the same way. - Erasure removes what the guard holds about the subject, not what the
guard concluded from it. The guard keeps no span text, but it does keep
identifiers: an erasure drops every live run
a purged span contributed to (rebuilt, if asked about again, from what the
store still holds) and every parked run it names or whose evidence names a
purged span, removes an erased session’s or trace’s events and its kill
switch, reset and grant, and a tenant erasure takes the tenant’s runs,
events and policies wholesale; for a tenant, session or trace subject
fuses.jsonlis rewritten from what remains. Work in flight cannot undo it: a rebuild that read the store before the erasure discards what it read and reads again, and an ingest acknowledged just before the erasure began — whose observer can run after the guard was swept — has its observer skip the spans the pending erasure covers. The one window left is such an observer stalled until after the erasure has settled, which would follow the run again from the erased spans until it is next rebuilt or evicted. Atraza_fuse_*counter, and an alert a dispatcher already sent from the event feed, are not taken back. A kill-switch reason belongs to its run, so it goes with an erased session; a span erasure that leaves the session standing leaves the reason too — keep personal data out of reasons. - One process. Live state is per server; there is no shared state across replicas.
Disabling
--no-fuses turns the observer off entirely: ingest does no fuse work,
fuses.jsonl is not read or written, responses carry no fuses field or
header, every /v1/fuses/* route answers
409 {"error": "run fuses are disabled on this store (traza-server --no-fuses)"},
and MCP does not list check_run_guard.