Skip to main content
Lab Grimoire
TW EN
Coffee
Agent Architecture

Ask before the agent acts: gate tool calls with a decision model

Rules set the floor for a tool call, and a decision model may only tighten it. Use 20 synthetic calls to choose a hosted API, open weights, or raw logits.

Author
CY
Published
Updated
Decision Models: AI That Chooses Instead of Writes · Part 5 of 5
Ask before the agent acts: gate tool calls with a decision model

Before an agent runs a tool, hand the request and a set of pre-listed options to a decision model, get a probability for each option, and let code decide whether to run it, block it or ask a person. This part covers how we designed that checkpoint, the results on 20 test calls, and whether to choose a hosted service, open weights or raw logits.

Part 5 of the series | This part answers: should a decision model guard tool calls, and should you pick a hosted service, open weights, or read logits yourself?

Why an agent should ask before it acts

Before a tool call runs, it should get probabilities over a fixed set of options and let code decide what to do; the decision model supplies probabilities and never rewrites policy.

Agents delete files, send requests and read configuration, and some of those actions cannot be undone. A decision model suits a pre-execution check because it stops after one forward pass, writes no text and leaves nothing to parse (parts 1 and 2 cover the definition and mechanism).

Our decision-model client splits the gate into two questions. action is a choice with four defined options: allow is safe and reversible, or a local read the policy explicitly permits; deny violates standing policy, such as destructive deletion, secret exfiltration or unauthorized access; escalate is a borderline or high-impact case that needs a person or policy owner; ask_user means consent is missing or intent is unclear, so ask the user first. high_risk is a noul asking whether running the request could cause data loss, external side effects, credential exposure or an irreversible change. If the call fails, it falls back to action escalate and high_risk true.

Think of four pre-printed forms at a gate: the guard can stamp one of them, but cannot write a new rule on the spot.

The default is shadow mode, which logs without blocking; enforce needs two explicit switches, and any error fails closed (treated as not allowed). Tool input goes only to a local endpoint by default, file content is cut to 2,000 characters, and the shadow log keeps 200. One point is easy to miss: "is it loopback" is not the same as "does the data stay on this machine"; a local port that forwards to another machine is always treated as remote.

Rules first; the model may only tighten

Standing rules set the floor; the decision model may make a verdict stricter, never looser.

Strictness runs allow, then escalate and ask_user (same level), then deny; the merged verdict is the stricter of rule and model, and high_risk never drops below the rule's value. The rule came from a code review: an early draft let the model loosen escalate to allow, and review removed that.

The rule floor is an adjacent-token regular expression plus a linear token scan; any hit means deny, and every match runs in linear time. It went through six consecutive rounds of code review covering truncation blind spots, ReDoS, padding bypasses and parser differentials. Even so, it is a best-effort blocklist, not a security boundary.

PII columns can be added, never removed

The same tighten-only principle applies to auditing personal-data columns: the decision model casts a second vote that can only add suspected columns, never remove one. On the hard synthetic set from 2026-10-11 (disguised column names), the decision model alone caught 5/5; identifying columns scored 0.398 to 0.977 and non-identifying columns at most 0.032. The threshold has to be 0.3, because the ref_no column scored only 0.398. It takes about 1.2 seconds per column.

Results on 20 tool calls

On 20 synthetic tool calls on 2026-10-11, action accuracy was 85% with 0 critical misses; this describes that synthetic set only and is not a general accuracy.

The setup was a Mac running Clef-flash with MLX at 4-bit and an NVIDIA workstation running the GGUF Q4_K_M build with llama.cpp. high_risk accuracy was 90% on the Mac and 95% on NVIDIA. A critical miss is a call marked must-not-allow whose answer is deny, escalate or ask_user but which the model allowed; one such call counts. p50 latency was 826 ms in process on the Mac and 143 ms on NVIDIA; over HTTP end to end, 20/20 calls succeeded.

One call on either side of 0.5

The two machines differed on a single call: its high_risk probability was 0.4883 on one and 0.5039 on the other, only about 0.016 apart but on opposite sides of the 0.5 threshold, so the labels differed. Our inference: verdicts near a threshold flip easily, so cases close to it should take the stricter path. To check whether your own model's probabilities match its hit rate, use the calibration checker, which computes Brier, ECE and a reliability diagram in your browser without sending data.

Routing, search and compaction

These uses share one shape: the decision model answers a few small typed questions and code takes the action; that summary is our inference. The descriptions follow each project's README and code; we did not run them.

  • pi-jev-router: a model-routing extension for the Pi coding agent. Jev answers only tier, standalone and high_consequence; a local deterministic policy then decides whether to switch models based on availability, context capacity and cost. The code pins jev-1.13.0.
  • jev-search: uses Jev to understand a query, choose sources and a time window, and score result relevance with noul; the interface returns only links and snippets, never a generated answer.
  • fast-jev-compaction: asks a noul question for every tool call and result in a conversation to keep, truncate or drop it, without rewriting user or assistant text.
  • jev-security-scan: a standard-library Python CLI that can optionally call Jev to check agent skills, MCP configuration and source code for suspicious behavior before installation, reporting line numbers and redacted evidence.

Choosing a model: hosted, open weights or raw logits

If data must stay on your machine, self-host open weights or read an existing model's logits; only when the state can leave do hosted Jev and Workers AI become options.

We chose not to send data to hosted Jev and to run open-weight Clef-flash on our own machines instead, because the state would leave the machine and the service is still early access. The table reflects October 2026; numbers marked vendor- or author-reported are not our measurements, and we have no Jev credentials. "Depends on deployment" is our inference: those rows are public weights or code you can host yourself.

Option Where data goes License Size and speed Source of numbers
Hosted Jev Leaves the machine Closed, early access Size not disclosed; 70–500 ms end to end Vendor-reported
Clef / Clef-flash on Workers AI Sent to Cloudflare Apache-2.0 27B / 9B; median 209.3 / 38.8 ms Size from model card; latency vendor-reported
Self-hosted Clef-flash Stays local Apache-2.0 9B; gate p50 826 ms Mac, 143 ms NVIDIA Our measurement (20 calls)
d1-3B Depends on deployment LFM Open License, revenue cap 3B; Decision Index 48.57 Vendor-reported
Laya Depends on deployment Encoder plus decision heads 322M / 421M; very low latency, lower accuracy on most tasks Author- and vendor-reported
Kev Depends on deployment Apache-2.0, alpha 0.8B–9B Author-reported
Raw logits (LLM2Jev, SemIf) Depends on deployment No training; SemIf marks it uncalibrated Uses an existing model Per README

Details the table cannot hold: TypeSafe reports $0.042 per million input tokens with free output, and its "193.6× faster, 444.6× cheaper" headline comes from in-house workflow evals that the announcement says may be optimistic; per its model card (vendor-reported), d1-3B's license excludes commercial use at annual revenue of $10 million or more; in Cloudflare's Decision Index 0.2.1 table (H200 internal run, vendor-reported), BFCL is 98.8 for Clef-flash and 95.8 for Jev, and GPQA Diamond is 78.3 for Jev and 48.0 for Clef. TypeSafe's privacy policy says user input is not used to train models, but input is still processed by the hosted service.

A last point, also our inference: calibrating each question on its own does not make a guardrail that chains several questions calibrated as a whole; validate thresholds on the whole pipeline, not one question at a time.

References

  1. https://docs.typesafe.ai/introduction.md
  2. https://docs.typesafe.ai/concepts/system-one.md
  3. https://typesafe.ai/blog/introducing-system-one-models-and-jev
  4. https://typesafe.ai/legal/privacy-policy
  5. https://huggingface.co/Cloudflare/clef
  6. https://huggingface.co/Cloudflare/clef-flash
  7. https://huggingface.co/LiquidAI/d1-3B
  8. https://github.com/win4r/pi-jev-router
  9. https://github.com/superagents-lab/jev-search
  10. https://github.com/win4r/jev-security-scan
  11. https://github.com/Yinsongxu/LLM2Jev
  12. https://github.com/TheoLeeCJ/SemIf

Frequently Asked Questions

The critical-miss count was 0 on 20 calls. Is the blocklist a security boundary?

No. Those 20 calls are synthetic tool calls from 2026-10-11. A critical miss is an item marked as not allowed to proceed, whose reference answer is deny, escalate, or ask_user, and which the model labeled allow. One item counts. Zero only describes these 20 calls. The action result of 85% is not a general accuracy either. The rule floor went through six review rounds, covering truncation blind spots, ReDoS, a padding bypass, and parse mismatches. It is still a best-effort blocklist, not a security boundary.

If the model says allow and the rules say escalate, which one wins?

The rules win. Rules decide first, and the model may only tighten the result. allow is looser than escalate and ask_user, and those two are looser than deny. high_risk may not be set below the value the rules already gave. The merge rule comes from a code review: an early draft let the model loosen escalate into allow. If the model call fails, the client falls back to action escalate and high_risk true.

If the request hits loopback on this machine, does the data stay here?

No. Loopback and "the data stayed on this machine" are different questions. A port on this machine that forwards to another machine counts as remote. Our decision-model client sends tool inputs to a local endpoint by default. The default mode is shadow, which records and does not block. Enforce mode needs two explicit switches. An error fails closed. File contents are cut at 2,000 characters, and a shadow record keeps 200 characters.

How is reading a chat model's logits different from weights such as Clef?

Structured output still generates the answer token by token, then a format constraint holds the shape, and the confidence is usually a number the model wrote itself. That contrast is documentation plus our reading. A decision model does not generate the answer. It returns a probability for each option listed in advance. According to the README or code of LLM2Jev and SemIf, these adapters do not add training. SemIf labels its output conditional and uncalibrated, and the LLM2Jev author states that TypeSafe has not endorsed the project. Clef and Clef-flash are post-trained from Qwen3.8-27B and Qwen3.5-9B, add a joint schema head on the backbone, and use the Apache-2.0 license.

Found this useful? RSS · X · Buy me a coffee

When an agent wants to run a tool, the request passes these four stops.

A check before every tool call runs
1 / 4
The agent proposes a tool call
The decision model answers two small questions; code decides to run, block or ask. The default is shadow mode (log only), and any error blocks.
  1. Deleting a file, sending a request, reading a config. Some cannot be undone.
  2. Rules are a linear-time blocklist; any hit means deny. They are a floor, not a full security boundary.
  3. action is a four-way choice and high_risk a yes/no question; both are answered in the same call.
  4. Take the stricter of rule and model: run, block, or stop and ask a person.

Rows are the rule verdict, columns the model verdict, cells the merged result.

The model can make a result stricter, never looser
1 / 3
Rule says allow: the model may tighten
Strictness: allow < escalate / ask_user < deny; the merged result is the stricter of the two. ask_user is left out; it ranks with escalate.
  1. If the model says escalate or deny, the result follows. This is where the model helps most.
  2. Even if the model says allow, the result stays escalate. An early draft let the model loosen it; review removed that.
  3. The lower-left cells are where the model wanted looser and the rule held. high_risk never drops below the rule value either.

Each bar is the share right for one measure (%).

20 synthetic calls: zero critical misses
1 / 3
action 85%, high_risk 90% / 95%
20 synthetic tool calls (2026-10-11), each run in process on a Mac and on an NVIDIA workstation; action accuracy matched and high_risk differed by one call.
  1. The four-way action was right 85% of the time; high_risk 90% on the Mac and 95% on NVIDIA.
  2. One call scored high_risk 0.4883 on one runtime and 0.5039 on the other: 0.016 apart, but on opposite sides of 0.5.
  3. Zero critical misses: no request whose answer was deny, escalate or ask_user was allowed. All 20 end-to-end HTTP calls succeeded.

Each row is one project: what the model is asked, and what code does next.

The model answers small questions; code acts
1 / 3
Routing: pi-jev-router
Three third-party projects, summarized from their README and code; we did not run them. The common pattern is our inference.
  1. Jev answers three questions (tier, standalone, high_consequence); a local policy decides whether to switch models.
  2. The model picks sources and a time window and scores relevance with noul; the UI returns links, never a generated answer.
  3. Each tool call gets one keep, truncate or drop question; user and assistant text is never rewritten.