@czottmann/pi-automode 1.16.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 +21 -1
- package/README.md +11 -26
- package/docs/GLOSSARY.md +1 -1
- package/docs/automode-classifier-flow.md +45 -23
- package/docs/configuration.md +35 -0
- package/docs/diagnostics.md +7 -1
- package/docs/observability-logging.md +2 -1
- package/docs/permission-recipes.md +243 -0
- package/extensions/auto-mode/classifier.ts +223 -163
- package/extensions/auto-mode/config.ts +16 -3
- package/extensions/auto-mode/constants.ts +4 -3
- package/extensions/auto-mode/extension.ts +37 -19
- package/extensions/auto-mode/permissions.ts +101 -1
- package/extensions/auto-mode/state.ts +1 -1
- package/extensions/auto-mode/types.ts +4 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@ 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
|
+
|
|
7
26
|
## [1.16.0] - 2026-09-07
|
|
8
27
|
|
|
9
28
|
## New features
|
|
@@ -62,7 +81,8 @@ All notable changes to this project are documented in this file.
|
|
|
62
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)
|
|
63
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)
|
|
64
83
|
|
|
65
|
-
[Unreleased]: https://github.com/czottmann/pi-automode/compare/v1.
|
|
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
|
|
66
86
|
[1.16.0]: https://github.com/czottmann/pi-automode/compare/v1.15.0...v1.16.0
|
|
67
87
|
[1.15.0]: https://github.com/czottmann/pi-automode/compare/v1.14.0...v1.15.0
|
|
68
88
|
[1.14.0]: https://github.com/czottmann/pi-automode/compare/v1.13.0...v1.14.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,37 +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
|
|
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
|
-
|
|
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
|
|
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.
|
|
136
|
-
|
|
137
|
-
### Pro tip
|
|
138
|
-
|
|
139
|
-
The default rule set might cause denials like this:
|
|
140
|
-
|
|
141
|
-
> Auto mode blocked subagent: Delegated workers would modify pre-existing files outside the user's bounded authorization, including migrations, CLI code, and approval documentation.
|
|
142
|
-
|
|
143
|
-
This is to be expected as the included classifier rules are very conservative and err on the side of caution! To explicitly give your robot more leeway, adjust your global `autoMode.allow` entry (or its project-level counterparts). For example, my personal `~/.pi/agent/extensions/pi-automode/config.json` file looks like this:
|
|
144
|
-
|
|
145
|
-
```json
|
|
146
|
-
{
|
|
147
|
-
"autoMode": {
|
|
148
|
-
"allow": [
|
|
149
|
-
"$defaults",
|
|
150
|
-
"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/worktree, external systems, credentials, safety controls, and Git history changes."
|
|
151
|
-
],
|
|
152
|
-
…
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
```
|
|
140
|
+
- [Permission recipes](docs/permission-recipes.md): use narrow examples for common permission and classifier-policy changes.
|
|
156
141
|
|
|
157
142
|
## Known limits
|
|
158
143
|
|
|
@@ -176,12 +161,12 @@ The tests cover these safety-sensitive areas:
|
|
|
176
161
|
- classifier routing for `write` and `edit`
|
|
177
162
|
- symlink-aware safety-control checks
|
|
178
163
|
- token-budgeted transcript selection
|
|
179
|
-
- staged classifier parsing and cache behavior
|
|
164
|
+
- staged classifier tool-call parsing and cache behavior
|
|
180
165
|
- hook-level allow and block behavior
|
|
181
166
|
|
|
182
167
|
## Publishing
|
|
183
168
|
|
|
184
|
-
When a maintainer publishes a GitHub Release, GitHub Actions publishes the package to npm. The release tag must
|
|
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`.
|
|
185
170
|
|
|
186
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.
|
|
187
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
|
|
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
|
|
|
@@ -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[
|
|
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
|
|
|
@@ -260,23 +260,21 @@ The prompt defines the policy semantics:
|
|
|
260
260
|
- the classifier cannot invent deny rules or treat the allow-exception list as exhaustive
|
|
261
261
|
- the classifier allows actions that match no hard-deny or soft-deny rule
|
|
262
262
|
|
|
263
|
-
The fast stage must return exactly `0` for clearly allowed or `1` for review. A `1` response triggers the detailed stage
|
|
263
|
+
The fast stage must return exactly `0` for clearly allowed or `1` for review. A `1` response triggers the detailed stage.
|
|
264
264
|
|
|
265
|
-
|
|
266
|
-
{"decision":"allow","tier":"allow","reason":"brief concrete reason"}
|
|
267
|
-
```
|
|
265
|
+
The detailed request provides one internal `classifier_decision` tool. This tool exists only in the nested classifier context.
|
|
268
266
|
|
|
269
|
-
|
|
267
|
+
Pi-automode does not register the tool with `pi.registerTool()`, and it has no execution handler. Thus, the main agent cannot call it.
|
|
270
268
|
|
|
271
|
-
|
|
272
|
-
{"decision":"block","tier":"soft_deny","reason":"brief concrete reason"}
|
|
273
|
-
```
|
|
269
|
+
The tool has these arguments:
|
|
274
270
|
|
|
275
|
-
|
|
271
|
+
- `decision`: `allow` or `block`
|
|
272
|
+
- `tier`: `hard_deny`, `soft_deny`, `allow`, `explicit_intent`, or `none`
|
|
273
|
+
- `reason`: a non-empty string
|
|
276
274
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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.
|
|
280
278
|
|
|
281
279
|
An `allow` decision can use `allow`, `explicit_intent`, or `none`. A `block` decision can use `hard_deny`, `soft_deny`, or `none`.
|
|
282
280
|
|
|
@@ -303,9 +301,13 @@ separate from the transcript. Pi-automode does not truncate it:
|
|
|
303
301
|
{"toolName":"bash","input":{"command":"npm test"}}
|
|
304
302
|
```
|
|
305
303
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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.
|
|
309
311
|
|
|
310
312
|
Pi-automode builds the transcript from active Pi context entries. It includes only:
|
|
311
313
|
|
|
@@ -345,13 +347,15 @@ No classifier model/API key available; auto mode fails closed.
|
|
|
345
347
|
|
|
346
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.
|
|
347
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
|
+
|
|
348
352
|
`autoMode.classifierTimeoutMs` limits each fast-stage and detailed-stage request. The default is 20000 ms.
|
|
349
353
|
|
|
350
354
|
If a request exceeds its budget, pi-automode aborts it and blocks the action. A stalled provider stream has the same result.
|
|
351
355
|
|
|
352
356
|
The fast stage requires one visible digit and uses `maxTokens: 512`. Reasoning models can use hidden tokens before they emit the digit.
|
|
353
357
|
|
|
354
|
-
Extra visible content fails parsing. Detailed review uses `maxTokens: 1200`. It can retry once after
|
|
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.
|
|
355
359
|
|
|
356
360
|
## Parsing the classifier result
|
|
357
361
|
|
|
@@ -359,19 +363,37 @@ The fast-stage parser requires `stopReason: "stop"`. It removes surrounding whit
|
|
|
359
363
|
|
|
360
364
|
Empty responses, additional content, malformed output, and non-stop responses block immediately. Observability logs preserve the untrimmed model response.
|
|
361
365
|
|
|
362
|
-
The detailed parser
|
|
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.
|
|
363
371
|
|
|
364
|
-
|
|
372
|
+
A response with `stopReason: "length"` can cause one retry. A missing or invalid decision tool call also can cause one retry.
|
|
365
373
|
|
|
366
|
-
A
|
|
374
|
+
A truncated or invalid response cannot authorize an action. Error and aborted responses block immediately.
|
|
367
375
|
|
|
368
376
|
If detailed parsing fails after its retry, pi-automode blocks the action with this reason:
|
|
369
377
|
|
|
370
378
|
```text
|
|
371
|
-
Classifier response
|
|
379
|
+
Classifier response did not contain a valid classifier decision tool call; auto mode fails closed.
|
|
372
380
|
```
|
|
373
381
|
|
|
374
|
-
If the model call throws
|
|
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.
|
|
375
397
|
|
|
376
398
|
## State, UI, and denial history
|
|
377
399
|
|
package/docs/configuration.md
CHANGED
|
@@ -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/diagnostics.md
CHANGED
|
@@ -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
|
|
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? }`,
|
|
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)
|