@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 +33 -1
- package/README.md +11 -6
- package/docs/GLOSSARY.md +2 -2
- package/docs/automode-classifier-flow.md +47 -24
- package/docs/configuration.md +35 -0
- package/docs/defaults.md +3 -2
- 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 +261 -165
- package/extensions/auto-mode/config.ts +16 -3
- package/extensions/auto-mode/constants.ts +9 -6
- package/extensions/auto-mode/extension.ts +48 -22
- 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,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.
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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[
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
271
|
-
{"decision":"block","tier":"soft_deny","reason":"brief concrete reason"}
|
|
272
|
-
```
|
|
269
|
+
The tool has these arguments:
|
|
273
270
|
|
|
274
|
-
|
|
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
|
-
|
|
277
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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/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
|
|
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
|
|
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
|
|
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)
|