Skip to content

feat(permissions): one named permission-mode vocabulary and two admission gates - #1935

Merged
aponcedeleonch merged 70 commits into
mainfrom
impl/unified-permission-mode
Oct 20, 2026
Merged

aponcedeleonch merged 70 commits into
mainfrom
impl/unified-permission-mode

Conversation

@aponcedeleonch

@aponcedeleonch aponcedeleonch commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Stage: Implementation. Implements the Plan / Interface contract in #1730, as revised by the review proposal in #1935 (comment). Stacked on #1730.

What it does

Five session permission modes that each change what the session may do: plan, default, accept-edits, auto, yolo.

  • One startup control. --permission-mode (on mecated, mecatui, mecak8s, mecatequi) and the operator-tier permissionMode: key set the mode new sessions start in and the server ceiling, the most permissive mode a session may switch to (accept-edits for plan/default/accept-edits; auto; yolo).
  • Modes are session-scoped. Allow-all (auto/yolo), loose substitution (yolo only; auto keeps the command-substitution guard for the main agent and children), the out-of-workspace path-escape decision, and serving a relative ../ path after an approved escape all follow the session's current mode, so switching from yolo to default restores ordinary approvals. Guardrail demotion and startup narration follow the session mode or the server ceiling, not the legacy posture. Deny rules and configured asks win in every mode.
  • auto needs an enforcing checker at startup, on create, and on SetMode; missing, --guardrails off, and advisory-only all refuse. Unchecked autonomy is an explicit yolo, where a configured checker is advisory.
  • Stored sessions are re-admitted. A persisted session in a mode the current deployment doesn't admit is refused (FailedPrecondition) at run, retry, approval resume, fork, clear, and MCP-authorization continue before any provider or tool work, history untouched, until the owner switches it.
  • One mode ordering. session.PermissionMode.Rank orders all five modes and replaces the local rank tables, so an agent definition's permissionMode ceiling clamps auto/yolo requests (explicit or from the server default), and fork/clear of an agent-bound auto/yolo session keeps its mode.
  • Mode changes and pending approvals. A session awaiting an approval cannot change mode; the pending ask resolves under the mode it was raised in (narrower contract agreed in review). The exception is a stored session awaiting an approval in a mode the server no longer admits: switching it to an admitted, lower mode cancels the pending turn without running it (refused_mode_recovery).
  • Trust is separate. A --permission-mode token never grants project trust; use --trust-project, trustedWorkspaces, or remembered trust. Only the deprecated --posture keeps its interactive trust floor.
  • Delegates inherit. Subagent children, Parallel branches, and Team members of an auto/yolo session run in that mode, unless explicitly configured narrower.
  • Clients. mecatui cycles all five modes on shift+tab; an unavailable mode keeps the current mode and draft and explains what's needed in the footer. A renderer-owned auto/yolo badge stays in the header whatever the template (server-wide state on legacy servers). /posture shows the subagent ask reviewer state and says "server-wide allow-all" on servers without session_permission_modes. ACP starts sessions in the server default and offers only admitted modes. HTTP rejects an unrecognised mode string with 400 instead of falling back to the server default. The TypeScript SDK supports Auto/Yolo over gRPC and HTTP.
  • Wrappers. The Helm chart gains permissionMode (default auto) and a checker choice: guardrails.model, guardrails.fromSettings: true (the checker comes from mounted settings), or guardrails.disabled. It refuses to render auto without a checker or with disabled, and rejects --posture/--permission-mode/--yolo/--guardrails/--guardrails-model in extraArgs. The mecatequi action's guardrails-model is empty = inherit, off = kill-switch, else a model; under auto the step fails early when there's no model and no operator settings file, or with off. The reusable workflow forwards a new permission-mode input.
  • Deprecated aliases. --posture, --yolo, mecatui --mode, and the posture: key still resolve, each warning once with the replacement token; combining them with --permission-mode is a startup error. mecated, mecak8s, and mecatequi share one flag helper and help text in internal/app.

API compatibility

Assessment: breaking change to the observable session-mode contract. API major not bumped (stays 1).

The wire policy asks for a major bump when existing conforming clients must change to stay correct. This change does require that for some clients (listed below). I, as the operator, approved keeping major 1 and recording the break here instead, with discovery through feature identifiers and the upgrade notes below.

Old contract:

  • A gRPC CreateSession with PERMISSION_MODE_UNSPECIFIED produced a DEFAULT session; the only modes on the wire were DEFAULT, PLAN, ACCEPT_EDITS.
  • Allow-all was process-wide: on a --posture auto|yolo server every session, including an explicit DEFAULT one, ran allow-all. Under --posture auto the main agent also ran unprovable command substitutions without asking.
  • ServerCapabilities.posture was display-only.

New contract:

  • Five session modes on the wire (adds AUTO=4, YOLO=5). An unspecified create (gRPC, HTTP, ACP, schedules, and the TS SDK's HTTP transport) resolves to the server's configured default, which can be AUTO or YOLO. SetMode treats UNSPECIFIED as DEFAULT.
  • HTTP create and SetMode accept the bare and PERMISSION_MODE_-prefixed spellings; any other unrecognised mode string is now 400 InvalidArgument instead of falling back to the server default.
  • Allow-all belongs to the session mode. An explicit DEFAULT session gets ordinary approvals on every server. Only YOLO lifts the command-substitution guard; AUTO keeps it for the main agent and children.
  • A session can switch to modes up to the server ceiling; AUTO additionally needs an enforcing checker. ServerCapabilities.posture now bounds which modes a client may cycle to.
  • ApprovePlan rejects AUTO and YOLO as target modes (InvalidArgument on gRPC and HTTP).
  • A stored session in a mode the current deployment doesn't admit is refused (FailedPrecondition) at run, retry, approval resume, fork, clear, and MCP-authorization continue until the owner switches it; history is untouched.
  • Mode changes are refused while a session awaits an approval, with one exception advertised by refused_mode_recovery: for a stored session awaiting an approval in a mode the server no longer admits, a change to an admitted, strictly lower mode cancels the pending turn without running it and applies the mode.

Affected clients:

  • SDKs and mecatui builds that predate AUTO/YOLO may reject an AUTO/YOLO session snapshot or report it as unspecified.
  • Clients relying on process-wide allow-all for DEFAULT sessions now see asks.
  • An older TS SDK over HTTP sends an explicit "default" when no mode is given, so its sessions start in DEFAULT rather than the server default until upgraded.

Discovery:

  • New feature identifiers session_permission_modes and refused_mode_recovery.
  • Additive: optional ServerCapabilities.project_trusted (32) and ServerCapabilities.subagent_ask_reviewer (33, on/off/inert).
  • Clients should gate AUTO/YOLO on session_permission_modes and check the echoed mode: an older server silently turns AUTO/YOLO into DEFAULT.

Upgrade implications:

  • Upgrade clients (TS SDK, mecatui) together with servers, or pass explicit modes from older clients.
  • Deployments that ran --posture auto without a checker must configure an enforcing checker or choose --permission-mode yolo. The mecak8s chart now fails at render time for permissionMode: auto without guardrails.model (or guardrails.fromSettings: true), so existing releases must make the choice before upgrading. The mecatequi action fails the step early under auto without a checker; callers of the reusable workflow with defaults must set guardrails-model or permission-mode: yolo.
  • Headless --posture auto deployments whose main agent ran $(...), backtick, or subshell commands unattended now get approval requests for them; use yolo if they must run unattended.
  • --permission-mode no longer grants project trust; use --trust-project or trustedWorkspaces.
  • Stored sessions keep their mode and are re-admitted on use.

Go source: engine adds session.ModeAuto/ModeYolo and PermissionMode.Rank (Added), and delegated children inherit an auto/yolo parent's mode (Changed, no signature change); see engine/CHANGELOG.md.

Stored data: no schema change. Rolling back to a release without session-scoped modes still loads AUTO/YOLO sessions but treats them as DEFAULT (ordinary approvals unless that server's own --posture allows all tools, reported as DEFAULT over gRPC). The stored mode is kept, so upgrading again restores it and re-admits it.

User-facing documentation: user-docs/features/security-and-execution/permissions-and-posture.md#upgrade-from-the-posture-model.

Gates

  • task lint: clean except a pre-existing darwin-only gosec finding in environment/microvm (untouched).
  • task docs, task site:build, task api:check, task sdk:*: pass.
  • Go tests: pass except tests that also fail on main on the author's macOS machine (socket paths, /private temp symlinks, git worktrees, microVM). Chart tests need HELM_NAMESPACE=default with a non-default kube context.
  • After the review fixes: task lint and task test:race rerun. Every remaining failure also fails on the pre-fix head 950fbd8ef (mecated/mecatui sockets, executionexecutor, deploy/mecatl-execution-kind) or passes in isolation (race-load flakes).
  • go run ./cmd/mecademo: tool call, permission ask/approval, and result.

@aponcedeleonch aponcedeleonch changed the title impl/unified permission mode feat(permissions): one named permission-mode vocabulary and two admission gates Sep 25, 2026
@aponcedeleonch
aponcedeleonch force-pushed the impl/unified-permission-mode branch from aad7dbb to 494f216 Compare October 5, 2026 12:31
Base automatically changed from plan/unified-permission-mode to main October 5, 2026 12:39
@aponcedeleonch
aponcedeleonch force-pushed the impl/unified-permission-mode branch from 494f216 to 2c56527 Compare October 5, 2026 12:39
@aponcedeleonch
aponcedeleonch marked this pull request as ready for review October 5, 2026 12:43
@aponcedeleonch
aponcedeleonch force-pushed the impl/unified-permission-mode branch from 2c56527 to bd9a699 Compare October 5, 2026 13:14
@aponcedeleonch
aponcedeleonch force-pushed the impl/unified-permission-mode branch from bd9a699 to 2446c27 Compare October 8, 2026 12:25
@aponcedeleonch
aponcedeleonch force-pushed the impl/unified-permission-mode branch 2 times, most recently from 8e6459b to 363f183 Compare October 14, 2026 10:29
@JAORMX

JAORMX commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

I think we should go further with the convergence here. If I choose default after running in yolo, I expect ordinary permission evaluation to come back. Changing only the session half while the server keeps its allow-all posture makes the mode selector hard to rely on. Putting restart-only choices into the cycle also makes discovery interrupt the work.

What I'd propose is five permission modes that actually change the session's behavior:

  • plan: inspect and plan, with mutations denied.
  • default: ordinary permission approvals.
  • accept-edits: automatically accept file edits; other mutations follow ordinary approval rules.
  • auto: autonomous execution with enforcing guardrails. An enforcing checker must be available to enter this mode.
  • yolo: autonomous execution without preventive model checking. Advisory monitoring may remain.

Explicit deny and deliberately configured human-required approval rules should still apply in every mode. Permission modes also remain separate from execution isolation: automatic approval does not imply sandboxing.

Workspace trust should be a separate yes/no decision, rather than another permission mode. That removes trusted and trusted-accept-edits from the list. Trust decides whether we automatically adopt the workspace's project instructions and configuration. It does not decide whether we can read its files! Inspecting an unfamiliar repository is often the whole reason to start an agent. We should still be able to read and search an untrusted workspace, including reading AGENTS.md as data without automatically treating it as authoritative. Executing repository scripts remains subject to execution permissions and isolation.

The UI could show something like Accept edits · Workspace instructions enabled. Revoking workspace trust may need a fresh session, since instructions the model already consumed cannot simply be unread.

For switching, the server should define which modes a session is allowed to select, and selecting one should apply its full behavior within that boundary. Switching from yolo to auto must restore checker enforcement; switching to default must remove the session's automatic allow-all behavior. Changes should take effect at a defined execution boundary, with pending approvals re-evaluated and already-running actions clearly identified. A mode change cannot undo a command that has already started. Unavailable choices should explain what's missing without blocking the composer.

I'd also avoid translating an empty checker setting into --guardrails off in the deployment wrappers. Inheriting configuration, configuring a checker, and explicitly disabling it are different choices. And when a model reviewer can approve eligible subagent requests, that delegation should be visible to the operator.

This is a larger change than adding startup presets: it needs session-specific effective policy bounded by server policy, rather than a fixed process-wide posture doing most of the work. But it addresses the actual UX problem. One control for what the agent may do, a separate decision for whether workspace instructions are authoritative, and mode switches that do what their labels say.

@JAORMX JAORMX left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The move to five real session modes and per-session guardrail composition is the right direction. The separate trust indicator and improved mode confirmation/refusal handling help too. But some permission layers and callers still use the old process-posture assumptions, so the selected mode does not yet reliably describe what will execute.

This follows the proposal in #1935 (comment). Reviewed the full change with independent spec, standards, test, security, architecture/API, UX, deployment and reuse reviewers. The branch was rebased during review; git range-diff confirms all 22 PR patches are unchanged. This review is pinned to 98fc96fa2f8be61916de443a17262adc50c050d0. Findings are from source tracing and CI inspection, not locally executed reproductions.

What needs fixing before merge

The inline comments cover persisted-session admission, checkerless default-auto, filesystem permissions surviving a downgrade, the incomplete TypeScript/API migration, trust still being implied by startup mode, wrapper checker opt-outs, and composer blocking.

There is one additional execution-path blocker outside a changed hunk: cmd/mecatequi/run.go:166 still calls CreateSession(ctx, session.ModeDefault, limits). It never inherits the mode resolved from the CLI. Since allow-all is now mode-driven, auto/yolo runs can ask and cancel on ordinary mutations, accept-edits does not take effect, and plan loses its hard mutation restriction (a configured Allow can authorize a write under the incorrectly selected default mode). Pass the unspecified mode so the service default applies, and test the actual CLI/composition/run boundary. Source:

mecatl/cmd/mecatequi/run.go

Lines 165 to 166 in 98fc96f

func run(ctx context.Context, svc *server.Service, limits session.Limits, prompt string, w io.Writer) (runOutcome, error) {
sess, err := svc.CreateSession(ctx, session.ModeDefault, limits)

The requested pending-approval behavior is also absent: engine/session/session.go:1188 rejects mode changes in StateAwaiting, and the TUI only cycles at idle/running. This is safely fail-closed; I am not claiming a stale verdict runs under a newly accepted mode. But it does not implement the requested ability to apply a mode transition and re-evaluate a pending ask. We should implement that boundary or explicitly agree on a narrower contract rather than describe it as completed.

Standards and test adequacy

  • internal/app/build.go:9165,9251 still tells the model there are exactly three session modes and advertises the removed trusted tokens. Update the real prompt factories and test the five-mode semantics through them. The user permissions guide, keybinding reference, action input help and mecatequi recovery message also retain removed tokens or old posture behavior. The PR description still claims seven modes and no engine/proto changes.
  • Advertise runtime-mode support through a stable feature identifier. An older server can already advertise auto/yolo posture, so posture does not establish support for the new mode enums. Document the actual wire and stored-session compatibility decision; additive enum numbering alone is not enough.
  • internal/adapter/server/mode_admission_test.go:152–163 currently requires checkerless default-auto to succeed. That is a contrary acceptance condition, not coverage of the requested guarantee.
  • The strongest new guardrail switch test changes mode before the first run. Add production-path runs before and after switches, persisted-session reload under a tighter ceiling, external file effects, and delegated children. Include planted configured Deny/Ask rules and positive controls.
  • Wrapper tests that only record argv and policy tests supplied with a mode manually miss callers such as mecatequi. The UI tests also explicitly require the composer blocking we asked to remove.

Other follow-ups

These are nonblocking findings, separate from the enforcement and compatibility blockers above:

  • Ordinary subagents/parallel branches still construct default-mode children (engine/agent/subagent.go:4201, engine/agent/parallel.go:1187). The new inheritance comment is not backed by propagation, so a yolo parent's substitution behavior can turn into child asks/review/denials. Children retain their existing builtin allow-all floor; this is not a claim that all child mutations fail.
  • ACP explicitly creates default sessions and retains its three-mode picker (internal/adapter/acp/agent.go:296,321,462). Define and migrate that integration's mode behavior.
  • The blocked-mode/help guidance says yolo needs a checker, although only auto has that prerequisite.
  • Existing Helm extraArgs with --posture=strict now conflict with the template's unconditional --permission-mode=auto. Add a chart-level mode choice and migration guidance.
  • The legacy posture-to-replacement-token mapping is copied across three command packages; reuse the composition mapping rather than maintaining three switches.
  • Exposing reviewer delegation in /posture would help discovery. Startup diagnostics already disclose it, so this is not a blocker.

I am not carrying forward the initial concern about the chart's old default image as a release blocker: release automation synchronizes the chart/image versions. Nor am I treating the pre-existing HTTP unknown-mode fallback as a newly introduced escalation.

CI on this rebased head is still running, with the two Node SDK jobs already failing. The request for changes is based on the source-level defects above, not red checks alone. The immediate priorities are admission on restored execution, enforcing auto consistently, and removing the remaining startup-posture authority from runtime mode changes.

Comment thread internal/adapter/server/service.go
Comment thread internal/adapter/server/service.go Outdated
Comment thread engine/adapter/permpolicy/permpolicy.go
Comment thread contracts/proto/mecatl/v1/harness.proto
// UNSPECIFIED mode stays empty so Service.createSession applies the server's
// configured DefaultMode (the session half of the operator's permission mode),
// matching the HTTP create path.
func createModeFromProto(m mecatlv1.PermissionMode) session.PermissionMode {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocker: this changes an existing wire contract, independently of whether adding enum values is protobuf-compatible. Previously an unspecified gRPC create produced DEFAULT; it can now produce AUTO/YOLO on the same permissive deployment. Existing conforming clients can reject those snapshots. An old client explicitly requesting DEFAULT also loses the former process-wide allow-all behavior now that policy is session-driven.

Please assess this against docs/architecture/api-surface.md:61–89. Preserve the existing contract or obtain explicit approval for a coordinated breaking transition with client/server upgrade and stored-session implications. We also need a stable feature identifier for runtime-mode support; the old posture field does not provide it. The latest commit's additive assessment and the PR body's 'no engine/proto change' statement do not account for these observable changes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessed and approved by the operator as a break: unspecified creates resolve to the server default (possibly AUTO/YOLO), and an explicit DEFAULT no longer inherits process-wide allow-all. The major stays at 1 by the operator's decision. Discovery is via the new session_permission_modes feature identifier. The API compatibility statement in the PR description now covers the old and new contracts, affected clients, and stored-session and upgrade implications. User-facing upgrade notes are in permissions-and-posture.md#upgrade-from-the-posture-model.

Comment thread internal/app/posture.go
Comment thread deploy/helm/mecak8s/templates/deployment.yaml
Comment thread cmd/mecatui/ui/mode_cycle.go
@aponcedeleonch

Copy link
Copy Markdown
Member Author

Thanks for the thorough review. Inline blockers are answered in their threads; all changes are in 9d276cc. The rest of the review body:

Blockers

  • mecatequi run mode: run now creates its session with an unspecified mode so the --permission-mode default applies. TestRunUsesPermissionModeFromFlags drives parseFlags → appConfig → Build → run.
  • Pending-approval mode switch: we're taking the narrower contract. A session awaiting an approval can't change mode; the ask resolves under the mode it was raised in, and a mode change never stops a started command. This is documented in permissions-and-posture.md and docs/architecture/governance.md, not described as a re-evaluation.

Standards / tests

  • Prompt text: the self-knowledge prompt now describes five modes, the ceiling, auto's checker requirement, and trust as a separate decision. Its behavioural claims are pinned against the implementation.
  • Stale text: the permissions guide, keybindings, using-the-tui, secure-and-expose, CLI help, the action/reusable-workflow descriptions, the mecatequi no-approver message, and the generated config/CLI references are updated.
  • Feature identifier: session_permission_modes is advertised and mirrored in the TS SDK.
  • Tests: coverage now includes production-path runs across a mode switch, a persisted-session reload under a tighter ceiling, an external-file effect, and delegated-child behaviour, with a configured-Deny positive control.

Non-blocking follow-ups (done)

  • Delegated children: Subagent children, Parallel branches, and Team members of an auto/yolo session inherit that mode (effectiveChildMode); an explicitly narrower configured mode is kept. A real parent-run test covers it, and it's recorded in engine/CHANGELOG.md.
  • ACP: sessions start in the server's default mode, and the picker offers only modes the server admits (Service.AdmittedModes), including auto/yolo when admitted.
  • Help guidance: only auto mentions a checker.
  • Helm: a chart-level permissionMode; extraArgs carrying --posture/--permission-mode/--yolo fails rendering with migration guidance.
  • Posture mapping: the legacy mapping is consolidated into app.PostureReplacementToken.
  • Reviewer visibility: an optional ServerCapabilities.subagent_ask_reviewer (on/off/inert), shown in /posture.

Known remaining

  • Serving of external relative ../ paths still follows the startup ceiling. The authorization decision follows the session mode, so this only affects whether an approved relative escape is served below yolo.
  • The repo's own mecatequi.yml now runs yolo with guardrails-model: off, since unchecked auto now refuses.

aponcedeleonch and others added 12 commits October 20, 2026 14:04
mecated.md said --guardrails off lets auto start without a checker; it is
refused, and unchecked autonomy is yolo. --trust-project no longer talks about
raising the posture. The upgrade notes add that an older TypeScript SDK sends
an explicit default over HTTP when no mode is given.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… server posture

The escape decision already keyed on the call's session mode, but the
workspace that serves an approved lexical ../ operand was built once per
root with a server-posture flag. A default session on a yolo server was
served an approved relative escape, and a yolo session lost it after the
server posture changed.

The factory workspace now never serves relative escapes. The run-entry
funnel binds a per-run view through the new optional
server.ModeScopedWorkspace capability; escapeWorkspace.ForPermissionMode
enables relative serving only for a yolo run, fails closed for any other
or unknown mode, and keeps child views confined. The mode is fixed per
run because SetMode is refused mid-turn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… agent

Only yolo selects the loose substitution evaluator, so under auto the main
agent's unprovable $(...), backtick, and subshell commands ask, as on
subagents. The governance table, the user guide, and the auto startup log
still said the main agent loses the guard. Fix them, call out the change
from --posture auto in the upgrade notes, and record the refused-mode
recovery exception in the domain model's mid-turn mode-switch invariant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An upgrade with the default permissionMode: auto and no checker rendered
fine, then crash-looped because mecak8s refuses auto without an enforcing
checker; Recreate releases went down. The chart now fails at render time
unless guardrails.model is set or the new guardrails.fromSettings asserts
that mounted settings bind the checker, and it rejects auto with
guardrails.disabled. extraArgs can no longer carry --guardrails or
--guardrails-model, and the checker choices are mutually exclusive.

Fixtures, deploy:check sets, unit suites, and the quick start now declare
a checker choice so they keep rendering.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reusable workflow only forwarded the deprecated posture input, with a
default of "auto" that differed from the action's empty default, so every
call warned and callers could not migrate. It now forwards a
permission-mode input and defaults posture to empty. The example workflow
and the microVM dev command no longer run auto without a checker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The chart now refuses to render permissionMode auto without a guardrails
checker. The continuous and release lint/render steps used overrides with
no checker choice, so they would fail. Declare guardrails.fromSettings, and
name a checker model in the published install command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cak8s and mecatequi

The three commands each carried their own copy of --permission-mode validation, the deprecation warning, and the usage line, and the help texts had drifted: mecatequi omitted the ceiling table and the --guardrails off refusal, and only mecak8s mentioned --guardrails off. Move them into app.PermissionModeFlags and app.PermissionModeHelp. The help text is now one shared line, parameterized only by what really differs: the default token, a one-shot session, the root/sandbox refusal (mecatequi has none), and the --yolo alias (mecated only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ot remove

The header's always-shown badge carries project trust, and the permission
mode appeared only in the template's mode segment, so the Minimal template
or a custom header could hide that a session runs auto or yolo.

Add renderer-owned chrome beside the trust badge: amber "⚠ auto" or the
YOLO alarm pill for a confirmed auto/yolo session, or for the process-wide
posture on a server without session_permission_modes. A header too narrow
for every indicator sheds the tail and trust badge and truncates the
identity line, but keeps this one. The default templates' mode segment is
now plain text, so the alarm is not shown twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… mode, not the posture

The shared engine's advisory demotion fell back to cfg.Posture >= yolo,
and Built.GuardrailsAdvisory, the guardrails startup line, and the
permission-mode narration keyed on the posture tier. Key the shared
engine on the server's default session mode (an unresolved mode stays
enforcing), the advisory flag and the startup line on that same
demotion, and the narration on the ceiling.

Drop the guardrails-off UNSUPERVISED note for auto: checkerRefusal now
refuses auto without an enforcing checker before that line is logged,
and the note claimed auto runs without one.

Pin that an auto agent-bound session on a yolo server keeps an
enforcing checker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-all grant

The PostureAuto/PostureYolo comments still said those tiers grant
allow-all and loosen substitution, and server.Config.Posture called the
capability display-only. Allow-all and loose substitution follow each
session's mode; the posture tier tracks the ceiling and bounds which
modes a client may cycle to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ender

The chart refuses to render permissionMode auto without a guardrails
checker, so this render fixture needs guardrails.fromSettings like the
other chart fixtures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@jhrozek jhrozek left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rechecked at c472bd9: the three HIGH findings and nearly all MEDIUM/LOW findings from the earlier review are addressed. Thanks for the thorough follow-up.

The action defaults to auto, which needs an enforcing guardrails checker.
With an empty guardrails-model and no operator settings file, or with
"off", the binary refused only after setup. Fail the step first with an
error naming guardrails-model and the yolo alternative.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@aponcedeleonch

Copy link
Copy Markdown
Member Author

Thanks for the thorough review, @jhrozek. Everything is addressed in the latest push except the crash-orphan recovery, which moves to #2425. I replied on each thread.

For the items outside the diff:

  • The API compatibility statement in the description is rewritten. It now covers refused_mode_recovery, project_trusted, the new meaning of posture, ApprovePlan rejecting AUTO/YOLO, client gating on session_permission_modes, and an explicit record of the major-1 exception.
  • Posture no longer drives guardrail demotion, GuardrailsAdvisory or the startup narration; those follow the session mode or the ceiling. The capabilities posture value is unchanged, since deriving it from the ceiling would report strict instead of trusted for --trust-project deployments. Only its docs changed.
  • The false docs, the examples workflow, the missing retry/configured-Ask/deprecation tests, the help and --trust-project wording, and the shared flag helper are all in.

One thing I found along the way: ApprovePlan with UNSPECIFIED approves, while the proto says it denies. It's the same on main, so I'll handle it in #2425.

@aponcedeleonch
aponcedeleonch merged commit fa79be7 into main Oct 20, 2026
45 checks passed
@aponcedeleonch
aponcedeleonch deleted the impl/unified-permission-mode branch October 20, 2026 12:58
aponcedeleonch added a commit that referenced this pull request Oct 21, 2026
…cted

TestUnavailableModeKeepsComposerUsable accepted any returned command as
proof that enter sent the draft, and TestUnavailableModeDoesNotHoldQueuedPrompt
never ran the drain's command. Both now execute the returned commands and
check the transport: idle sends exactly one Prompt frame carrying the draft,
running stages exactly that draft and sends nothing, and the drain sends one
Prompt frame with the queued follow-up.

Follow-up agreed in the review of #1935.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aponcedeleonch added a commit that referenced this pull request Oct 21, 2026
TestPermissionModeTokenDerivesNoTrust only read the trust_project
narration. It now also seeds an AGENTS.md sentinel, runs one turn of a
server-default session through the real Build, and checks whether the
sentinel reaches the provider request. No --permission-mode token feeds
untrusted instructions to the model, --trust-project does, and the
deprecated --posture yolo trust floor still does.

Follow-up agreed in the review of #1935.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aponcedeleonch added a commit that referenced this pull request Oct 21, 2026
Session saves overwrite whole snapshots, and the session lease is the only
thing that keeps two replicas off the same session. A store other
processes may share (a remote driver or a store without its own lease)
with no lease configured got no exclusion and no warning, and a backend
that reports leasing unsupported downgraded with only an INFO line. The
under-lease snapshot adoption in #1935 is a no-op in both setups.

Build now logs a WARN naming the lease flags when it resolves no lease for
any store other than the process-private memstore (a local StoreDir
already gets the flock lease). The run-entry and child-session
lease-unsupported downgrades log at WARN instead of INFO; the staleness
sweep's downgrade stays INFO since it only disables the sweep.

Follow-up agreed in the review of #1935 (the pre-existing concurrency
limitations). The storage-level gaps (no fencing token or aggregate
version on Save) are out of scope here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aponcedeleonch added a commit that referenced this pull request Oct 21, 2026
The --permission-mode help text in internal/app/permission_mode_flags.go
changed in #1935 without regenerating the reference, so
task docs:server-cli:check reported drift. Regenerated with
task docs:server-cli; no flag behavior changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aponcedeleonch added a commit that referenced this pull request Oct 21, 2026
…cted

TestUnavailableModeKeepsComposerUsable accepted any returned command as
proof that enter sent the draft, and TestUnavailableModeDoesNotHoldQueuedPrompt
never ran the drain's command. Both now execute the returned commands and
check the transport: idle sends exactly one Prompt frame carrying the draft,
running stages exactly that draft and sends nothing, and the drain sends one
Prompt frame with the queued follow-up.

Follow-up agreed in the review of #1935.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aponcedeleonch added a commit that referenced this pull request Oct 21, 2026
TestPermissionModeTokenDerivesNoTrust only read the trust_project
narration. It now also seeds an AGENTS.md sentinel, runs one turn of a
server-default session through the real Build, and checks whether the
sentinel reaches the provider request. No --permission-mode token feeds
untrusted instructions to the model, --trust-project does, and the
deprecated --posture yolo trust floor still does.

Follow-up agreed in the review of #1935.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aponcedeleonch added a commit that referenced this pull request Oct 21, 2026
Session saves overwrite whole snapshots, and the session lease is the only
thing that keeps two replicas off the same session. A store other
processes may share (a remote driver or a store without its own lease)
with no lease configured got no exclusion and no warning, and a backend
that reports leasing unsupported downgraded with only an INFO line. The
under-lease snapshot adoption in #1935 is a no-op in both setups.

Build now logs a WARN naming the lease flags when it resolves no lease for
any store other than the process-private memstore (a local StoreDir
already gets the flock lease). The run-entry and child-session
lease-unsupported downgrades log at WARN instead of INFO; the staleness
sweep's downgrade stays INFO since it only disables the sweep.

Follow-up agreed in the review of #1935 (the pre-existing concurrency
limitations). The storage-level gaps (no fencing token or aggregate
version on Save) are out of scope here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aponcedeleonch added a commit that referenced this pull request Oct 21, 2026
The --permission-mode help text in internal/app/permission_mode_flags.go
changed in #1935 without regenerating the reference, so
task docs:server-cli:check reported drift. Regenerated with
task docs:server-cli; no flag behavior changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@toolhive-release-app toolhive-release-app Bot mentioned this pull request Oct 23, 2026
2 tasks
aponcedeleonch added a commit that referenced this pull request Oct 26, 2026
…cted

TestUnavailableModeKeepsComposerUsable accepted any returned command as
proof that enter sent the draft, and TestUnavailableModeDoesNotHoldQueuedPrompt
never ran the drain's command. Both now execute the returned commands and
check the transport: idle sends exactly one Prompt frame carrying the draft,
running stages exactly that draft and sends nothing, and the drain sends one
Prompt frame with the queued follow-up.

Follow-up agreed in the review of #1935.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aponcedeleonch added a commit that referenced this pull request Oct 26, 2026
TestPermissionModeTokenDerivesNoTrust only read the trust_project
narration. It now also seeds an AGENTS.md sentinel, runs one turn of a
server-default session through the real Build, and checks whether the
sentinel reaches the provider request. No --permission-mode token feeds
untrusted instructions to the model, --trust-project does, and the
deprecated --posture yolo trust floor still does.

Follow-up agreed in the review of #1935.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aponcedeleonch added a commit that referenced this pull request Oct 26, 2026
Session saves overwrite whole snapshots, and the session lease is the only
thing that keeps two replicas off the same session. A store other
processes may share (a remote driver or a store without its own lease)
with no lease configured got no exclusion and no warning, and a backend
that reports leasing unsupported downgraded with only an INFO line. The
under-lease snapshot adoption in #1935 is a no-op in both setups.

Build now logs a WARN naming the lease flags when it resolves no lease for
any store other than the process-private memstore (a local StoreDir
already gets the flock lease). The run-entry and child-session
lease-unsupported downgrades log at WARN instead of INFO; the staleness
sweep's downgrade stays INFO since it only disables the sweep.

Follow-up agreed in the review of #1935 (the pre-existing concurrency
limitations). The storage-level gaps (no fencing token or aggregate
version on Save) are out of scope here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — 22fb14d6 Deployed Oct 20, 2026 by vercel[bot]
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.

3 participants