1# Chapter 12: jevhooks-daemon/src, the questions and the verdicts 2 3Here, at last, is what Jev is actually asked. It is the heart of the whole 4plugin, and it lives in one file, [judge.rs](judge.rs). 5 6Jev cannot be asked "is this command dangerous?" Or rather it can, and it 7will answer, and the answer will be about the word "dangerous". A command 8that merely *prints* `rm -rf ~/` as text was once stopped as if it ran it. 9So `judge.rs` never asks with an adjective. It writes down a short list of 10concrete things a command could do, defines each in plain acts, and asks 11Jev which one this is. Then it decides what to do with the answer itself, 12in code, where a test can hold it still. 13 14## A command: one request, three questions 15 16Before a Bash command runs, the daemon sends Jev a **state** (the command, 17first 6,000 characters; the working directory; the project root) and three 18questions in one request: 19 201. **`act`, a Choice:** which of these describes what the command does? If 21 it does several, the one hardest to undo. 22 23 | Label | Defined as (abridged) | Consequential? | 24 | --- | --- | --- | 25 | `read` | only reads or prints; nothing is different afterwards | no | 26 | `build` | builds, tests, formats, lints, installs dependencies: regenerable files only | no | 27 | `edit` | creates or changes files a person wrote, or records them in version control; recoverable by an ordinary command | no | 28 | `delete` | removes or overwrites data no build regenerates and version control does not hold | yes | 29 | `history` | discards or rewrites version-control state: reset --hard, clean, rebase, force push | yes | 30 | `system` | changes the machine or the account: packages, services, dotfiles, sudo | yes | 31 | `remote` | publishes or sends something elsewhere: push, deploy, an API call that writes | yes | 32 | `unread` | runs code nobody has read: a script piped from the network into a shell | yes | 33 342. **`undo`, a Score of four levels:** how hard would it be to put 35 everything back? 0 nothing to put back; 1 one ordinary command; 2 only 36 with care or luck; 3 not from this machine. 373. **`load`, a Score of four levels:** how much of the machine does it take 38 while it runs? 0 negligible; 1 light; 2 heavy (compiles a project, a 39 whole test suite); 3 very heavy (several heavy jobs, a large release 40 build). 41 42All three end with `WHAT_RUNS`, a paragraph that says what "the command 43does" means: only what the shell would execute. Text carried as data (a 44here-document written to a file, a quoted string printed or searched for or 45sent) is not executed unless it is handed to something that runs it. 46 47Jev answers each with a probability for every option (and for a Score, an 48expected level from 0 to 3). Then `bash_verdict` decides: 49 50```mermaid 51flowchart TD 52 A["Jev's answers"] --> R{"one consequential act at 60% or more,<br/>or expected undo at 1.6 or more?"} 53 R -- yes --> ASK["ask"] 54 R -- no --> S{"memory available known,<br/>and below what this load needs?"} 55 S -- yes --> ASK 56 S -- no --> O{"read + build + edit at 90% or more,<br/>and expected undo at most 1.2?"} 57 O -- yes --> ALLOW["allow"] 58 O -- no --> PASS["pass:<br/>the usual permission check"] 59``` 60 61What a load "needs" rises from 0 MB (negligible) through 500 MB and 623,000 MB to 6,000 MB (very heavy), interpolated between levels: an expected 63load of 2.5 wants 4,500 MB free. 64 65> **Aside: one confident act, never a sum.** An early version added up 66> the consequential acts' probabilities, and a command Jev was unsure 67> about (its likeliest act at 36%) was stopped. Take a spread like 36% 68> `unread`, 20% `system`, 14% `delete`, 30% `edit`: summed, that is 70% 69> "consequential". But it is Jev saying "I don't know", and an unsure 70> answer should change nothing. Now only the single 71> likeliest consequential act counts, and that answer is a `pass`. The 72> test `doubt_spread_over_several_consequential_acts_is_not_a_flag` pins 73> exactly that case. 74 75A real answer, from the log: a long command that read some figures and 76called out to another program came back `read` 56%, `system` 39%, undo 770.4, load 1.3. No single consequential act reached 60%, nothing was hard to 78undo, memory was ample; but the ordinary acts came to 57%, short of 90%. So: 79`Jev: only reads (56%); nothing to undo (0.4 of 3); left to the usual 80permission check`. Not waved through, not flagged; Claude Code's own 81permission rules decided. 237 ms, 1,739 input tokens, $0.00007. 82 83## A turn's end: one question 84 85When a turn is about to end, the state is the session's last five prompts 86(each first 3,000 characters) and the end of the assistant's final message 87(last 6,000 characters), and the one question is a Choice of endings: 88`finished`, `waiting` (needs something only the developer can give), 89`blocked` (names an obstacle), `running` (work still going, will report), 90and `stopped-early` (work asked for is left undone and no obstacle is 91named). Only at 85% or more for `stopped-early` is the end refused: a wrong 92refusal costs the user a wasted turn. 93 94Three checks in `serve.rs` come first, and none of them asks Jev: a `Stop` 95that follows this plugin's own refusal (`stop_hook_active`) is let through, 96so one refusal can never become a loop; a turn with background tasks or 97scheduled wakeups still pending has paused, not ended; and with no prompt 98or final message there is nothing to judge. 99 100> **Aside: the 1.5 seconds.** Every request waits at most `ANSWER_WITHIN`, 101> 1.5 s, and past that the verdict is `pass`. The request itself runs in 102> its own task, so an answer that arrives late still settles its hold in 103> the spend ledger; only the waiting is abandoned. 104 105> **Try it.** `cargo test -p jevhooks-daemon`. The verdict tests in 106> `judge.rs` feed `bash_verdict` and `stop_verdict` made-up probabilities: 107> `ordinary_work_is_allowed`, `consequential_acts_are_put_to_the_user`, 108> `an_unsure_answer_is_neither_allowed_nor_asked`, 109> `a_heavy_command_is_put_to_the_user_when_memory_is_short`, 110> `stop_blocks_only_when_confident`. Change a threshold and watch which 111> ones notice. 112 113## For the people who maintain it 114 115The files, in the order a request meets them: 116 117| Path | What | 118| --- | --- | 119| [main.rs](main.rs) | The `jevhooks` command line (clap): `serve`, `mcp`, `status`, `recent`, `stop`. | 120| [paths.rs](paths.rs) | Where things live: `state_dir`, `socket`, `lock`, `daemon_log`, `decisions`, and `identity`, the build's fingerprint. | 121| [mcp.rs](mcp.rs) | The per-session MCP server (rmcp, stdio): the `status` and `recent` tools, and the call to `client::ensure`. | 122| [client.rs](client.rs) | `call`, one HTTP request on the socket; `ensure`, which makes sure a daemon of this build serves; `spawn`, the one place one is started. | 123| [serve.rs](serve.rs) | The daemon: the lock, the socket, the routes, per-session state (the last five prompts, the cost, the judged tool calls), `decide`, `observe`, `status`. The model is pinned here (`MODEL`, `jev-1.13.0`). | 124| [judge.rs](judge.rs) | The questions, every term defined, the thresholds, `bash_verdict`, `stop_verdict`, and their tests. | 125| [headroom.rs](headroom.rs) | The config file (`Config`), and where the available-memory figure comes from (`Source`: a configured command, `MemAvailable`, or none), measured at most every 5 s. | 126| [log.rs](log.rs) | `decisions.jsonl`: `DecisionRecord`, `OutcomeRecord`, `append`, and `recent`, which reads the last 512 KiB. | 127 128The thresholds, all in `judge.rs` and all reported by `status`: 129 130| Constant | Value | Meaning | 131| --- | --- | --- | 132| `BASH_ASK_AT_CONSEQUENTIAL` | 0.60 | One consequential act this likely: ask. | 133| `BASH_ASK_AT_UNDO` | 1.60 | Expected undo level this high: ask. | 134| `BASH_ALLOW_AT_ORDINARY` | 0.90 | Ordinary acts together this likely ... | 135| `BASH_ALLOW_UNDO_AT_MOST` | 1.20 | ... and undo at most this: allow. | 136| `LOAD_NEEDS_MB` | 0, 500, 3000, 6000 | Memory each load level should find free. | 137| `STOP_BLOCK_AT_LEAST` | 0.85 | `stopped-early` this likely: refuse the end. | 138| `ANSWER_WITHIN` | 1.5 s | Longest wait for Jev. | 139 140A decision record in `decisions.jsonl` has `record: "decision"`, the 141session, `kind` (`bash` or `stop`), the `subject` (a command's first 500 142characters, or a final message's last 500), the `tool_use_id`, the verdict 143and line, `answers` with every option's probability, and `ms`, `jev_ms`, 144`input_tokens`, `usd`, `attempts` and `request_id`. An outcome record has 145`record: "outcome"`, the session, the `tool_use_id` and `ran`, `failed` or 146`denied`, and is written only for a tool call that was judged. 147 148← Previous: [Chapter 11, jevhooks-daemon/](../) · Up: [jevhooks-daemon](../) · Next: [Chapter 13, third-party/](../../../third-party/) →