feat(permissions): approval notes and session-scoped user rules for the AI auto-approve judge [WIP] - #2229
Open
YodonTan wants to merge 1 commit into
Open
Conversation
…he AI auto-approve judge
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2228
Summary
Extends the AI auto-approve permission mode with two capabilities:
<user_rules>section the fast-model judge reads between the stable session context and the growing tool history. Subagents inherit the parent session's rules.Design
1. Approval notes: wire-compatible DTO extension
PermissionReply::OnceandPermissionReply::Alwaysbecome struct variants with an optionalfeedbackfield (#[serde(default, skip_serializing_if = "Option::is_none")]):The old wire shape
{"reply":"once"}still serializes unchanged and still deserializes (contract test covers both directions). All construction/match sites across the workspace were updated (desktop command, CLI peer host, ACP prompt, IPC protocol tests, app-server tests).2. Note propagation
respond_permissionfillsfeedbackintoOnce/Alwaysreplies (the request DTO already carried the field for rejections).PermissionAuthorization::Allowednow carriesuser_feedback; the pipeline appendsUser approved this tool call with feedback: {note}to the tool result so the agent honors the intent in the same turn.(user note: "...")so the judge sees in-turn intent.source: user.3. Session-scoped user rules for the judge
Prompt structure (stable prefix, then growing history, then the call):
Extraction (
load_session_rules): from the project permission audit, filtered to the session (plus the parent session for subagents) and tosource: userreplies with durable intent:Alwaysapprovals →Always approved: {action} on {resources}User approved {action} on {resources} with note: "..."User rejected {action} on {resources}: "..."(so the agent stops retrying refused operations)Project grants are appended separately as
Persistent grant:authorization facts so the judge knows what is already auto-allowed for the project.Ordering and cap: newest-first (audit recency), deduplicated by a stable rule id (kind + action + resources), capped at
MAX_USER_RULES = 50.Turn stability / KV cache: rules are built once per dialog turn (cache key = project + session + dialog turn) and reused for every judge call in that turn — the prefix is byte-stable and KV-cache friendly. New approvals inside a turn only appear in the growing
<tool_history>with their note marker; they are formalized into<user_rules>at the next turn boundary.Fail-closed: the rules section opens with a fixed preamble — rules express user intent, are not blank checks; only directly matching calls count, and dangerous operations are never approved by a rule. The system prompt additionally teaches the judge to treat an in-turn
(user note: "...")on a directly matching history entry as pre-approved intent.Subagent inheritance:
load_user_rules_for_batchmergesdelegation.parent_session_idinto the session filter, so delegated tool calls judge against the same user intent.4. LRU ordering was implemented, measured, and removed
The first iteration ordered rules by an LRU of "rule hits": each judged request was structurally matched (action + wildcard resources) against the rules and matches were recorded, with per-session persistence (
permission-rule-lru/<session>.json).Real-world testing showed this mechanism did not work as intended:
The LRU machinery (matcher, port, JSON store, record/flush methods) was therefore removed entirely. Ordering is plain audit recency — simple, predictable, and aligned with "the most recent approval expresses the user's current intent". The per-turn byte-stable cache remains, as it is an independent KV-cache win.
5. Safety boundaries (unchanged behavior)
.env, credentials, private keys, tokens, ...) still go through the judge.rm -rf /and similar destructive/secret-exposing/system-wide operations are still rejected outright (deny+critical→Reject), and the judge is explicitly told rules never cover them.escalate(ask the user).Testing
{"reply":"once"}round-trip); permission manager reply/audit with notes.tool_pipeline): approval with a note → tool executes and the result contains the note; approval without a note → result text unchanged; escalation → user approval with note flows through.<user_rules>rendered at the next turn with the user's notes; similar operations auto-approved from the next turn; subagent judge input contains the parent session's rules;rm -rf /still rejected outright with "not covered by any user rule".