Skip to content

Feat/permission modes#261

Merged
cukas merged 5 commits into
mainfrom
feat/permission-modes
Jul 16, 2026
Merged

Feat/permission modes#261
cukas merged 5 commits into
mainfrom
feat/permission-modes

Conversation

@cukas

@cukas cukas commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

No description provided.

One resolvePermissionDecision pipeline (hard-deny -> deny rules -> task
lease -> allow sources -> boundary asks -> mode policy) now backs the
native/API preflight, the XML and eager tool paths, the companion
buildOnApproval gate, the MCP side-channel watcher, and the delegated
agent callback. The three divergent per-gate permissionMode reads are
retired; notably the smart-mode blanket auto-approve for delegated
agents is replaced by an auto-edit floor clamp (file edits run free,
Bash mutations still prompt), and an Always rule now suppresses the
lease prompt instead of double-gating. New agonPermissionMode config
key (ask|auto-edit|auto) migrates from the legacy permissionMode
(auto->auto, smart->auto-edit, ask/deny-all->ask); deny-all stays a
hard-deny override checked first. Leaseless delegated pushes and other
external side effects still ask even in auto mode. The approval prompt
fallback without a dispatch surface now denies instead of approving.

34-case golden decision table in tests/unit/permission-resolver.test.ts.

⚔️ Forged by [Agon](https://github.com/KERNlang/agon)

Co-Authored-By: agon (KERN) <292465531+KERN-Agon@users.noreply.github.com>
Shift+Tab now cycles ask -> auto-edit -> auto (Claude Code parity);
Ctrl+A and /auto stay as the direct AUTO toggle, mapped onto the same
mode enum (auto <-> auto-edit) through one applyPermissionMode writer
so cesarAutoMode, agonPermissionMode, the lease, and the footer never
disagree. The footer's AUTO slot becomes an always-visible permission
segment: 'AUTO' when autonomous, otherwise the mode label with a
shift+tab hint — restoring the approval-posture visibility the status
bar rewrite dropped. New /mode command (bare cycles, /mode <mode> sets,
/mode status explains).

⚔️ Forged by [Agon](https://github.com/KERNlang/agon)

Co-Authored-By: agon (KERN) <292465531+KERN-Agon@users.noreply.github.com>
The permission prompt grows to Yes / Yes-for-this-session / Always /
No / Never. Always persists a validated scoped rule (two-token Bash
scope like Bash(git push:*), exact resolved path for file tools) into
permissions.allow — never a bare base token; when no safe rule can be
synthesized (bare verbs, compounds, substitution, rendered previews)
the Always/Never choices simply don't appear. Never persists a deny
rule. Yes-for-session stores the rule in a session-scoped store so one
answer drains coalesced sibling prompts across a multi-engine run; the
queue drain now also honors persisted and session rules, not just
allowedCommands. /permissions lists persisted + session rules, the
legacy allowedCommands compat list, and the active mode, and gains
add allow|deny <rule> / remove <rule> subcommands.

⚔️ Forged by [Agon](https://github.com/KERNlang/agon)

Co-Authored-By: agon (KERN) <292465531+KERN-Agon@users.noreply.github.com>
Full-roster review (4/6 engines) confirmed two blockers, both fixed:
legacy bare allow sources (allowedCommands base tokens, tool-level
toolPermissions allow, session base tokens) could cover a dangerous
lease boundary — they now rank BELOW the boundary ask, and only
scoped permission rules (the deliberate Always artifact) remain strong
enough to auto-approve a push. And fileTargetInsideWorkspace treated an
empty cwd as universal containment, so delegated agent runs floor-
clamped to auto-edit could silently approve edits anywhere on disk;
empty-cwd now passes only relative non-escaping paths, the delegated
callback passes the real workspace root (team worktrees live under
.agon/agent-worktrees inside it), and auto mode without a lease fences
file mutations to the workspace itself.

Also from the review: subcommand synthesis skips key=value option
tokens (git -c user.name=x push -> Bash(git push:*)), persisting a rule
evicts it from the opposite bucket so Always-after-Never actually wins,
the remaining inline approval-string checks use
isApprovedPermissionResponse, ask-mode prompts carry the resolver
reason, and dead rule-engine imports are dropped. Refuted (no change):
'auto_off' in boundaryReasons is reachable via the lease reason
carry-through; parsePermissionRule strips ':*' so synthesized rules
validate (covered by tests).

⚔️ Forged by [Agon](https://github.com/KERNlang/agon)

Co-Authored-By: agon (KERN) <292465531+KERN-Agon@users.noreply.github.com>
# Conflicts:
#	packages/cli/src/generated/cesar/brain.ts
#	packages/cli/src/generated/signals/intent.ts
#	packages/cli/src/kern/signals/intent.kern
#	packages/core/src/generated/models/types.ts
@cukas
cukas merged commit b08bb91 into main Jul 16, 2026
2 checks passed
@cukas
cukas deleted the feat/permission-modes branch July 16, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants