6. Permission model: auto-run by default, smart opt-in gating

Copy Markdown View Source

Date: 2026-05-29 Status: Accepted

Context

v0.1 ships with tools auto-running and the interactive permission gate deferred (ADR 0005, CONTEXT). Before building the gate we have to decide its default stance.

How people actually use coding agents informs this: the maintainer — and most users — run Claude/Codex in "YOLO" / --dangerously-skip-permissions mode ~99% of the time. A gate that prompts on every write or command is friction users immediately disable, which means a prompt-by-default design optimizes for a mode almost nobody runs. Modern models also rarely take harmful actions unprompted, and Pixir already bounds the blast radius two ways: Workspace confinement (a hard floor) and dry-run (ADR 0005).

So the question is not "how do we ask about everything safely" but "how do we make the frictionless path the blessed default, and make the optional gate smart enough that turning it on isn't painful either."

Decision

Permission handling is a mode, scoped to a Turn (sourced from the Session / CLI), with :auto as the default:

  • :auto (default) — every tool runs, no prompts. This is a first-class, blessed mode (not "dangerous mode"); it is how Pixir is expected to run most of the time.
  • :ask (opt-in) — gate only genuinely risky operations, and be smart about it:
    • read and other read-only tools never ask.
    • bash commands on a conservative safe-list (read-only commands like ls, cat, grep, git status|diff|log, with no chaining/redirection/substitution) auto-run.
    • Only write and non-safe-listed bash actually prompt.
  • :read_only (opt-in) — mutating tools are denied (not asked); reads and safe commands run. Pairs with a future read-only/plan role.

Always-on floor, every mode: Workspace confinement. Paths outside the Workspace are refused, never "asked about" — confinement is the real safety boundary; the gate is a convenience layer on top, not the floor.

Decisions in :ask/:read_only are recorded as canonical permission_decision Events (already reserved in ADR 0004), so they are auditable in the Log and can be answered by any front-end (the CLI today; the REPL/web later). The front-end supplies an asker; the core never blocks on a hard-coded prompt.

Deferred refinements (not v1): session-scoped "remember / always allow this command" allow-lists, per-role default modes, and pattern-based rules. v1 keeps the policy stateless per call — the safe-list already removes most friction, and :auto remains the default.

Consequences

  • The default matches reality — no friction for the way Pixir is actually used; you opt into gating, not out of it.
  • Confinement stays the floor — safety doesn't depend on the user keeping prompts on.
  • :ask is tolerable when chosen — reads and safe commands never interrupt; only real mutations prompt. It shines with the REPL (an interactive answerer).
  • Auditablepermission_decision Events make gated runs replayable.
  • Cost — the bash safe-list is a heuristic (first token + no shell metacharacters); it errs toward asking, never toward silently running something risky. Maintaining it is a small, deliberate vocabulary.
  • The enforcement points are Pixir.Permissions (pure policy) and the Executor (which consults it and invokes the asker); :auto short-circuits to allow with zero overhead.