@czottmann/pi-automode 1.10.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ ## [1.12.0] - 2026-08-23
6
+
7
+ ## New features
8
+
9
+ - **Deterministic permission allows** — Add user-owned `permissions.allow` patterns that skip classifier review after deterministic checks pass. Accepted ask rules still require classifier review. Thank you, @sergeykonkin! (#14)
10
+ - **Configurable classifier request timeout** — new `autoMode.classifierTimeoutMs` setting. The default is 20000 ms. The timeout applies to each classifier request. The fast stage and the detailed stage each have their own budget. A request that exceeds the timeout is aborted. Auto mode fails closed and blocks the action.
11
+ - **Read-only agent diagnostics** — Add `automode_inspect` tool for status, configuration, defaults, and recent denial metadata. Thank you, @blalor! (#11)
12
+
13
+ ## Bug fixes
14
+
15
+ - **Reject invalid config values** — Invalid boolean and log config values (e.g. `enabled: 0`, `log.enabled: 1`) are now rejected at merge time instead of being applied with diagnostics only. (#20)
16
+ - **Preserve defaults for malformed rule lists** — A malformed `hard_deny` entry like `[42]` no longer strips all built-in hard-deny rules. Malformed entries are rejected and defaults preserved conservatively. (#21)
17
+ - **Runtime classifier providers** — Dispatch classifier calls through Pi's runtime model registry so providers registered with `pi.registerProvider()` work immediately. Preserve normalized reasoning and header-only authentication on the temporary simple-completion bridge. (#15)
18
+ - **Bounded wildcard matching** — Replace regex-based permission and denied-path globs with a linear-time matcher. Reject oversized patterns and fail closed for oversized runtime inputs. (#19)
19
+ - **Complete classifier action input** — Send the exact current tool input to both classifier stages in a dedicated message. Block the action if it cannot fit without truncation. (#17)
20
+ - **Path policy normalization** — Use Pi-compatible resolution for file-tool paths, including file URLs, `@` and tilde aliases, and read fallback names. Enforce denied paths across omitted and recursive search scopes and both sides of symlink aliases. (#18)
21
+ - **Project config trust gate** — Ignore `.pi/automode.local.json` and `.pi/automode.json` until Pi trusts the project. Apply the trust gate during startup and config reloads. (#16)
22
+ - **In-memory observability logs** — Write logs to an extension-owned directory (`~/.pi/agent/extensions/pi-automode/logs/`) instead of the launching project directory. Thanks, @HerbertGao! (#13)
23
+
24
+ [1.12.0]: https://github.com/czottmann/pi-automode/compare/v1.11.0...v1.12.0
package/README.md CHANGED
@@ -4,7 +4,9 @@ Claude Code-style auto mode for Pi.
4
4
 
5
5
  This is a guardrail extension. It intercepts agent tool calls before execution and blocks actions that match permission deny rules, deterministic hard-deny checks, or the auto-mode classifier's block decision.
6
6
 
7
- It is not a sandbox. Extensions run in the Pi process, and a determined malicious extension can do anything your user account can do. It also does not guard user `!` / `!!` shell commands; by design, it guards agent tool calls only. Use this to reduce unsafe autonomous tool use, not as an OS security boundary.
7
+ It is not a sandbox. Extensions run in the Pi process. A malicious extension can do anything that your user account can do.
8
+
9
+ Pi-automode does not guard user `!` or `!!` shell commands. It guards only agent tool calls. Use it to reduce unsafe autonomous tool use. Do not use it as an OS security boundary.
8
10
 
9
11
  ## Install
10
12
 
@@ -43,128 +45,53 @@ pi -e ./extensions/auto-mode.ts
43
45
 
44
46
  `/auto-mode` is an alias.
45
47
 
46
- ## Status line
48
+ ## Agent diagnostics
47
49
 
48
- When the Pi TUI is available, the extension renders a persistent status line:
50
+ The package registers one model-callable, read-only tool:
49
51
 
50
- ```text
51
- AM● a:12 d:2 ca:5 cd:1
52
- ```
52
+ `automode_inspect` accepts one `action`:
53
53
 
54
- - `AM` auto-mode prefix; `●` when enabled, `○` when disabled (via config or `/automode off`).
55
- - `a:` actions allowed so far (checked minus denied).
56
- - `d:` actions denied so far, for any reason (permission rule, deterministic hard-deny, or classifier).
57
- - `ca:` / `cd:` classifier decisions split into allowed vs denied. These segments appear only after the classifier has run at least once; `d:` counts all denials, so `d:` is always `>= cd:`.
54
+ - `status`: active state and counters
55
+ - `config`: active effective configuration, log path, and diagnostics
56
+ - `defaults`: built-in rule lists
57
+ - `denials`: recent denial timestamps, kinds, and tool names
58
58
 
59
- ## Docs
59
+ The tool reads the same in-memory configuration and state that the guardrail enforces. Permission and deterministic checks run before the bypass.
60
60
 
61
- - [Defaults and rule-list behavior](docs/defaults.md)
62
- - [Auto-mode classifier flow](docs/automode-classifier-flow.md)
63
- - [Observability logging](docs/observability-logging.md)
61
+ The bypass does not change automode counters, persisted state, or observability logs. The extension verifies the source of the tool before it applies the exemption. Thus, a name collision does not exempt a tool from another extension.
64
62
 
65
- ## Configuration
63
+ The tool cannot enable or disable auto mode. It cannot reload configuration, reset state, select a model, or change configuration.
66
64
 
67
- The extension follows Claude Code's documented config model where Pi can support it.
65
+ Pi sends tool output to the current model. The `status` and `denials` views omit denial reasons and action payloads.
68
66
 
69
- It reads `autoMode` from Pi-owned config only:
67
+ If a diagnosis requires a reason, inspect a known-safe entry in the local observability log. The `config` view includes effective rule text. It removes raw JSON parser details from diagnostics.
70
68
 
71
- - `~/.pi/agent/automode.json`
72
- - `.pi/automode.local.json`
73
- - `PI_AUTOMODE_SETTINGS_JSON`
69
+ Do not put credentials or other secrets in automode rules.
74
70
 
75
- It deliberately does not read `autoMode` from shared project `.pi/automode.json`, because a checked-in repo should not be able to weaken auto-mode rules. Shared project config may still contribute `permissions.deny` and `permissions.ask`.
71
+ The bundled `automode-diagnostics` skill uses this tool to diagnose unexpected decisions without asking the user to copy output from slash commands. Configuration edits and automode state changes remain user-controlled. See [Agent diagnostics](docs/diagnostics.md) for the inspection contract, privacy limits, and diagnosis workflow.
76
72
 
77
- To disable pi-automode for the current project, create or edit `.pi/automode.local.json`:
78
-
79
- ```json
80
- {
81
- "autoMode": {
82
- "enabled": false
83
- }
84
- }
85
- ```
86
-
87
- This is project-local and should not be committed. Shared project `.pi/automode.json` cannot disable auto-mode.
88
-
89
- Set a global default classifier model in `~/.pi/agent/automode.json`; override it per project in `.pi/automode.local.json`.
90
-
91
- `classifierReasoningLevel` optionally requests `low`, `medium`, `high`, `xhigh`, or `max` reasoning for both classifier stages. If the key is absent, pi-automode sends no reasoning preference and leaves the choice to the server. Pi AI clamps unsupported values to the nearest level supported by the selected model; a non-reasoning model resolves to `off`. `low` matches Codex Auto Review's reasoning effort and the practical default when an explicit value is needed. Higher levels can consume the existing 512/1200-token stage limits before producing visible output, which causes the classifier to fail closed. Raise `fastClassifierMaxTokens` (default 512, integer ≥ 16) if you run a reasoning model whose fast-stage budget is truncated before it emits the required `0`/`1` digit.
92
-
93
- `allowInsideWorkingDirectory` (default `false`) adds a deterministic silent-allow tier for the file tools (`read`, `write`, `edit`, `grep`, `find`, `ls`): when `true`, a call whose resolved path is inside the working directory is allowed without any classifier call, and file access outside the working directory is routed to the classifier (including reads, which would otherwise take the read-only fast path). This matches the Codex/Claude Code "inside the sandbox = silent, outside = review" model. The tier takes precedence over `classifyReadOnlyTools`: with both enabled, in-tree file access is still allowed without a classifier call, and out-of-tree file access is classified. `classifyReadOnlyTools: true` only routes in-tree reads to the classifier when `allowInsideWorkingDirectory` is `false`.
94
-
95
- `deniedPaths` (default `[]`) is a list of path glob patterns that are hard-denied before the classifier and before the inside-working-directory tier — the file-tool equivalent of a secret/system deny list. Patterns support `~`, `$HOME`, and `${HOME}` expansion and `*` (which matches any characters, including `/`, so `**/id_rsa` matches a private key at any depth). Matching checks both the path as typed and its symlink-resolved form, so a `~/.ssh/*` rule still matches when `~/.ssh` is a symlink. A matching path blocks the call unconditionally (no classifier, no override). The deny list applies to file tools only; `bash` path access is governed by the classifier. Both keys follow the normal scalar/array precedence.
96
-
97
- The setting follows the normal scalar precedence: global, then project-local, then `PI_AUTOMODE_SETTINGS_JSON`. Shared project `.pi/automode.json` cannot set it. Omitting the key at a higher-precedence scope does not clear a lower-precedence value.
98
-
99
- Example:
100
-
101
- ```json
102
- {
103
- "autoMode": {
104
- "classifierModel": "provider/model-id",
105
- "classifierReasoningLevel": "low",
106
- "classifyReadOnlyTools": false,
107
- "fastClassifierMaxTokens": 512,
108
- "allowInsideWorkingDirectory": false,
109
- "deniedPaths": [],
110
- "maxUserTranscriptTokens": 4000,
111
- "maxToolTranscriptTokens": 4000,
112
- "environment": [
113
- "$defaults",
114
- "Source control: github.example.com/acme-corp and all repos under it",
115
- "Trusted internal domains: *.corp.example.com, git.example.com",
116
- "Trusted cloud buckets: s3://acme-dev-artifacts, gs://acme-ci-cache",
117
- "Key internal services: staging deploy API at deploy.corp.example.com"
118
- ],
119
- "allow": ["$defaults"],
120
- "protectedPaths": ["$defaults"],
121
- "soft_deny": ["$defaults"],
122
- "hard_deny": [
123
- "$defaults",
124
- "Never send repository contents to third-party code-review APIs"
125
- ]
126
- },
127
- "permissions": {
128
- "deny": ["bash(rm -rf *)"],
129
- "ask": ["bash(git push *)"]
130
- }
131
- }
132
- ```
133
-
134
- `maxUserTranscriptTokens` and `maxToolTranscriptTokens` are approximate per-category budgets; both default to 4000 and accept integers of at least 32. The former `maxTranscriptLines` setting is no longer supported because evidence selection is token-budgeted rather than line-based.
135
-
136
- ### Ask-user tools and explicit authorization
137
-
138
- Classifier evidence includes normal user messages and assistant tool-call inputs, but excludes assistant prose and all tool results. This includes answers returned by ask-user tools such as `@vanillagreen/pi-questions`. Selecting "Yes" there helps the agent decide what to do next, but pi-automode does not treat that tool result as explicit authorization to override a soft deny. Send the authorization as a normal chat message instead; the agent can then retry the action. Tool results are excluded because they may contain untrusted or prompt-injected content.
139
-
140
- ### `$defaults`
141
-
142
- See [Defaults and rule-list behavior](docs/defaults.md) for built-in `environment`, `allow`, `protectedPaths`, `soft_deny`, and `hard_deny` entries, plus replacement behavior when `$defaults` is omitted.
143
-
144
- ### Observability logging
73
+ ## Status line
145
74
 
146
- Auto mode can write a JSONL observability log next to the current Pi session file, so you can inspect decisions and classifier usage. It is off by default.
75
+ When the Pi TUI is available, the extension renders a persistent status line:
147
76
 
148
- ```json
149
- {
150
- "autoMode": {
151
- "log": {
152
- "enabled": true,
153
- "classifierIo": false
154
- }
155
- }
156
- }
77
+ ```text
78
+ AM● a:12 d:2 ca:5 cd:1
157
79
  ```
158
80
 
159
- With logging enabled, the sidecar also writes ccusage-compatible entries for every classifier response. `ccusage pi` reports this usage as a separate `-pi-automode` session even when `classifierIo` is off.
160
-
161
- See [Observability logging](docs/observability-logging.md) for the log file location, entry schema, and the `classifierIo` privacy tradeoff. Run `/automode config` to see the resolved log file path.
81
+ - `AM` auto-mode prefix. `●` means enabled. `○` means disabled through configuration or `/automode off`.
82
+ - `a:` — actions allowed so far (checked minus denied).
83
+ - `d:` actions denied so far, for any reason (permission rule, deterministic hard-deny, or classifier).
84
+ - `ca:` / `cd:` — classifier decisions split into allowed and denied. These segments appear after the first classifier call. `d:` counts all denials, so `d:` is always `>= cd:`.
162
85
 
163
- ### Permission patterns
86
+ ## Docs
164
87
 
165
- Permission patterns use Pi tool names, for example `bash(...)`, `write(...)`, `edit(...)`, `read(...)`. The parser accepts capitalized names like `Bash(...)` for convenience, but the documented form is lowercase because Pi tool names are lowercase.
88
+ - [Configuration](docs/configuration.md)
89
+ - [Defaults and rule-list behavior](docs/defaults.md)
90
+ - [Auto-mode classifier flow](docs/automode-classifier-flow.md)
91
+ - [Observability logging](docs/observability-logging.md)
92
+ - [Architecture decisions](docs/adr/INDEX.md)
166
93
 
167
- ## What is enforced before the classifier
94
+ ## What runs before the classifier
168
95
 
169
96
  The extension blocks these before any allow or classifier decision:
170
97
 
@@ -177,11 +104,25 @@ The extension blocks these before any allow or classifier decision:
177
104
  - root, home, and system-path destructive deletes
178
105
  - edits to `.pi/automode*`, `.pi` auto-mode files, and this extension's safety-control files
179
106
 
180
- Read-only Pi tools (`read`, `grep`, `find`, `ls`) are allowed after those checks. Every side-effecting action goes to the classifier, including all `write` and `edit` calls, `bash`, MCP, subagent, network-capable tools, and unknown tools. This keeps classifier hard-deny rules unconditional; direct file writes cannot bypass them. Set `classifyReadOnlyTools: true` to route read-only tools through the classifier as well, so reads outside the trusted working tree can be denied by policy. With it enabled, every `read`, `grep`, `find`, and `ls` call runs the two-stage classifier, which raises the number of model calls, the latency, and the cost per session.
107
+ After these checks, pi-automode applies `permissions.allow`. Protected `write` and `edit` targets continue to the classifier.
108
+
109
+ Accepted ask rules also continue to the classifier. They cannot use an allow tier. Pi-automode then allows the read-only tools `read`, `grep`, `find`, and `ls`. Every remaining action goes to the classifier.
110
+
111
+ A `permissions.allow` rule intentionally skips classifier policy, including classifier `hard_deny` rules. Use narrow patterns and user-owned configuration. Deterministic hard-deny and path controls remain unconditional.
181
112
 
182
- Path matches in `deniedPaths` are blocked before every classifier and fast-path decision, so secret and system paths never reach the model through the file tools. The deny list does not govern `bash`; shell access to those paths is handled by the classifier and the deterministic hard-deny checks. With `allowInsideWorkingDirectory: true`, file tools inside the working directory are allowed without a classifier call, and outside-working-directory file access (reads included) goes to the classifier.
113
+ Set `classifyReadOnlyTools: true` (default `false`) to route read-only tools through the classifier. This configuration value increases model calls, latency, and session cost.
183
114
 
184
- Classification starts with a one-token conservative filter and runs structured review only when that filter requests it. Both stages use a classifier-specific session key and short provider cache retention where the provider supports it. Missing models, provider failures, or malformed responses block the action.
115
+ Pi-automode blocks a `deniedPaths` match before classifier review or an allow tier. Thus, file tools cannot send matching secret or system paths to the model.
116
+
117
+ The list does not govern `bash`. The classifier and deterministic hard-deny checks govern shell access to these paths.
118
+
119
+ The value `allowInsideWorkingDirectory: true` allows file access inside the working directory locally. Pi-automode sends all outside file access to the classifier, including reads.
120
+
121
+ Classification starts with a conservative one-token filter. If the filter requests review, pi-automode runs structured review.
122
+
123
+ Both stages receive the complete current tool input in a dedicated message. Transcript truncation cannot remove action content. If the exact input cannot fit in the classifier context, auto mode blocks the call.
124
+
125
+ Both stages use a classifier-specific session key. They request short cache retention from providers that support it. A missing model, provider failure, or malformed response blocks the action.
185
126
 
186
127
  ## Examples
187
128
 
@@ -199,19 +140,44 @@ npm test
199
140
  npm pack --dry-run
200
141
  ```
201
142
 
202
- The tests cover the risky parts: scoped permission matching, config-source precedence, `$defaults` behavior, config diagnostics, deterministic hard-deny checks, shell parsing, write/edit classifier routing, symlink-aware safety-control checks, token-budgeted transcript selection, staged classifier parsing and caching options, and hook-level blocking/allowing.
143
+ The tests cover these safety-sensitive areas:
144
+
145
+ - scoped permission matching
146
+ - the `permissions.allow` tier and its precedence
147
+ - configuration-source precedence and diagnostics
148
+ - `$defaults` behavior
149
+ - deterministic hard-deny checks and shell parsing
150
+ - classifier routing for `write` and `edit`
151
+ - symlink-aware safety-control checks
152
+ - token-budgeted transcript selection
153
+ - staged classifier parsing and cache behavior
154
+ - hook-level allow and block behavior
203
155
 
204
156
  ## Publishing
205
157
 
206
- GitHub Actions publishes the package to npm when a GitHub Release is published. The release tag must match `package.json` exactly, with or without a leading `v` (`v1.0.0` and `1.0.0` both work for version `1.0.0`).
158
+ When a maintainer publishes a GitHub Release, GitHub Actions publishes the package to npm. The release tag must match `package.json` exactly. The forms `v1.0.0` and `1.0.0` both match version `1.0.0`.
207
159
 
208
160
  The workflow uses npm Trusted Publishing, so it does not need an npm token secret. Configure this package on npm with this repository and workflow file (`.github/workflows/publish.yml`). The workflow builds the package, runs `npm run check`, and publishes with npm provenance.
209
161
 
210
162
  ### Release tag must point at the version bump
211
163
 
212
- The publish workflow checks out the commit the release tag points at and compares `package.json` against the tag name. **The tag must point at a commit where `package.json` already carries the new version.** Concretely: commit the version bump (`chore: release X.Y.Z`), push `main`, then create the GitHub release targeting that pushed commit. Creating the release before pushing the version bump — or targeting a commit that still has the old version — fails the `Check release tag` step with `Release tag (vX.Y.Z) does not match package.json version (x.y.z)`.
164
+ The publish workflow checks the commit that the release tag identifies. It compares the version in `package.json` with the tag name.
165
+
166
+ **The tag must identify a commit that contains the new version.**
167
+
168
+ 1. Commit the version change with `chore: release X.Y.Z`.
169
+ 2. Push `main`.
170
+ 3. Create the GitHub release from that commit.
171
+
172
+ If the tag identifies an older version, the `Check release tag` step fails. It reports `Release tag (vX.Y.Z) does not match package.json version (x.y.z)`.
173
+
174
+ CAUTION: Do not move a correct release tag. Moving the tag changes a published reference.
175
+
176
+ 1. If the tag identifies the wrong commit, force-move it to the version-change commit.
177
+ 2. Force-push the corrected tag.
178
+ 3. Run `gh workflow run publish.yml --ref vX.Y.Z`.
213
179
 
214
- If the tag was cut against the wrong commit, fix it by force-moving it to the version-bump commit and pushing, then trigger the workflow via `gh workflow run publish.yml --ref vX.Y.Z` (the `release` event fires on tag creation; re-running a failed `release`-triggered run reuses the original ref and won't pick up the moved tag).
180
+ When GitHub creates the tag, the `release` event occurs. A rerun of a failed `release` workflow uses the original reference. It does not use the moved tag.
215
181
 
216
182
  ## Author
217
183
 
package/docs/GLOSSARY.md CHANGED
@@ -1,35 +1,39 @@
1
1
  # Glossary
2
2
 
3
- Canonical vocabulary for `@czottmann/pi-automode`. Project-specific terms only standard technical words appear here only when this project uses them in a specific way. Longer explanations live in the docs linked from each entry.
3
+ This glossary defines project terms for `@czottmann/pi-automode`. It does not define standard technical words. Each entry links to a longer explanation where applicable.
4
4
 
5
5
  ## Enforcement flow
6
6
 
7
- The ordered pipeline that runs on every agent tool call before execution. See [Auto-mode classifier flow](automode-classifier-flow.md).
7
+ The enforcement flow is the ordered pipeline that runs before each agent tool call. See [Auto-mode classifier flow](automode-classifier-flow.md).
8
8
 
9
- **Auto mode** — Claude Code-style guardrail posture: a pre-execution classifier allows routine, reversible actions and blocks risky ones, replacing routine permission prompts.
9
+ **Auto mode** — A Claude Code-style guardrail posture. A pre-execution classifier allows routine, reversible actions and blocks risky actions. It replaces routine permission prompts.
10
10
 
11
- **Deterministic hard-deny** — Local code checks that block high-risk actions before any classifier call and cannot be overridden. Distinct from the config-level [hard_deny](#classifier-policy-and-rules); independent of the model.
11
+ **Deterministic hard-deny** — Local code checks that block high-risk actions before classifier review. A user or model cannot override them. They are independent of the classifier-level [hard_deny](#classifier-policy-and-rules) rules.
12
12
 
13
- **Read-only bypass** — The default fast path where the read-only tools (`read`, `grep`, `find`, `ls`) are allowed without classifier review once permission and deterministic checks pass. `classifyReadOnlyTools` routes them through the classifier instead.
13
+ **`permissions.allow` tier** — A user-owned list of scoped tool patterns. A match skips classifier review after deterministic checks pass. An accepted `permissions.ask` rule disables this tier for the current call. The tier cannot override a denial or cover protected `write` and `edit` targets. See [ADR-001](adr/ADR-001-permission-precedence-and-trust-boundaries.md).
14
14
 
15
- **Staged classifier** — The two-stage safety classifier: a conservative one-token filter gates an optional structured review. See [Fast stage](#enforcement-flow) and [Detailed stage](#enforcement-flow).
15
+ **Read-only bypass** — The default allow tier for `read`, `grep`, `find`, and `ls`. Permission and deterministic checks run before this tier. `classifyReadOnlyTools` sends these calls to the classifier instead.
16
16
 
17
- **Fast stage** — The first classifier stage: a one-token filter that returns `0` (clearly allowed) or `1` (may need review).
17
+ **Staged classifier** — A two-stage safety classifier. A conservative one-token filter controls access to an optional structured review. See [Fast stage](#enforcement-flow) and [Detailed stage](#enforcement-flow).
18
18
 
19
- **Detailed stage** — The second classifier stage, run when the fast stage requests review, that returns a structured allow/block decision.
19
+ **Fast stage** — The first classifier stage. It returns `0` for a clearly allowed action or `1` for an action that can require review.
20
+
21
+ **Detailed stage** — The second classifier stage. It runs after a fast-stage review result and returns a structured allow or block decision.
20
22
 
21
23
  ## Classifier policy and rules
22
24
 
23
- The classifier's deny tiers and rule-list syntax. See [Defaults and rule-list behavior](defaults.md).
25
+ The classifier policy defines denial tiers and rule-list syntax. See [Defaults and rule-list behavior](defaults.md).
26
+
27
+ **hard_deny** — Classifier rules that block unconditionally. They are independent of the code-level [deterministic hard-deny](#enforcement-flow) checks.
24
28
 
25
- **hard_deny** — Unconditional classifier rules that cannot be overridden. Distinct from the code-level [deterministic hard-deny](#enforcement-flow) checks.
29
+ **soft_deny** — Classifier rules that normally block but support defined overrides. Unlike [hard_deny](#classifier-policy-and-rules), these rules are not unconditional.
26
30
 
27
- **soft_deny** — Overridable classifier block rules, unlike [hard_deny](#classifier-policy-and-rules).
31
+ **explicit_intent** — A classifier tier for direct user authorization. The latest user instruction specifically authorizes an action that matches a [soft_deny](#classifier-policy-and-rules) rule.
28
32
 
29
- **explicit_intent** — A classifier tier meaning the allow was justified because the user's latest instruction directly and specifically authorized an otherwise [soft-denied](#classifier-policy-and-rules) action.
33
+ **allow exception** (`autoMode.allow`) — A prose rule that overrides a matching [soft_deny](#classifier-policy-and-rules) rule. It cannot override [hard_deny](#classifier-policy-and-rules). It is independent of the [`permissions.allow` tier](#enforcement-flow).
30
34
 
31
- **`$defaults`** — A section-local marker in a rule list that expands to the built-in entries for that list.
35
+ **`$defaults`** — A section-local marker in a rule list. It expands to the built-in entries for that list.
32
36
 
33
37
  ## Status
34
38
 
35
- **AM status line** — The persistent TUI footer (prefixed `AM`) that reports auto-mode status: enabled/disabled and action/classifier counts.
39
+ **AM status line** — The persistent TUI footer that starts with `AM`. It reports the auto-mode state and action counts.
@@ -0,0 +1,44 @@
1
+ # ADR-001: Permission precedence and configuration trust boundaries
2
+
3
+ **Date:** 2026-08-23
4
+
5
+ ## Context
6
+
7
+ Pi-automode supports `permissions.deny`, `permissions.ask`, and `permissions.allow` tool patterns. These rules affect whether a tool call reaches the classifier.
8
+
9
+ An allow rule reduces classifier coverage. A checked-in repository file must not reduce that coverage. An accepted ask rule must also keep its promise of classifier review.
10
+
11
+ The wildcard matcher limits its input size to keep matching time bounded. Deny and ask rules must fail closed for oversized inputs. The same behavior is unsafe for allow rules.
12
+
13
+ ## Decision
14
+
15
+ Pi-automode uses this permission policy:
16
+
17
+ 1. A matching `permissions.deny` rule blocks the tool call.
18
+ 2. A matching `permissions.ask` rule requires user confirmation.
19
+ 3. If the user declines, pi-automode blocks the tool call.
20
+ 4. If the user accepts, deterministic denial checks continue.
21
+ 5. After these checks pass, the classifier reviews the accepted tool call.
22
+ 6. An accepted ask rule disables all later deterministic allow tiers for that call.
23
+ 7. If no ask rule matched, a `permissions.allow` rule can skip classifier review.
24
+ 8. Deterministic hard-deny checks, path-deny checks, and protected-path controls take precedence over allow rules.
25
+
26
+ Pi-automode reads `permissions.allow` only from user-owned configuration sources:
27
+
28
+ - `~/.pi/agent/automode.json`
29
+ - trusted `.pi/automode.local.json`
30
+ - `PI_AUTOMODE_SETTINGS_JSON`
31
+
32
+ Shared `.pi/automode.json` can add deny and ask rules after project trust. It cannot add allow rules.
33
+
34
+ For an oversized matcher input, deny and ask rules return a match. Allow rules return no match. This behavior keeps denial rules fail-closed without broadening allow rules.
35
+
36
+ ## Consequences
37
+
38
+ A repository cannot use checked-in configuration to remove classifier review. Users can still add project-specific allow rules in the local configuration file.
39
+
40
+ An accepted ask rule always causes classifier review after deterministic checks pass. This behavior is stricter than a model where confirmation grants direct permission.
41
+
42
+ The matcher needs separate overflow behavior for denial and allow contexts. Tests must cover both behaviors.
43
+
44
+ The permission lists are not symmetric across configuration sources. The documentation and diagnostics must make this difference clear.
@@ -0,0 +1,5 @@
1
+ # Architecture Decision Records
2
+
3
+ | # | Decision | Date |
4
+ |---|----------|------|
5
+ | [ADR-001](ADR-001-permission-precedence-and-trust-boundaries.md) | Permission precedence and configuration trust boundaries | 2026-08-23 |