Skip to main content
ZNYX AI
DetectionArchitecture

Rules vs. Models: Layering Deterministic and ML Detection

When to escalate from deterministic rules to an ML classifier: uncertainty bands, latency budgets, graceful fallback, and keeping inference in your boundary.

GGowtham RagothamanJune 5, 2026 · 10 min read
ShareLinkedInX

Every detection layer starts as a pattern. Someone writes a rule that catches the attack they just saw in production, it works, and for a while that feels like progress. Then an attacker rephrases, or a customer writes something perfectly innocent that happens to match, and you learn the two failure modes of deterministic detection in the same week. The usual response is to reach for a model, which trades those problems for a different set: latency, cost, and answers that arrive as a confidence score rather than a reason. The better move is to stop choosing. This post is about layering the two so each one handles the cases it is genuinely good at, and about the seams between them, which is where most of the interesting engineering lives.

Two Kinds of Wrong

Deterministic detection fails by being literal. A pattern matches what it was written to match and nothing else, which makes it fast, explainable, and free of surprises - you can point at the exact rule that fired and say why a request was blocked. It also makes it brittle. An attacker who rewrites ignore your previous instructions as disregard the guidance you were given earlier walks straight past a rule tuned to the first phrasing, and the maintenance treadmill of chasing paraphrases never stops.

Model-based detection fails the other way. A classifier trained on a large corpus of attempts generalizes to phrasings nobody wrote down, which is exactly what you want from it. But it answers with a probability rather than a reason, it costs tens or hundreds of milliseconds, and it will occasionally be confidently wrong in a way you cannot trace to any line of configuration. Run one on every request and the guardrail becomes the slowest component in your stack, which is how safety layers end up getting switched off.

The useful observation is that these two failure modes are not correlated. Deterministic checks are most reliable precisely where text is unambiguous, at both extremes: a PEM private key block is not a close call, and neither is a polite question about business hours. Models earn their cost in the middle, where the text is genuinely debatable and a literal rule has nothing helpful to contribute. That asymmetry is the entire basis for layering them, and it is a stronger foundation than any argument about which approach is better.

The Uncertainty Band

The mechanism follows directly from that asymmetry. Run the deterministic layer first on every request and let it produce a score. If the score is clearly low, allow and stop. If it is clearly high, act on the policy and stop. Only scores landing in the ambiguous middle escalate to the model. On a hundred-point risk scale, a band somewhere between roughly 35 and 70 is a reasonable place to start, though the right numbers are a property of your traffic rather than a universal constant.

The economics are better than they first appear. In most production traffic the overwhelming majority of requests are not close calls, so they never reach the model at all. You pay for inference only on the slice of traffic where inference could actually change the answer, and the median latency of your guardrail stays close to the deterministic path. A model you invoke on a tenth of requests can be five times slower than one you invoke on all of them and still win on both cost and perceived responsiveness.

The failure mode to watch for is a band that was never really configured. If the escalation condition is empty or absent, the sensible-looking default in many systems is to escalate always, which quietly converts a cheap two-layer design into an expensive one-layer design. We have watched deployments call a model on requests the deterministic layer had already scored at zero, several times over within a single response as multiple detectors each escalated independently. Nobody had made a decision to do that; nobody had set the band. Find out what your system does when the condition is unset, and then set it explicitly even if the default looks harmless.

  • Clear lows resolve deterministically and never reach the model, which covers most real traffic.
  • Clear highs resolve deterministically too - a classifier adds nothing to an unambiguous private key.
  • Only the debatable middle escalates, which is where a model's generalization is worth its latency.
  • An unset band often means escalate always. Verify the default rather than assuming it is off.
  • Watch for several detectors escalating the same text to the same model. That is duplicated inference nobody asked for.

Ordered Layers, Not a Toggle

It helps to stop framing this as rules-or-model and start treating it as an ordered sequence of strategies that a single detector runs. Deterministic first, a model second, and for a small number of genuinely hard questions a third semantic layer beyond that. Each layer sees the previous result and either accepts it or refines it, and a detector's configuration says explicitly which layers it uses and in what order.

Ordering by cost is the discipline that makes the structure pay off. Compiled patterns cost microseconds, a local classifier costs tens of milliseconds, and anything involving a large model costs hundreds or thousands. Arrange the layers cheapest-first, short-circuit as soon as one produces a confident answer, and the aggregate cost of your guardrail is dominated by the cheap layer that ran on everything rather than the expensive one that ran on a fraction of it.

This also keeps configuration honest in a way scattered application code cannot. When each detector declares its own layer order, its own escalation condition, and its own timeouts, you can read a policy and derive the worst-case cost of a request from it. The first time someone asks why a particular request took two seconds, that property is what lets you answer in minutes instead of days. The third semantic layer raises questions of its own - what it should be allowed to decide, and how you validate something that expensive - and it deserves a separate discussion rather than a paragraph here.

Additive, Never Subtractive

One design decision matters far more than it looks: what happens when the layers disagree? The deterministic layer flagged something, the model says the text is fine. Whose verdict survives?

Not the model's. The safe composition is additive - the model layer may raise the severity of a verdict or contribute new findings, but it never erases what the deterministic layer found. A pattern that matched a private key matched it, whatever a classifier concludes about the surrounding prose. Letting a probabilistic layer overrule a deterministic finding means a single model error can silently disable a check you deliberately wrote, which inverts the reason you added guardrails in the first place.

In practice this means merging results rather than replacing them: take the worse of the two decisions, union the findings, and preserve both layers' evidence in the decision record. The reviewer looking at a blocked request six weeks later needs to know whether a rule fired, a model fired, or both, and a merge that discards the loser's reasoning throws away exactly the information that makes the block defensible.

When the Model Is Slow or Gone

The moment you add a model call to a detector you have added a dependency that can time out, and what the guardrail does in that moment is a policy decision rather than an implementation detail. Give every escalation an explicit timeout, and decide in advance what the detector resolves to when that timeout fires. A model call with no deadline is a guardrail with no deadline.

For most escalations the right answer is to fall back to the deterministic verdict. You already hold a decision from the cheap layer; the escalation was an attempt to refine it, and losing the refinement is a smaller harm than failing the request outright. This is graceful degradation in its most literal form - the guardrail keeps working with reduced discrimination instead of going down, and users see a slightly blunter policy rather than an error.

That default is not universal, though, and applying it everywhere is its own mistake. For a check where an unrefined verdict is genuinely unsafe to act on, falling back to a deterministic maybe is worse than refusing, and the detector should fail closed. The point is not that one posture beats the other but that the posture is declared per check, sits in the policy next to the rule it protects, and can be reviewed and changed without a code deploy - rather than emerging by accident from wherever a try/catch happens to sit.

  • Set a per-escalation timeout. An unbounded model call is an unbounded guardrail.
  • Default to the deterministic verdict on timeout, so a slow model costs you discrimination rather than availability.
  • Fail closed instead for the narrow set of checks where an unrefined answer is unsafe to act on.
  • Declare the posture per check in policy, not in code, so it is auditable and changeable.
  • Record every fallback and alert on the rate. Checks passing because they could not run is a silent incident.

Keeping Inference Inside the Boundary

Adding a model layer reintroduces a risk the deterministic layer never carried. A regex inspects text in process. A model usually lives behind an endpoint, and if that endpoint is somewhere else, then the content you are inspecting - the prompt with a pasted credential in it, the support ticket full of personal data - now travels across a network in order to be judged.

This is the same trap as routing prompts to a hosted moderation API to find out whether they contain secrets: the inspection becomes the leak. The distinction that resolves it is whether the model call crosses your trust boundary. A classifier running as a co-located sidecar on loopback does not cross it; that call is as local as the regex was, and treating it as internal is correct. A classifier on any other host does cross it, and every protection you apply to model traffic - destination allowlists, residency rules, redaction, audit logging - has to apply to it too.

Get this wrong in the safe direction and you pay for some redundant auditing. Get it wrong in the other direction and raw, unredacted content leaves your infrastructure with no record that it happened. So treat the model's location as a security property of the configuration rather than a deployment detail: if the endpoint is not genuinely local, it is egress, and it should be configured as egress even when it is running on a machine you own. This is one of the reasons a self-hosted runtime suits a layered design - a runtime like ZNYX AI keeps both the deterministic layer and the model layer inside your own infrastructure, so escalating a decision to a classifier never becomes the reason your traffic leaves the building.

Tuning the Band With Data, Not Instinct

The band is the one parameter you cannot guess well, because it depends entirely on how your deterministic scores are distributed across your actual traffic. Two applications with identical policies can need different bands. The way to set it is to measure before you enforce anything.

Run the model layer in shadow first: invoke it on a sample of traffic, log what it would have decided, and change nothing about the live decision. Two numbers fall out of that exercise. The first is the escalation rate you would see at a candidate band, which tells you what it costs. The second is the disagreement rate - the fraction of escalated requests where the model's verdict differs from the deterministic one - which tells you whether escalation is buying anything at all. A band with a high escalation rate and a low disagreement rate is pure expense, and it is a common place to start.

Then move the bounds deliberately. Lower the floor and you catch more genuine ambiguity at more cost; raise it and you save money at the risk of resolving real close calls with a literal rule. Watch the tails while you do it: if a large share of traffic is piling up just outside the band on either side, your deterministic scoring is probably compressed into a narrow range and the band is the wrong knob to be turning. Fix the scoring first, because no choice of bounds compensates for a score that does not discriminate.

  • Shadow the model layer first, logging what it would have decided while enforcing nothing.
  • Track escalation rate for cost and disagreement rate for value; high cost with low value means the band is too wide.
  • Sample the disagreements by hand. That is where you find out whether the model or the rule was right.
  • Revisit the band whenever either layer changes. It is a property of the pair, not of the model alone.

The Takeaway

Rules and models fail differently, and a layered detector exploits that difference rather than picking a side. Let the deterministic layer decide the extremes, where it is fast and explainable and right. Escalate only the ambiguous middle, where a model's generalization is worth paying for. Order the layers cheapest-first and short-circuit early, so the common case never touches the expensive path.

Then handle the seams honestly, because that is where these designs actually break. Compose additively, so a probabilistic layer can never erase a deterministic finding. Give every escalation a timeout and a declared fallback posture, defaulting to the deterministic verdict and failing closed only where an unrefined answer is unsafe. Keep the model inside your trust boundary, or treat the call as egress with everything that implies. And set the band from shadow-mode data rather than instinct, remembering that a band nobody configured usually means escalating everything.

Done well, the result does not feel like a compromise between two approaches. It feels like a detector that is cheap when the answer is obvious and thoughtful when it is not, which is what you wanted from each of them separately in the first place.

Run it yourself

The detection runtime is open source and self-hostable. Everything described here runs inside your own boundary.

Secure every prompt, agent, and tool call, in your boundary.

Pull the open-source runtime, drop it into your stack, and start enforcing policy in minutes, free, forever. Add the hosted control plane when you want centralized policies, evidence, traces, and team workflows.