← All writing

Writing · 2026

Deterministic policy does not make an AI guardrail deterministic

On this page

What rebuilding Intaris taught me about interpretation, false alarms, and the cost of supervising an agent.

A robot asks, 'Can I water it now?' while a robot approval committee meets beside a wilting plant.

I wanted a guardrail that would leave an agent alone when it was doing what I asked, and stop it when it was about to do something I would not allow. Preferably without asking me to approve the twentieth ordinary file edit.

That is the problem I have been working on with Intaris. In my previous article, I wrote about why isolation does not answer whether an allowed action is what the user wanted. This is about what happened when I tried to answer that next question.

One of our design decisions was to separate model assessment from policy. Let a model interpret the user's intention and classify the proposed action. Let ordinary code decide whether to approve, deny, or escalate it.

I still like that separation. But moving a model from deciding to classifying did not remove uncertainty from the outcome. A deterministic policy applied to a wrong interpretation can consistently produce the wrong decision.

Why there is a model in the loop

Some controls belong entirely in code. Is the path inside the workspace? Is the recipient on an allowlist? Does this purchase exceed a spending limit? Has the user required confirmation before sending? If a condition can be expressed precisely and checked from trusted data, there is no reason to ask a model.

Intention alignment is harder. Consider "prepare a reply", "send this reply", and "handle this for me". The proposed email tool call can be identical. Whether it is appropriate depends on the conversation, the recipient, the task, and any limits the user set earlier. A later "yes" might approve sending, approve a wording change, or answer an unrelated question.

We can make the user translate all of that into explicit grants, or restrict the agent to a fixed workflow. That works for some tasks. It is not the general-purpose assistant I want.

For that assistant, something has to interpret the conversation. Today that means an LLM. The important problem is not whether its sampling is random. A model that returns the same wrong interpretation every time is still wrong.

The design looked sensible

The pipeline we experimented with had four parts.

First, an intention generator maintained a compact representation of the user's current task. We used GPT-OSS-120B through Groq. For this workload, I found it a good performance/cost trade-off. Instead of keeping only a paragraph of prose, we used a rich schema with requests, restrictions, approvals, their status, and quotes from the user's instructions.

Next, Jev classified each proposed tool call. It assessed effects, risk, and alignment with that intention. It returned typed answers and probabilities rather than a written safety assessment.

Deterministic policy combined those answers with controller observations, configured rules, and the selected profile to produce a decision.

Finally, an optional Judge reviewed eligible escalations before interrupting a human. We also used GPT-OSS-120B for this. The question was essentially: would the user approve this request? The Judge received richer source context. In an early version, it also received the evaluation's conclusion.

This gave each component a clear job. It also gave us several places where an interpretation could go wrong.

A rich intention is still an interpretation

Maintaining intention sounds like summarization until you try it on a conversation that keeps changing.

The model has to understand the current request in the context of assistant messages, distinguish a proposal from an instruction, resolve short confirmations, and track which earlier constraints still apply. It must not turn permission into a new task, or an assistant's plan into user authorization. That is demanding work even for much larger models.

The rich schema helped us retain specific information. It did not make that information correct. A quote can exist verbatim in the source while being assigned the wrong meaning, scope, or status. An accurately quoted approval for one email is not approval for another. A perfectly structured intention can also be stale.

The evaluator then sees our representation of the user's intention. It does not see the intention itself. It can classify an action correctly against a bad representation and still produce the wrong outcome.

This is also why a compact intention layer remained useful. Feeding an entire growing conversation into every evaluation is expensive, and the evaluator has context limits. Jev's documented default limits are 64k tokens per request and 32k for the state plus an individual question. TypeSafe also offers an extended-context option, but more room does not solve the interpretation problem. Its own documentation notes that irrelevant context reduces accuracy.

We need to select and compress the conversation. Selecting what matters is part of the hard work.

Jev made classification cheap, not interpretation easy

Jev was fast and inexpensive. It was not a shortcut to a more accurate system.

A decision model is attractive here: ask narrow questions, receive bounded answers, apply thresholds in code. But that output format should not be confused with contextual reasoning. Jev is designed for System One decisions, not the kind of multi-step reasoning needed to reconstruct changing permissions across a messy conversation.

In our experiments, it worked better when the relevant facts and the question were already clear. Sometimes it classified a call incorrectly despite a useful intention. Sometimes the intention itself was wrong or outdated. We did not get a clean improvement in end-to-end accuracy simply by putting Jev in the evaluator seat.

These are not just integration concerns. TypeSafe's documented failure modes include literal reading, indirection, irrelevant context, and adversarial content. Those are all relevant to agent supervision.

There is a trap here: if another model must first resolve the difficult interpretation so that a cheap classifier can answer an easy question, we have not made the difficult part cheap or reliable. We have moved it upstream.

We also learned to inspect the actual model input rather than admire the schema. In one experiment, a richer structured intention made the small generative evaluator worse even when the relevant restriction had been extracted correctly. More information was present, but the action was buried behind metadata. A tidy data structure for the controller was a poor reading experience for the model.

The Judge inherited our mistakes too

The Judge was supposed to remove unnecessary escalations. It could read more context and decide whether this was something the user would actually allow.

An early version often deferred to the evaluation result we had included in its input. We asked it for an independent judgment, then handed it a conclusion to follow. Removing that verdict helped address the anchoring, but adding a Judge did not turn the pipeline into a reliable committee.

One failure made this particularly clear. The user had prohibited printing credential values. The intention retained the restriction, and Jev flagged a conflict. The call went to review anyway, where the Judge approved some repeated trials on the reasoning that the action only read data rather than printed it.

Our controller had contributed to the mistake: it described the operation as a read. But reading a secret into logs or chat is disclosure. The relevant question was not just whether the command modified state.

The later stage trusted a misleading description over the action's actual consequence. We added deterministic disclosure handling for that case. The lesson was broader than the fix: controller facts need scrutiny too, and a second model can repeat or amplify the framing error it receives.

The Judge also normally sees escalations, not every call. If an earlier stage incorrectly approves an action directly, the richer review never happens.

We did not find the perfect threshold

We never reached the combination I wanted: approve everything the user would obviously approve, and stop everything they would reject.

Making the system stricter caught more questionable actions but also blocked legitimate work. Relaxing it reduced interruptions and let some unwanted actions through. Adding context, richer extraction, or more review did not consistently improve both sides. Each also added cost or latency.

The separation between assessment and policy was still useful. I could inspect rules, test them, distinguish controller observations from model opinions, and keep hard restrictions outside model discretion. What it did not give me was a correct answer to every semantic question feeding those rules.

I do not have one neat explanation for every failure. We saw extraction mistakes, stale state, classification mistakes, input-format sensitivity, and Judges leaning on earlier conclusions. The errors were not independent, so stacking more layers did not make them cancel out.

For this workload, today's models are either too expensive, not accurate enough, or both. That is why the operating mode matters as much as the architecture.

Where I would use each layer

A guardrail has to justify its cost. That includes intention updates, classification, Judge calls, retries, latency, and the human attention consumed by false alarms. For routine use, I want supervision to cost less than the agent inference it supervises. Sending every Bash call to another capable agent is not an economical answer.

My defaults now depend on what the agent does.

Coding agents

For a coding agent in an isolated development environment, I would disable per-call model guardrails and L2/L3 behavioral analytics. Coding generates a huge number of tool calls. Many are legitimate but ambiguous when viewed as individual shell commands, which makes supervision expensive and interruptions frequent.

I would put the effort into sandboxing, disposable workspaces, restricted credentials, and controlled access to external systems. This assumes a real isolation boundary. A shell with production credentials is a different risk profile. Disabling model guardrails does not mean disabling permissions or execution controls.

Personal assistants

For a personal assistant, I would enable guardrails in balanced or bounded-autonomy mode, usually without L2/L3 analytics.

This is where intention checks add practical value. The assistant sends messages, changes calendars, manages purchases, and interacts with other people. I want it to notice the difference between preparing something and committing me to it. I also want it to do reasonable supporting work without asking me to name every action in advance.

When I am directly steering the conversation, continuous session and cross-session analytics usually add less value than checking the consequential action in front of me.

Autonomous agents

For an autonomous agent operating with less direct supervision, especially around untrusted content, I would enable behavioral analytics and use strict or balanced guardrails.

Individual calls are not the whole picture. An agent can gradually expand its scope or repeat a questionable pattern across sessions. L2 session summaries and L3 cross-session analysis help expose that behavior. They are monitoring layers; the execution boundary still needs its own controls.

These are different workloads. Giving them all the same supervision policy wastes money in one place and leaves gaps in another.

What I want from the next generation

I still think specialized decision models are a good direction. Jev is an interesting start. What I want next is a specialized open-weight model that I can self-host cheaply and that is accurate enough for this job.

Privacy matters here. A useful intention guardrail may need the user's instructions, tool arguments, correspondence, and other sensitive context. The supervisor can end up seeing much of what the agent sees. I would rather keep that on my own infrastructure than send it to another external service.

A cheap classifier alone is not enough. The system also has to keep up with changing intent, understand the effects of an action, and avoid treating its own earlier interpretation as authority. Either the specialized model handles more of that work, or we need a cheaper and more reliable way to prepare its inputs.

I expect substantial progress here. This kind of supervision is still young, and specialized self-hosted models are where I think much of it will go.

For now, I use guardrails where they earn their cost, isolation where it gives a clearer boundary, and analytics where nobody is watching every session. Moving uncertainty into a classifier did not make it disappear. It made it easier to see where we were depending on a model being right.

← All articlesHome →