Your agent doesn't crash when it goes off the rails. It just keeps billing you[p]
It doesn't throw. It doesn't return malformed JSON. It just quietly stops doing what you asked - losing the thread, repeating itself, answering a question nobody asked - and keeps burning tokens for every step after that.
Constrained decoding guarantees the shape of the output. It has no opinion about whether the agent is still doing the work.
So I built a detector for that and open-sourced it:
from driftguard import AgentWatch
watch = AgentWatch(task="the objective you gave the agent")
for step in loop:
out = agent.step()
if watch.observe(out).drifting:
halt()
The two signals
Two signals, both measured against the agent's own history:
relevance- is the output still about the task it was given?self-drift- has the output distribution moved away from what this agent produced while it was working?
Neither needs an external notion of "correct." The only assumption is that your agent used to be self-consistent and on-topic - which is the only thing you can actually check without a human in the loop.
What counts as drift
Drift is not one bad step. One bad output is noise. Drift is the rate rising and staying risen against this agent's own baseline, measured in standard errors, called only when the breach holds across 25 consecutive windows. An earlier one-window version fired on healthy agents - that's exactly why the requirement exists.
Measured results
Measured:
- 400-step agent, derails at step 200 โ drift called at step 228 (28-call latency)
- healthy agent, 600 steps ร 3 trials โ zero false alarms
Known limits
Limits, up front:
- Relevance is bag-of-words by default - no model, no API call, zero cost per step. Swap in embeddings if your agent drifts semantically while staying lexically on-topic; the statistics downstream are identical.
- The ~28-call latency is what buys the zero false alarms. A detector that fires in one call fires on healthy agents too - measured, not assumed.
- It tells you to stop. It does not fix the agent.
- The shipped demo uses stdlib docstrings vs stdlib source so it has no dependencies, and those are only ~1.6ร separated - which makes the demo's latency look worse than the real number. It's in the README rather than hidden.
- No dependencies, Python 3.10+, offline.
- https://github.com/devkancheti4-design/hal
The parameter I'm least sure about is the 25-window hold - it's probably too conservative for short agent runs. If you're running loops under 100 steps I'd genuinely like to know what you'd want there.
Comments
No comments yet. Start the discussion.