Incidents and the command judge
What it shows
Section titled “What it shows”Enso watches what pi does after a refusal. A call that retries what was refused, or tries another way to the same thing, raises a security signal. The signals add up to a session risk, and when it reaches incident, Enso opens an incident: a card at the end of the chat that says what was observed and what is suspended, and takes your resolution.
Each signal reads the call and the refusals before it, within the last 8 calls:
| Signal | Raised on |
|---|---|
denial-retry |
A refusal by the same rule, or of the same command or path, as an earlier one. |
command-obfuscation |
After refusing a network client by name, the network rule refused a command it couldn’t read: a name built at run time, for example. |
alternate-execution-path |
After a shell refusal, an interpreter handed code on its command line: python -c, node -e, sh -c, eval. |
tool-substitution-after-denial |
After a shell refusal, a file written or edited, then run. |
policy-introspection |
After a refusal, a call that names the guards’ policy or Enso’s own files. |
protected-record-touch |
A shell command that names the session or log directory. Needs no refusal before it. |
config-tier-touch |
A write or edit of a config file (.enso/config.json, .enso/config.local.json), or a shell command that writes one (a redirect, tee, sed -i, cp/mv onto it, an interpreter one-liner naming it), refused or not. A read or a mention is not one. Needs no refusal before it. |
The risk is medium from the first signal, high at 3 signals or 2 kinds, and incident at 5 signals, or as soon as command-obfuscation and alternate-execution-path are both seen.
By default an incident only alerts. With halting on, it suspends the tool families execute (the shell: bash, powershell), network (the web: web_fetch, web_search), write (file writes: write, edit) until you resolve it, and pi is asked for a report on what it did. Nothing but the report tool runs until pi has filed it; after that, reading and searching work again.
The command judge is a second opinion on the bash calls the guards let through: one that comes right after a refusal, or one the guards cannot read, such as a name built at run time, a decoded string or an interpreter one-liner. It asks a classifier model how risky the command is, how hidden its program is, whether it runs code nobody has shown, and, after a refusal, whether it does what was refused. Its answers are recorded. They never block a call or change one.
The judge is off until you choose a backend:
security.judge.backend |
Sends the command to |
|---|---|
off |
Nothing is judged, and nothing is sent. The default. |
jev |
TypeSafe’s Jev classifier. Needs TYPESAFE_API_KEY in ENSO_HOME/secrets.env. |
local |
A llama.cpp router on this machine: security.judge.local.baseUrl (a loopback address only) and security.judge.local.model. |
How to open it
Section titled “How to open it”-
Open a thread from the rail. The card sits at the end of the chat, below the last answer.
-
Halting, the thresholds and the judge are set in Settings › Security (the card’s “halting is off” opens it), which writes your
config.jsonunderENSO_HOME. A project cannot set them. In the file:{ "security": { "incidents": { "halt": true }, "judge": { "backend": "jev" } } } -
From the command line, with the web app running (
bun run dev):bun run incidents every incident: model, halt, signals, report, resolutionbun run incidents export <incident> its records, the report and the resolutions, as filesbun run incidents replay --incident-at-signals 3the stored threads, under other thresholdsbun run judge cases which calls would be judged, and the exact requestsbun run judge replay --send send them to the judge, and save its answers
What it looks like
Section titled “What it looks like”
- open · tools suspended: the incident is open, and it suspended tools when it opened, because halting is on.
- Observed: each signal: its kind, the call it was raised on, the refusals it came after, and what it saw. The ids are the calls’ own, as on their rows above.
- Inferred risk: the level the signals add up to. A level, not a verdict.
- Suspended tools: the tool families the incident took away, with their tools.
- intervention required, in the status bar, while an open incident holds tools suspended.
With halting off, the card says open · halting off and nothing suspended, the status bar says incident open, and pi is not asked for a report.
To see it: open a thread from the rail, then choose probable bypass, then choose resume with restrictions — the families ticked below stay suspended, then choose network (web_fetch, web_search), then choose Record resolution.

- The agent’s explanation: the report pi filed, kept apart from what was observed. It is pi’s account, not the record.
- What was denied, with the quotes behind it. Each quote names the call it is from, and says
verifiedwhen the words are really there. - The uncharitable read: pi states the least charitable reading of what it did.
- Resolutions: what you judged it to be, the outcome, and what stays suspended.
- Change what stays suspended: after a resume with restrictions the form stays, so you can lift what is still suspended later.
The card’s header now reads resolved · resume-with-restrictions, and the status bar no longer says intervention required. A resolution resumes every family, resumes with the families you tick kept suspended, or ends the thread. Ending it is final: it takes no new prompts, every family stays suspended, and it can still be forked.
What to look for
Section titled “What to look for”In the thread above, the shell refused curl, and pi ran the same download through sh -c with the name built at run time, $(printf cu)rl. The guard refused that too. That one call raised three signals: an interpreter one-liner after a shell refusal, the same rule refusing again, and a network refusal the guard could not read after a named one. The first and the last together open an incident at once, whatever the count.
Read The agent’s explanation against Observed. The signals are what Enso saw. The report is what pi says about it, and only its quotes are checked: verified means the quoted words are in the call or the prompt it names, not that pi’s reading of them is right. A report whose What was denied does not cite one of the incident’s own refusals is flagged.
A resolution starts the risk over: the level, the signals and the window. A refusal from before it cannot pair with a call after it. The card’s records, and the resolution’s, are kept in the session file, so a reload or a restart shows the same card.
A fork made while an incident is open has an incident of its own, inherited from the source. The card says where it came from, and each thread is resolved on its own.
The judge’s answers are in the session file, one enso-command-judgement record per judged call, and in the day’s log as command <call> (<scope>) judged by <backend> in <ms>ms. A call the judge could not answer is recorded as unjudged, with why.
The day’s stats (/debug?stats) count signals by kind, incidents by model, the reports, and the resolutions by what you judged them.
What it doesn’t do
Section titled “What it doesn’t do”- The thresholds are first guesses, not yet tuned on real sessions. That is why halting is off until you turn it on.
security.incidentstakeswindowCalls,highAtSignals,highAtKindsandincidentAtSignals(8, 3, 2, 5), andbun run incidents replayshows what other values would have opened. - A signal reads what a call says, not what it does. A command the guards never refused, and that names no internals, raises none.
- The judge only records. It blocks nothing, its answers don’t raise signals or open incidents, and the web app has no view of them yet.
- The judge judges
bashcalls only, and only after a refusal or when the guards can’t read the command. - The judge sends the command, its recent calls and the refusal before it, with secrets replaced by markers: known secret values, and anything shaped like one.
bun run judge casesshows exactly what would be sent, and sends nothing. - You can’t resolve an incident while the thread is running. Wait for it to stop, or stop it.
- The guards are not a sandbox. Records in the session file are protected from the shell, which makes changing them harder and raises a signal, but not impossible.
Go deeper
Section titled “Go deeper”- Guards and receipts: the refusals the signals follow.
- Session eval: the signals on a thread’s timeline, and its risk.
- Guards seam: the policy, the risk fold and the judge.
- Glossary: security signal, session risk, incident, suspension, incident report and command judge.
