@czottmann/pi-automode 1.11.0 → 1.13.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 +39 -0
- package/README.md +82 -114
- package/docs/GLOSSARY.md +18 -14
- package/docs/adr/ADR-001-permission-precedence-and-trust-boundaries.md +46 -0
- package/docs/adr/ADR-002-global-config-in-extension-data-directory.md +60 -0
- package/docs/adr/INDEX.md +6 -0
- package/docs/automode-classifier-flow.md +201 -107
- package/docs/configuration.md +171 -0
- package/docs/defaults.md +55 -14
- package/docs/diagnostics.md +90 -0
- package/docs/observability-logging.md +61 -26
- package/examples/automode.local.json +5 -0
- package/extensions/auto-mode/bash.ts +692 -0
- package/extensions/auto-mode/classifier.ts +172 -18
- package/extensions/auto-mode/config.ts +307 -32
- package/extensions/auto-mode/constants.ts +7 -1
- package/extensions/auto-mode/extension.ts +393 -44
- package/extensions/auto-mode/hard-deny.ts +103 -148
- package/extensions/auto-mode/log.ts +60 -5
- package/extensions/auto-mode/paths.ts +124 -14
- package/extensions/auto-mode/permissions.ts +542 -30
- package/extensions/auto-mode/state.ts +1 -0
- package/extensions/auto-mode/types.ts +11 -0
- package/extensions/auto-mode/utils.ts +9 -1
- package/extensions/auto-mode.ts +1 -0
- package/package.json +12 -2
- package/skills/automode-diagnostics/SKILL.md +63 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
## [1.13.0] - 2026-08-25
|
|
6
|
+
|
|
7
|
+
## New features
|
|
8
|
+
|
|
9
|
+
- **Bash AST analysis** — Replace handwritten shell parsing with `unbash`. Permission and hard-deny checks now inspect command structure, nested commands, wrappers, redirects, and malformed input. Bash allow rules require complete structural coverage and fail closed when analysis is unsafe. (#26)
|
|
10
|
+
- **Extension-owned global config** — Store global settings at `~/.pi/agent/extensions/pi-automode/config.json`. Migrate the legacy file automatically, preserve a safe fallback after migration errors, and report conflicts through notifications and diagnostics. (#27)
|
|
11
|
+
|
|
12
|
+
## Bug fixes
|
|
13
|
+
|
|
14
|
+
- **Conservative permission rules** — Malformed deny and ask patterns block actions. Malformed patterns do not expand allow rules. Permission checks examine each Bash subcommand and normalize whitespace. Path checks resolve symlinks and normalize `file://` and Windows paths. (#22)
|
|
15
|
+
- **Recursive deletion hard-deny** — Detect uppercase flags and GNU abbreviations for recursive `rm`, including commands behind `command`, `exec`, and `env`. Parse the `--` delimiter and shell tilde expansion. Protect Linux and macOS system roots without blocking active-home subdirectories, `/opt`, or `/srv`. (#23)
|
|
16
|
+
- **Global Pi safety-control paths** — Hard-deny direct writes and edits to `~/.pi/agent/extensions/`, `~/.pi/agent/settings.json`, and `~/.pi/agent/settings/`. Resolve case variants and symlink targets before matching. (#24)
|
|
17
|
+
- **Case-insensitive protected paths** — Match protected paths without case distinctions and normalize Unicode spellings. This closes path bypasses on case-insensitive filesystems. (#25)
|
|
18
|
+
|
|
19
|
+
## [1.12.0] - 2026-08-23
|
|
20
|
+
|
|
21
|
+
## New features
|
|
22
|
+
|
|
23
|
+
- **Deterministic permission allows** — Add user-owned `permissions.allow` patterns that skip classifier review after deterministic checks pass. Accepted ask rules still require classifier review. Thank you, @sergeykonkin! (#14)
|
|
24
|
+
- **Configurable classifier request timeout** — new `autoMode.classifierTimeoutMs` setting. The default is 20000 ms. The timeout applies to each classifier request. The fast stage and the detailed stage each have their own budget. A request that exceeds the timeout is aborted. Auto mode fails closed and blocks the action.
|
|
25
|
+
- **Read-only agent diagnostics** — Add `automode_inspect` tool for status, configuration, defaults, and recent denial metadata. Thank you, @blalor! (#11)
|
|
26
|
+
|
|
27
|
+
## Bug fixes
|
|
28
|
+
|
|
29
|
+
- **Reject invalid config values** — Invalid boolean and log config values (e.g. `enabled: 0`, `log.enabled: 1`) are now rejected at merge time instead of being applied with diagnostics only. (#20)
|
|
30
|
+
- **Preserve defaults for malformed rule lists** — A malformed `hard_deny` entry like `[42]` no longer strips all built-in hard-deny rules. Malformed entries are rejected and defaults preserved conservatively. (#21)
|
|
31
|
+
- **Runtime classifier providers** — Dispatch classifier calls through Pi's runtime model registry so providers registered with `pi.registerProvider()` work immediately. Preserve normalized reasoning and header-only authentication on the temporary simple-completion bridge. (#15)
|
|
32
|
+
- **Bounded wildcard matching** — Replace regex-based permission and denied-path globs with a linear-time matcher. Reject oversized patterns and fail closed for oversized runtime inputs. (#19)
|
|
33
|
+
- **Complete classifier action input** — Send the exact current tool input to both classifier stages in a dedicated message. Block the action if it cannot fit without truncation. (#17)
|
|
34
|
+
- **Path policy normalization** — Use Pi-compatible resolution for file-tool paths, including file URLs, `@` and tilde aliases, and read fallback names. Enforce denied paths across omitted and recursive search scopes and both sides of symlink aliases. (#18)
|
|
35
|
+
- **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)
|
|
36
|
+
- **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)
|
|
37
|
+
|
|
38
|
+
[1.13.0]: https://github.com/czottmann/pi-automode/compare/v1.12.0...v1.13.0
|
|
39
|
+
[1.12.0]: https://github.com/czottmann/pi-automode/compare/v1.11.0...v1.12.0
|
package/README.md
CHANGED
|
@@ -4,7 +4,9 @@ Claude Code-style auto mode for Pi.
|
|
|
4
4
|
|
|
5
5
|
This is a guardrail extension. It intercepts agent tool calls before execution and blocks actions that match permission deny rules, deterministic hard-deny checks, or the auto-mode classifier's block decision.
|
|
6
6
|
|
|
7
|
-
It is not a sandbox. Extensions run in the Pi process
|
|
7
|
+
It is not a sandbox. Extensions run in the Pi process. A malicious extension can do anything that your user account can do.
|
|
8
|
+
|
|
9
|
+
Pi-automode does not guard user `!` or `!!` shell commands. It guards only agent tool calls. Use it to reduce unsafe autonomous tool use. Do not use it as an OS security boundary.
|
|
8
10
|
|
|
9
11
|
## Install
|
|
10
12
|
|
|
@@ -37,134 +39,59 @@ pi -e ./extensions/auto-mode.ts
|
|
|
37
39
|
/automode defaults # print the built-in rule lists
|
|
38
40
|
/automode config # effective config, resolved log file path, + diagnostics
|
|
39
41
|
/automode denials # denial history for this session
|
|
40
|
-
/automode model # open classifier model selector and save to ~/.pi/agent/automode.json
|
|
41
|
-
/automode model provider/model-id # save classifier model to ~/.pi/agent/automode.json
|
|
42
|
+
/automode model # open classifier model selector and save to ~/.pi/agent/extensions/pi-automode/config.json
|
|
43
|
+
/automode model provider/model-id # save classifier model to ~/.pi/agent/extensions/pi-automode/config.json
|
|
42
44
|
```
|
|
43
45
|
|
|
44
46
|
`/auto-mode` is an alias.
|
|
45
47
|
|
|
46
|
-
##
|
|
47
|
-
|
|
48
|
-
When the Pi TUI is available, the extension renders a persistent status line:
|
|
49
|
-
|
|
50
|
-
```text
|
|
51
|
-
AM● a:12 d:2 ca:5 cd:1
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
- `AM` — auto-mode prefix; `●` when enabled, `○` when disabled (via config or `/automode off`).
|
|
55
|
-
- `a:` — actions allowed so far (checked minus denied).
|
|
56
|
-
- `d:` — actions denied so far, for any reason (permission rule, deterministic hard-deny, or classifier).
|
|
57
|
-
- `ca:` / `cd:` — classifier decisions split into allowed vs denied. These segments appear only after the classifier has run at least once; `d:` counts all denials, so `d:` is always `>= cd:`.
|
|
58
|
-
|
|
59
|
-
## Docs
|
|
60
|
-
|
|
61
|
-
- [Defaults and rule-list behavior](docs/defaults.md)
|
|
62
|
-
- [Auto-mode classifier flow](docs/automode-classifier-flow.md)
|
|
63
|
-
- [Observability logging](docs/observability-logging.md)
|
|
64
|
-
|
|
65
|
-
## Configuration
|
|
48
|
+
## Agent diagnostics
|
|
66
49
|
|
|
67
|
-
The
|
|
50
|
+
The package registers one model-callable, read-only tool:
|
|
68
51
|
|
|
69
|
-
|
|
52
|
+
`automode_inspect` accepts one `action`:
|
|
70
53
|
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
- `
|
|
54
|
+
- `status`: active state and counters
|
|
55
|
+
- `config`: active effective configuration, log path, and diagnostics
|
|
56
|
+
- `defaults`: built-in rule lists
|
|
57
|
+
- `denials`: recent denial timestamps, kinds, and tool names
|
|
74
58
|
|
|
75
|
-
|
|
59
|
+
The tool reads the same in-memory configuration and state that the guardrail enforces. Permission and deterministic checks run before the bypass.
|
|
76
60
|
|
|
77
|
-
|
|
61
|
+
The bypass does not change automode counters, persisted state, or observability logs. The extension verifies the source of the tool before it applies the exemption. Thus, a name collision does not exempt a tool from another extension.
|
|
78
62
|
|
|
79
|
-
|
|
80
|
-
{
|
|
81
|
-
"autoMode": {
|
|
82
|
-
"enabled": false
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
This is project-local and should not be committed. Shared project `.pi/automode.json` cannot disable auto-mode.
|
|
88
|
-
|
|
89
|
-
Set a global default classifier model in `~/.pi/agent/automode.json`; override it per project in `.pi/automode.local.json`.
|
|
90
|
-
|
|
91
|
-
`classifierReasoningLevel` optionally requests `low`, `medium`, `high`, `xhigh`, or `max` reasoning for both classifier stages. If the key is absent, pi-automode sends no reasoning preference and leaves the choice to the server. Pi AI clamps unsupported values to the nearest level supported by the selected model; a non-reasoning model resolves to `off`. `low` matches Codex Auto Review's reasoning effort and the practical default when an explicit value is needed. Higher levels can consume the existing 512/1200-token stage limits before producing visible output, which causes the classifier to fail closed. Raise `fastClassifierMaxTokens` (default 512, integer ≥ 16) if you run a reasoning model whose fast-stage budget is truncated before it emits the required `0`/`1` digit.
|
|
92
|
-
|
|
93
|
-
`allowInsideWorkingDirectory` (default `false`) adds a deterministic silent-allow tier for the file tools (`read`, `write`, `edit`, `grep`, `find`, `ls`): when `true`, a call whose resolved path is inside the working directory is allowed without any classifier call, and file access outside the working directory is routed to the classifier (including reads, which would otherwise take the read-only fast path). This matches the Codex/Claude Code "inside the sandbox = silent, outside = review" model. The tier takes precedence over `classifyReadOnlyTools`: with both enabled, in-tree file access is still allowed without a classifier call, and out-of-tree file access is classified. `classifyReadOnlyTools: true` only routes in-tree reads to the classifier when `allowInsideWorkingDirectory` is `false`. Writes and edits to protected in-tree paths (`.git/hooks`, `.pi` controls, shell profiles, config files) are exempt from the silent-allow tier and still go to the classifier.
|
|
94
|
-
|
|
95
|
-
`deniedPaths` (default `[]`) is a list of path glob patterns that are hard-denied before the classifier and before the inside-working-directory tier — the file-tool equivalent of a secret/system deny list. Patterns support `~`, `$HOME`, and `${HOME}` expansion and `*` (which matches any characters, including `/`, so `**/id_rsa` matches a private key at any depth). Matching checks both the path as typed and its symlink-resolved form, so a `~/.ssh/*` rule still matches when `~/.ssh` is a symlink. A matching path blocks the call unconditionally (no classifier, no override). The deny list applies to file tools only; `bash` path access is governed by the classifier. Both keys follow the normal scalar/array precedence.
|
|
96
|
-
|
|
97
|
-
The setting follows the normal scalar precedence: global, then project-local, then `PI_AUTOMODE_SETTINGS_JSON`. Shared project `.pi/automode.json` cannot set it. Omitting the key at a higher-precedence scope does not clear a lower-precedence value.
|
|
98
|
-
|
|
99
|
-
Example:
|
|
100
|
-
|
|
101
|
-
```json
|
|
102
|
-
{
|
|
103
|
-
"autoMode": {
|
|
104
|
-
"classifierModel": "provider/model-id",
|
|
105
|
-
"classifierReasoningLevel": "low",
|
|
106
|
-
"classifyReadOnlyTools": false,
|
|
107
|
-
"fastClassifierMaxTokens": 512,
|
|
108
|
-
"allowInsideWorkingDirectory": false,
|
|
109
|
-
"deniedPaths": [],
|
|
110
|
-
"maxUserTranscriptTokens": 4000,
|
|
111
|
-
"maxToolTranscriptTokens": 4000,
|
|
112
|
-
"environment": [
|
|
113
|
-
"$defaults",
|
|
114
|
-
"Source control: github.example.com/acme-corp and all repos under it",
|
|
115
|
-
"Trusted internal domains: *.corp.example.com, git.example.com",
|
|
116
|
-
"Trusted cloud buckets: s3://acme-dev-artifacts, gs://acme-ci-cache",
|
|
117
|
-
"Key internal services: staging deploy API at deploy.corp.example.com"
|
|
118
|
-
],
|
|
119
|
-
"allow": ["$defaults"],
|
|
120
|
-
"protectedPaths": ["$defaults"],
|
|
121
|
-
"soft_deny": ["$defaults"],
|
|
122
|
-
"hard_deny": [
|
|
123
|
-
"$defaults",
|
|
124
|
-
"Never send repository contents to third-party code-review APIs"
|
|
125
|
-
]
|
|
126
|
-
},
|
|
127
|
-
"permissions": {
|
|
128
|
-
"deny": ["bash(rm -rf *)"],
|
|
129
|
-
"ask": ["bash(git push *)"]
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
```
|
|
63
|
+
The tool cannot enable or disable auto mode. It cannot reload configuration, reset state, select a model, or change configuration.
|
|
133
64
|
|
|
134
|
-
|
|
65
|
+
Pi sends tool output to the current model. The `status` and `denials` views omit denial reasons and action payloads.
|
|
135
66
|
|
|
136
|
-
|
|
67
|
+
If a diagnosis requires a reason, inspect a known-safe entry in the local observability log. The `config` view includes effective rule text. It removes raw JSON parser details from diagnostics.
|
|
137
68
|
|
|
138
|
-
|
|
69
|
+
Do not put credentials or other secrets in automode rules.
|
|
139
70
|
|
|
140
|
-
|
|
71
|
+
The bundled `automode-diagnostics` skill uses this tool to diagnose unexpected decisions without asking the user to copy output from slash commands. Configuration edits and automode state changes remain user-controlled. See [Agent diagnostics](docs/diagnostics.md) for the inspection contract, privacy limits, and diagnosis workflow.
|
|
141
72
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
### Observability logging
|
|
73
|
+
## Status line
|
|
145
74
|
|
|
146
|
-
|
|
75
|
+
When the Pi TUI is available, the extension renders a persistent status line:
|
|
147
76
|
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
"autoMode": {
|
|
151
|
-
"log": {
|
|
152
|
-
"enabled": true,
|
|
153
|
-
"classifierIo": false
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
77
|
+
```text
|
|
78
|
+
AM● a:12 d:2 ca:5 cd:1
|
|
157
79
|
```
|
|
158
80
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
81
|
+
- `AM` — auto-mode prefix. `●` means enabled. `○` means disabled through configuration or `/automode off`.
|
|
82
|
+
- `a:` — actions allowed so far (checked minus denied).
|
|
83
|
+
- `d:` — actions denied so far, for any reason (permission rule, deterministic hard-deny, or classifier).
|
|
84
|
+
- `ca:` / `cd:` — classifier decisions split into allowed and denied. These segments appear after the first classifier call. `d:` counts all denials, so `d:` is always `>= cd:`.
|
|
162
85
|
|
|
163
|
-
|
|
86
|
+
## Docs
|
|
164
87
|
|
|
165
|
-
|
|
88
|
+
- [Configuration](docs/configuration.md)
|
|
89
|
+
- [Defaults and rule-list behavior](docs/defaults.md)
|
|
90
|
+
- [Auto-mode classifier flow](docs/automode-classifier-flow.md)
|
|
91
|
+
- [Observability logging](docs/observability-logging.md)
|
|
92
|
+
- [Architecture decisions](docs/adr/INDEX.md)
|
|
166
93
|
|
|
167
|
-
## What
|
|
94
|
+
## What runs before the classifier
|
|
168
95
|
|
|
169
96
|
The extension blocks these before any allow or classifier decision:
|
|
170
97
|
|
|
@@ -177,11 +104,27 @@ The extension blocks these before any allow or classifier decision:
|
|
|
177
104
|
- root, home, and system-path destructive deletes
|
|
178
105
|
- edits to `.pi/automode*`, `.pi` auto-mode files, and this extension's safety-control files
|
|
179
106
|
|
|
180
|
-
|
|
107
|
+
After these checks, pi-automode applies `permissions.allow`. Protected `write` and `edit` targets continue to the classifier.
|
|
108
|
+
|
|
109
|
+
Accepted ask rules also continue to the classifier. They cannot use an allow tier. Pi-automode then allows the read-only tools `read`, `grep`, `find`, and `ls`. Every remaining action goes to the classifier.
|
|
110
|
+
|
|
111
|
+
A `permissions.allow` rule intentionally skips classifier policy, including classifier `hard_deny` rules. Use narrow patterns and user-owned configuration. Deterministic hard-deny and path controls remain unconditional.
|
|
112
|
+
|
|
113
|
+
Set `classifyReadOnlyTools: true` (default `false`) to route read-only tools through the classifier. This configuration value increases model calls, latency, and session cost.
|
|
181
114
|
|
|
182
|
-
|
|
115
|
+
Pi-automode blocks a `deniedPaths` match before classifier review or an allow tier. Thus, file tools cannot send matching secret or system paths to the model.
|
|
183
116
|
|
|
184
|
-
|
|
117
|
+
The list does not govern `bash`. The classifier and deterministic hard-deny checks govern shell access to these paths.
|
|
118
|
+
|
|
119
|
+
The value `allowInsideWorkingDirectory: true` allows file access inside the working directory locally. Pi-automode sends all outside file access to the classifier, including reads.
|
|
120
|
+
|
|
121
|
+
Classification starts with a conservative one-token filter. If the filter requests review, pi-automode runs structured review.
|
|
122
|
+
|
|
123
|
+
Both stages receive the complete current tool input in a dedicated message. Transcript truncation cannot remove action content. If the exact input cannot fit in the classifier context, auto mode blocks the call.
|
|
124
|
+
|
|
125
|
+
Both stages use a classifier-specific session key. They request short cache retention from providers that support it. A missing model, provider failure, or malformed response blocks the action.
|
|
126
|
+
|
|
127
|
+
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.
|
|
185
128
|
|
|
186
129
|
## Examples
|
|
187
130
|
|
|
@@ -199,19 +142,44 @@ npm test
|
|
|
199
142
|
npm pack --dry-run
|
|
200
143
|
```
|
|
201
144
|
|
|
202
|
-
The tests cover
|
|
145
|
+
The tests cover these safety-sensitive areas:
|
|
146
|
+
|
|
147
|
+
- scoped permission matching
|
|
148
|
+
- the `permissions.allow` tier and its precedence
|
|
149
|
+
- configuration-source precedence and diagnostics
|
|
150
|
+
- `$defaults` behavior
|
|
151
|
+
- deterministic hard-deny checks and Bash AST analysis
|
|
152
|
+
- classifier routing for `write` and `edit`
|
|
153
|
+
- symlink-aware safety-control checks
|
|
154
|
+
- token-budgeted transcript selection
|
|
155
|
+
- staged classifier parsing and cache behavior
|
|
156
|
+
- hook-level allow and block behavior
|
|
203
157
|
|
|
204
158
|
## Publishing
|
|
205
159
|
|
|
206
|
-
GitHub Actions publishes the package to npm
|
|
160
|
+
When a maintainer publishes a GitHub Release, GitHub Actions publishes the package to npm. The release tag must match `package.json` exactly. The forms `v1.0.0` and `1.0.0` both match version `1.0.0`.
|
|
207
161
|
|
|
208
162
|
The workflow uses npm Trusted Publishing, so it does not need an npm token secret. Configure this package on npm with this repository and workflow file (`.github/workflows/publish.yml`). The workflow builds the package, runs `npm run check`, and publishes with npm provenance.
|
|
209
163
|
|
|
210
164
|
### Release tag must point at the version bump
|
|
211
165
|
|
|
212
|
-
The publish workflow checks
|
|
166
|
+
The publish workflow checks the commit that the release tag identifies. It compares the version in `package.json` with the tag name.
|
|
167
|
+
|
|
168
|
+
**The tag must identify a commit that contains the new version.**
|
|
169
|
+
|
|
170
|
+
1. Commit the version change with `chore: release X.Y.Z`.
|
|
171
|
+
2. Push `main`.
|
|
172
|
+
3. Create the GitHub release from that commit.
|
|
173
|
+
|
|
174
|
+
If the tag identifies an older version, the `Check release tag` step fails. It reports `Release tag (vX.Y.Z) does not match package.json version (x.y.z)`.
|
|
175
|
+
|
|
176
|
+
CAUTION: Do not move a correct release tag. Moving the tag changes a published reference.
|
|
177
|
+
|
|
178
|
+
1. If the tag identifies the wrong commit, force-move it to the version-change commit.
|
|
179
|
+
2. Force-push the corrected tag.
|
|
180
|
+
3. Run `gh workflow run publish.yml --ref vX.Y.Z`.
|
|
213
181
|
|
|
214
|
-
|
|
182
|
+
When GitHub creates the tag, the `release` event occurs. A rerun of a failed `release` workflow uses the original reference. It does not use the moved tag.
|
|
215
183
|
|
|
216
184
|
## Author
|
|
217
185
|
|
package/docs/GLOSSARY.md
CHANGED
|
@@ -1,35 +1,39 @@
|
|
|
1
1
|
# Glossary
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This glossary defines project terms for `@czottmann/pi-automode`. It does not define standard technical words. Each entry links to a longer explanation where applicable.
|
|
4
4
|
|
|
5
5
|
## Enforcement flow
|
|
6
6
|
|
|
7
|
-
The ordered pipeline that runs
|
|
7
|
+
The enforcement flow is the ordered pipeline that runs before each agent tool call. See [Auto-mode classifier flow](automode-classifier-flow.md).
|
|
8
8
|
|
|
9
|
-
**Auto mode** — Claude Code-style guardrail posture
|
|
9
|
+
**Auto mode** — A Claude Code-style guardrail posture. A pre-execution classifier allows routine, reversible actions and blocks risky actions. It replaces routine permission prompts.
|
|
10
10
|
|
|
11
|
-
**Deterministic hard-deny** — Local code checks that block high-risk actions before
|
|
11
|
+
**Deterministic hard-deny** — Local code checks that block high-risk actions before classifier review. A user or model cannot override them. They are independent of the classifier-level [hard_deny](#classifier-policy-and-rules) rules.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
**`permissions.allow` tier** — A user-owned list of scoped tool patterns. A match skips classifier review after deterministic checks pass. An accepted `permissions.ask` rule disables this tier for the current call. The tier cannot override a denial or cover protected `write` and `edit` targets. See [ADR-001](adr/ADR-001-permission-precedence-and-trust-boundaries.md).
|
|
14
14
|
|
|
15
|
-
**
|
|
15
|
+
**Read-only bypass** — The default allow tier for `read`, `grep`, `find`, and `ls`. Permission and deterministic checks run before this tier. `classifyReadOnlyTools` sends these calls to the classifier instead.
|
|
16
16
|
|
|
17
|
-
**
|
|
17
|
+
**Staged classifier** — A two-stage safety classifier. A conservative one-token filter controls access to an optional structured review. See [Fast stage](#enforcement-flow) and [Detailed stage](#enforcement-flow).
|
|
18
18
|
|
|
19
|
-
**
|
|
19
|
+
**Fast stage** — The first classifier stage. It returns `0` for a clearly allowed action or `1` for an action that can require review.
|
|
20
|
+
|
|
21
|
+
**Detailed stage** — The second classifier stage. It runs after a fast-stage review result and returns a structured allow or block decision.
|
|
20
22
|
|
|
21
23
|
## Classifier policy and rules
|
|
22
24
|
|
|
23
|
-
The classifier
|
|
25
|
+
The classifier policy defines denial tiers and rule-list syntax. See [Defaults and rule-list behavior](defaults.md).
|
|
26
|
+
|
|
27
|
+
**hard_deny** — Classifier rules that block unconditionally. They are independent of the code-level [deterministic hard-deny](#enforcement-flow) checks.
|
|
24
28
|
|
|
25
|
-
**
|
|
29
|
+
**soft_deny** — Classifier rules that normally block but support defined overrides. Unlike [hard_deny](#classifier-policy-and-rules), these rules are not unconditional.
|
|
26
30
|
|
|
27
|
-
**
|
|
31
|
+
**explicit_intent** — A classifier tier for direct user authorization. The latest user instruction specifically authorizes an action that matches a [soft_deny](#classifier-policy-and-rules) rule.
|
|
28
32
|
|
|
29
|
-
**
|
|
33
|
+
**allow exception** (`autoMode.allow`) — A prose rule that overrides a matching [soft_deny](#classifier-policy-and-rules) rule. It cannot override [hard_deny](#classifier-policy-and-rules). It is independent of the [`permissions.allow` tier](#enforcement-flow).
|
|
30
34
|
|
|
31
|
-
**`$defaults`** — A section-local marker in a rule list
|
|
35
|
+
**`$defaults`** — A section-local marker in a rule list. It expands to the built-in entries for that list.
|
|
32
36
|
|
|
33
37
|
## Status
|
|
34
38
|
|
|
35
|
-
**AM status line** — The persistent TUI footer
|
|
39
|
+
**AM status line** — The persistent TUI footer that starts with `AM`. It reports the auto-mode state and action counts.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# ADR-001: Permission precedence and configuration trust boundaries
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-08-23
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
Pi-automode supports `permissions.deny`, `permissions.ask`, and `permissions.allow` tool patterns. These rules affect whether a tool call reaches the classifier.
|
|
8
|
+
|
|
9
|
+
An allow rule reduces classifier coverage. A checked-in repository file must not reduce that coverage. An accepted ask rule must also keep its promise of classifier review.
|
|
10
|
+
|
|
11
|
+
The wildcard matcher limits its input size to keep matching time bounded. Deny and ask rules must fail closed for oversized inputs. The same behavior is unsafe for allow rules.
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
Pi-automode uses this permission policy:
|
|
16
|
+
|
|
17
|
+
1. A matching `permissions.deny` rule blocks the tool call.
|
|
18
|
+
2. A matching `permissions.ask` rule requires user confirmation.
|
|
19
|
+
3. If the user declines, pi-automode blocks the tool call.
|
|
20
|
+
4. If the user accepts, deterministic denial checks continue.
|
|
21
|
+
5. After these checks pass, the classifier reviews the accepted tool call.
|
|
22
|
+
6. An accepted ask rule disables all later deterministic allow tiers for that call.
|
|
23
|
+
7. If no ask rule matched, a `permissions.allow` rule can skip classifier review.
|
|
24
|
+
8. Deterministic hard-deny checks, path-deny checks, and protected-path controls take precedence over allow rules.
|
|
25
|
+
|
|
26
|
+
Pi-automode reads `permissions.allow` only from user-owned configuration sources:
|
|
27
|
+
|
|
28
|
+
- `~/.pi/agent/automode.json`
|
|
29
|
+
- trusted `.pi/automode.local.json`
|
|
30
|
+
- `PI_AUTOMODE_SETTINGS_JSON`
|
|
31
|
+
|
|
32
|
+
Shared `.pi/automode.json` can add deny and ask rules after project trust. It cannot add allow rules.
|
|
33
|
+
|
|
34
|
+
[ADR-002](ADR-002-global-config-in-extension-data-directory.md) replaces only the global configuration path. Permission precedence and trust boundaries stay unchanged.
|
|
35
|
+
|
|
36
|
+
For an oversized matcher input, deny and ask rules return a match. Allow rules return no match. This behavior keeps denial rules fail-closed without broadening allow rules.
|
|
37
|
+
|
|
38
|
+
## Consequences
|
|
39
|
+
|
|
40
|
+
A repository cannot use checked-in configuration to remove classifier review. Users can still add project-specific allow rules in the local configuration file.
|
|
41
|
+
|
|
42
|
+
An accepted ask rule always causes classifier review after deterministic checks pass. This behavior is stricter than a model where confirmation grants direct permission.
|
|
43
|
+
|
|
44
|
+
The matcher needs separate overflow behavior for denial and allow contexts. Tests must cover both behaviors.
|
|
45
|
+
|
|
46
|
+
The permission lists are not symmetric across configuration sources. The documentation and diagnostics must make this difference clear.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# ADR-002: Store the global config in the extension data directory
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-08-25
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
Pi-automode stores its global configuration at `~/.pi/agent/automode.json`. The extension stores application-owned logs below `~/.pi/agent/extensions/pi-automode/logs/`.
|
|
8
|
+
|
|
9
|
+
The separate global configuration path makes extension-owned data harder to identify and manage. The global configuration belongs beside the existing log directory.
|
|
10
|
+
|
|
11
|
+
An existing installation can contain the legacy file. Migration must not discard configuration data or create different read and write sources.
|
|
12
|
+
|
|
13
|
+
This decision refines the global user-owned configuration source from [ADR-001](ADR-001-permission-precedence-and-trust-boundaries.md). It does not change permission precedence or project trust boundaries.
|
|
14
|
+
|
|
15
|
+
Tracking issue: [#27](https://github.com/czottmann/pi-automode/issues/27)
|
|
16
|
+
|
|
17
|
+
## Decision
|
|
18
|
+
|
|
19
|
+
Pi-automode stores the global configuration at:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
~/.pi/agent/extensions/pi-automode/config.json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The existing log directory stays at:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
~/.pi/agent/extensions/pi-automode/logs/
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Project configuration files keep their current paths and semantics:
|
|
32
|
+
|
|
33
|
+
- `.pi/automode.local.json`
|
|
34
|
+
- `.pi/automode.json`
|
|
35
|
+
|
|
36
|
+
At startup, pi-automode selects one active global configuration path for the session:
|
|
37
|
+
|
|
38
|
+
1. If only the new file is present, pi-automode uses the new file.
|
|
39
|
+
2. If only the legacy file is present, pi-automode moves it to the new path before loading configuration.
|
|
40
|
+
3. If the two files are present, pi-automode uses the new file and does not change the legacy file.
|
|
41
|
+
4. If migration fails, pi-automode uses the legacy file for reads and writes during that session.
|
|
42
|
+
5. If neither file is present, pi-automode uses the new path for the next write.
|
|
43
|
+
|
|
44
|
+
Pi-automode attempts migration once per session. A later session retries a failed migration.
|
|
45
|
+
|
|
46
|
+
A successful migration produces one UI notification. A path conflict produces a warning during every startup. A migration error produces one warning per session.
|
|
47
|
+
|
|
48
|
+
Conflict and migration error details also appear in configuration diagnostics. This behavior makes the details available in headless sessions.
|
|
49
|
+
|
|
50
|
+
## Consequences
|
|
51
|
+
|
|
52
|
+
All extension-owned global data has one identifiable root directory. Configuration backups and extension cleanup become easier to understand.
|
|
53
|
+
|
|
54
|
+
The new configuration inherits the deterministic protection for `~/.pi/agent/extensions/`. Direct agent file tools cannot modify the configuration.
|
|
55
|
+
|
|
56
|
+
The extension must pass the selected active path to configuration reads and writes. This requirement prevents split-brain configuration during migration errors.
|
|
57
|
+
|
|
58
|
+
The migration is one-way. A downgraded pi-automode version does not automatically find the new configuration path.
|
|
59
|
+
|
|
60
|
+
Startup gains a small filesystem operation and migration state. Tests must cover migration, conflicts, errors, notifications, diagnostics, and unchanged project precedence.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Architecture Decision Records
|
|
2
|
+
|
|
3
|
+
| # | Decision | Date |
|
|
4
|
+
|---|----------|------|
|
|
5
|
+
| [ADR-001](ADR-001-permission-precedence-and-trust-boundaries.md) | Permission precedence and configuration trust boundaries | 2026-08-23 |
|
|
6
|
+
| [ADR-002](ADR-002-global-config-in-extension-data-directory.md) | Store the global config in the extension data directory | 2026-08-25 |
|