rm -rf build/ that you asked for from rm -rf ~ that slipped into a plan, so they block too much in one place and too little in another. Jev, TypeSafe’s classifier, reads the call against what you actually asked for and answers a set of yes/no questions about it in one fast request.
With your own Jev endpoint and key configured, Failproof AI asks Jev about each tool call alongside the regex policies, never instead of them:
- A hard policy’s deny is final. Jev cannot clear it. Every policy is hard unless it is explicitly marked reviewable and names the Jev checks that cover it, so a custom, pack or Cloud policy that says nothing is hard, and the always-on self-protection guard is always hard.
- A reviewable policy’s deny may be cleared, but only when Jev was asked about the exact concern that policy covers and answered “nothing here” or “the user asked for this”. A check that finds the concern real, when the user did not ask for the call, keeps the deny — even when its own verdict is only a warning, because before a tool call a warning does not stop the agent. And when that check is one that can deny (secret exposure, credential exfiltration, destructive deletion, …), nothing is cleared on that call.
- A block can still become a warning when the call is a step of the task you gave and reaches no further: Jev softens its own deny to a warning, and that warning — naming what is actually wrong with the call — replaces the policy’s block.
- Jev can also warn or deny on its own, for harm no regex describes.
- If Jev cannot answer (timeout, rate limit, server error, no credits, an unexpected model version), that call gets the regex result, exactly as without Jev.
- Jev never makes a call more permissive than your policies alone unless it read the whole call and was asked about the exact concern. Anything less — a call too big to send whole, a suspected injection — withdraws the clearances and keeps every deny.
Without a Jev config nothing changes: hooks run the regex policies exactly as they always have. The config is the whole opt-in.
Choose a provider
Jev is reachable through five routes. Bring a key for any one of them.Set it up
One command, the endpoint and the key:The URL picks the provider
You do not have to name the provider: the URL’s host is which one it is.
Three things follow from that:
- A URL that is the provider’s own API writes no override.
--url https://api.typesafe.ai/v1produces exactly the config--provider typesafewould have. Give a different path or host on a known provider and it is stored as the base URL, as--base-urlwould store it. --providerstill overrides the inference, which is how you reach a proxy that speaks a provider’s API from a host of your own:--url https://jev-proxy.internal/v1 --provider typesafe.- A
--providerthat contradicts the host is refused, not guessed at.--provider openrouter --url https://api.typesafe.ai/v1writes nothing and says why: the two spellings disagree about where your key is about to be sent. The same pair is refused fromjev setup --base-urland from the dashboard’s Jev settings. (--provider customis not a contradiction — it means “treat this URL as itself” — except on Cloudflare’s host, whose per-account endpoint a custom route cannot reach.)
--url is validated exactly as the baseUrl in the config file is, and refused in the same words: https, or plain http://localhost in shadow mode only.
The key
Pipe it in with--key-stdin, or run the command in a terminal without it and paste the key at a masked prompt. Either way it goes straight into the config file and is never printed back.
- TypeSafe
- OpenRouter
- Vercel
- Cloudflare
- Custom
failproofai jev setup takes the same flags and is the longhand for all of it: setup --provider <kind> where you would rather name the provider than the URL.
--token, and what it costs
--token <token> puts the key on the command line, which is the fastest way to configure a machine and the only spelling that leaves the key anywhere but the config file:
--token, --key-stdin and --key-from-env are mutually exclusive: give one.
Then send one small live request to check the key, the endpoint and which Jev answered:
jev test exits 1, and says so in its title, when the answer arrives after the timeout (every hook would fall back to regex as timeout) or answers its check question wrongly.
Hooks read the config on every tool call, so it applies from the next one. There is nothing to restart, with or without the daemon.
Check what it is doing
status shows the provider, endpoint, model, mode, the config file and its permissions, and never the key. Below that it summarizes recent activity: how many calls Jev evaluated, how often it fell back to regex and why, its latency, and which reviewable policies it cleared.
Shadow mode
enforce is the default. To watch Jev without letting it change any decision, switch to shadow: Jev is still asked and its verdicts are recorded, but the regex result is what is enforced.
off keeps the config — the endpoint and the key — and stops asking Jev: hooks run the regex policies exactly as without a config, and failproofai jev status says “off (switched off)”. Switch back with --mode shadow or --mode enforce.
Re-running setup for the same provider keeps the stored key, so a mode switch is one flag. Switching provider starts over and asks for that provider’s key. So does a --base-url that moves requests to a different host: a stored key is only sent to the host it was given for, or to its provider’s own API.
The config file
Everything lives in one file,~/.failproofai/jev.json, written by setup:
Three rules protect it:
- Owner-only. It is written with permissions
0600. A copy that any other user or group can read or write is refused, and hooks fall back to regex until you runchmod 600 ~/.failproofai/jev.jsonorsetupagain. The directory is checked too:~/.failproofaimust not be writable by anyone else, because whoever can write there can replace the file whatever its own permissions are.setuptakes those write bits off if it finds them.failproofai jev statussays when a config has been refused and shows the endpoint the file names: someone else could have changed it, so check it is yours before youchmod. Re-runningsetupon such a file carries its stored key only to the provider’s own API; any other endpoint it names needs the key again (--key-stdin), or--base-url defaultto send requests back to the provider. - Global only. A repository cannot turn Jev on, point it at another endpoint or pick its model: a
.failproofai/jev.jsoninside a project is ignored, and the provider, URL, model and account id are read only from that file — never from the environment, which a repository’s agent settings can set. (FAILPROOFAI_HOMEis not a way around that: it moves the whole failproofai directory, your policies included, rather than redirecting Jev on its own.) - The key alone may come from the environment. If the file has no
apiKey,FAILPROOFAI_JEV_API_KEYsupplies it for that session (setup --key-from-envwrites such a file). It never replaces a key the file holds, and it cannot turn Jev on without the file. Where the variable is not set, Jev is simply off for that shell:failproofai jev statussays so, exits 0 and leaves the config alone (status --jsonreports"status": "key-missing"with"reason": "no-env-key"). Thefailproofaiddaemon does not see your shell’s environment, so on a machine set up withfailproofai config, keep the key in the file.
Which Jev answers
Failproof AI’s decision thresholds were calibrated on Jev 1.13, so an answer is used only when it comes from that family:jev-1.13.x, or OpenRouter’s typesafe/jev-1.13-<date>. Where a provider names Jev only by an alias and reports no version (Vercel, and Cloudflare when it does not say), the answer is used and recorded as unverified. A custom endpoint must report the model that answered; the one exception is an unversioned --model name you configured for it, which, echoed back, is recorded as unverified in the same way. An answer reporting any other version, or a custom answer reporting none, is not used: that call falls back to regex with the reason model-mismatch.
When Jev cannot answer
Each of these falls back to the regex result for that call and is recorded with its reason, whichfailproofai jev status totals:
failproofai jev status can show a few rarer reasons too, such as upstream-error (the answer carried the provider’s own error) or config, and totals any reason it cannot name as other.
request-cut is in this table because failproofai jev status totals it with the rest, and because it too leaves every deny standing. It is the one reason here that says nothing about your provider: the request arrived and Jev answered it. Unlike every row above it, that answer still counts — Jev’s own deny or warning applies on top of the regex result rather than being discarded. So a run of them means calls are reaching the evaluator too big to send whole, not that your endpoint is unwell, and topping up credits or changing the URL will not move the number.
When Jev answered, but not on the whole call
Two more things can happen, and neither is Jev failing to answer. Both are about how much of the call, or of the conversation, fitted into one request. Part of the call itself did not fit. A tool call is sent inside a fixed budget, and an outsized one — a very largeWrite, a huge MCP body, a command padded out to the cap — is sent with what fitted. Jev still answers, and its answer still counts: its own deny or warning applies as usual. What it cannot do is clear anything, because a verdict given on part of a call is not a verdict on the call. So every policy deny stands, and the call is recorded as a fallback with the reason request-cut, which failproofai jev status totals alongside the reasons above. The rule this gives you: making a call bigger can cost it its clearances, and can never buy one.
A message did not fit. A long prompt you pasted, the agent’s last message, or a prompt this evaluator’s own store had already capped. Nothing changes: the call is judged, cleared and recorded exactly as any other, and it is not counted as a fallback. The length of what you type never decides a verdict, and a cut cannot manufacture consent: where a prompt arrived already capped, “you did not ask for this” stops being a conclusion that can be drawn from it at all, rather than becoming one.
The line between the two is who wrote the text. The call is the agent’s, and a rule that let its length subtract severity would be a rule the agent can use; your prompt is yours, and treating its length as a signal only ever punished pasting a spec or a stack trace.
What leaves the machine
For each tool call Jev evaluates, one request goes to your provider, carrying:- the tool call itself, with secrets such as API keys, bearer tokens and
KEY=assignments redacted; - the recent prompts you typed, with text your agent’s harness added removed;
- the agent’s last message before your latest prompt, labelled as agent-written;
- facts computed locally, such as whether a path is inside the project — the one the session was in at its first reviewed call, pinned for the session — and the current git branch.
Turn it off
~/.failproofai/jev.json. From the next tool call, hooks run the regex policies exactly as before. The per-session stores under ~/.failproofai/state/semantic/ (recorded prompts in sessions/, project roots in roots/) are left in place and age out. To stop asking Jev but keep the config, use failproofai jev setup --mode off instead.

