@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.
@@ -0,0 +1,171 @@
1
+ # Configuration
2
+
3
+ The extension follows the documented Claude Code configuration model where Pi supports it.
4
+
5
+ It reads `autoMode` only from Pi-owned configuration sources:
6
+
7
+ - `~/.pi/agent/extensions/pi-automode/config.json`
8
+ - `.pi/automode.local.json` for trusted projects
9
+ - `PI_AUTOMODE_SETTINGS_JSON`
10
+
11
+ At startup, pi-automode moves a legacy `~/.pi/agent/automode.json` file to the new global path. If both files exist, it uses the new file and reports the conflict. If migration fails, it uses the legacy file for that session and reports the error.
12
+
13
+ It does not read project configuration until Pi trusts the project. For an untrusted project, it ignores `.pi/automode.local.json` and `.pi/automode.json`. `/automode config` reports each ignored file that exists.
14
+
15
+ Shared project `.pi/automode.json` cannot weaken auto mode. For a trusted project, it can add `permissions.deny` and `permissions.ask` rules.
16
+
17
+ The shared file cannot set `autoMode` or add `permissions.allow` rules. If the file contains `permissions.allow`, `/automode config` reports a diagnostic.
18
+
19
+ To disable pi-automode for the current project, create or edit `.pi/automode.local.json`:
20
+
21
+ ```json
22
+ {
23
+ "autoMode": {
24
+ "enabled": false
25
+ }
26
+ }
27
+ ```
28
+
29
+ This file is project-local. Pi reads it only after project trust. Do not commit this file. Shared project `.pi/automode.json` cannot disable auto mode.
30
+
31
+ Set a global default classifier model in `~/.pi/agent/extensions/pi-automode/config.json`. For a trusted project, override it in `.pi/automode.local.json`.
32
+
33
+ `classifierReasoningLevel` requests `low`, `medium`, `high`, `xhigh`, or `max` reasoning for both classifier stages. If the key is absent, pi-automode sends no reasoning preference. The server then selects the level.
34
+
35
+ Pi AI clamps an unsupported value to the nearest level that the selected model supports. A model without reasoning support resolves to `off`. `low` matches the reasoning effort of Codex Auto Review.
36
+
37
+ Higher levels can use all 512 or 1200 stage tokens before they produce visible output. In this case, the classifier fails closed. If truncation occurs before the required `0` or `1` digit, increase `fastClassifierMaxTokens`. The default is 512, and the minimum is 16.
38
+
39
+ `classifierTimeoutMs` limits each classifier request in milliseconds. The default is 20000, and the minimum is 1000. The fast and detailed stages have separate budgets.
40
+
41
+ If a request stalls or exceeds its budget, pi-automode aborts it. Then auto mode fails closed and blocks the action.
42
+
43
+ `allowInsideWorkingDirectory` adds a deterministic allow tier for the file tools. The default value is `false`. The file tools are `read`, `write`, `edit`, `grep`, `find`, and `ls`.
44
+
45
+ The value `allowInsideWorkingDirectory: true` allows access to paths inside the working directory without classifier review. Pi-automode sends access to outside paths to the classifier. This rule also applies to read calls.
46
+
47
+ This tier takes precedence over `classifyReadOnlyTools`. If both configuration fields are enabled, pi-automode still allows in-tree file access locally. `classifyReadOnlyTools: true` does not change this behavior.
48
+
49
+ Protected in-tree targets do not use this allow tier. Writes and edits to `.git/hooks`, `.pi` controls, shell profiles, and configuration files still reach the classifier.
50
+
51
+ `deniedPaths` is a list of path glob patterns. The default list is `[]`. A matching pattern blocks a file-tool call before classifier review or an allow tier.
52
+
53
+ Patterns support `~`, `$HOME`, and `${HOME}` expansion. The `*` wildcard matches all characters, including `/`. Thus, `**/id_rsa` matches a private key at any depth.
54
+
55
+ Each pattern can contain at most 4,096 UTF-16 code units. Pi-automode matches the typed path and its symlink-resolved form. It also resolves the fixed path prefix of each pattern. Thus, a symlink alias cannot bypass a denied target.
56
+
57
+ If a recursive `grep` or `find` scope can contain a denied path, pi-automode blocks the call. A broad pattern such as `*.env` blocks these tools for every directory scope.
58
+
59
+ A matching path blocks the call without classifier review or an override. The list applies only to file tools. The classifier governs `bash` path access. Both keys use the normal scalar and array precedence.
60
+
61
+ `allowInsideWorkingDirectory` uses scalar precedence: global, then project-local, then `PI_AUTOMODE_SETTINGS_JSON`. `deniedPaths` entries accumulate across these configuration sources.
62
+
63
+ Shared project `.pi/automode.json` cannot set either field. Omitting either field at a higher-precedence source does not clear a lower-source value.
64
+
65
+ Example:
66
+
67
+ ```json
68
+ {
69
+ "autoMode": {
70
+ "classifierModel": "provider/model-id",
71
+ "classifierReasoningLevel": "low",
72
+ "classifyReadOnlyTools": false,
73
+ "fastClassifierMaxTokens": 512,
74
+ "classifierTimeoutMs": 20000,
75
+ "allowInsideWorkingDirectory": false,
76
+ "deniedPaths": [],
77
+ "maxUserTranscriptTokens": 4000,
78
+ "maxToolTranscriptTokens": 4000,
79
+ "environment": [
80
+ "$defaults",
81
+ "Source control: github.example.com/acme-corp and all repos under it",
82
+ "Trusted internal domains: *.corp.example.com, git.example.com",
83
+ "Trusted cloud buckets: s3://acme-dev-artifacts, gs://acme-ci-cache",
84
+ "Key internal services: staging deploy API at deploy.corp.example.com"
85
+ ],
86
+ "allow": ["$defaults"],
87
+ "protectedPaths": ["$defaults"],
88
+ "soft_deny": ["$defaults"],
89
+ "hard_deny": [
90
+ "$defaults",
91
+ "Never send repository contents to third-party code-review APIs"
92
+ ]
93
+ },
94
+ "permissions": {
95
+ "deny": ["bash(rm -rf *)"],
96
+ "ask": ["bash(git push *)"],
97
+ "allow": ["bash(git status*)", "example-extension-tool"]
98
+ }
99
+ }
100
+ ```
101
+
102
+ `maxUserTranscriptTokens` and `maxToolTranscriptTokens` are approximate budgets for each category. Both default to 4000 and accept integers of at least 32.
103
+
104
+ Pi-automode does not support the former `maxTranscriptLines` field. Evidence selection now uses token budgets instead of line counts.
105
+
106
+ ## Ask-user tools and explicit authorization
107
+
108
+ Classifier evidence includes normal user messages and assistant tool-call inputs. It excludes assistant prose and all tool results. This exclusion includes answers from ask-user tools such as `@vanillagreen/pi-questions`.
109
+
110
+ Selecting "Yes" in that tool helps the agent select its next action. Pi-automode does not treat the result as authorization to override a soft deny.
111
+
112
+ Send the authorization as a normal chat message. Then the agent can retry the action. Tool results remain excluded because they can contain untrusted or prompt-injected content.
113
+
114
+ ## `$defaults`
115
+
116
+ See [Defaults and rule-list behavior](defaults.md) for built-in `environment`, `allow`, `protectedPaths`, `soft_deny`, and `hard_deny` entries. The document also explains replacement behavior after omission of `$defaults`.
117
+
118
+ ## Observability logging
119
+
120
+ Auto mode can write a JSONL observability log for decisions and classifier usage. Persisted sessions use a sidecar next to the Pi session file. In-memory sessions use a global application directory. Logging is off by default.
121
+
122
+ ```json
123
+ {
124
+ "autoMode": {
125
+ "log": {
126
+ "enabled": true,
127
+ "classifierIo": false
128
+ }
129
+ }
130
+ }
131
+ ```
132
+
133
+ With logging enabled, persisted-session sidecars also contain ccusage-compatible entries for every classifier response. When `classifierIo` is off, `ccusage pi` still reports a separate `-pi-automode` session. In-memory logs use the same entry shape but live outside the normal Pi session tree.
134
+
135
+ See [Observability logging](observability-logging.md) for the log file location, entry schema, and the `classifierIo` privacy tradeoff. Run `/automode config` to see the resolved log file path.
136
+
137
+ ## Permission patterns
138
+
139
+ Permission patterns use Pi tool names. Examples include `bash(...)`, `write(...)`, `edit(...)`, and `read(...)`. The parser accepts capitalized names such as `Bash(...)`. The documented form is lowercase because Pi tool names are lowercase.
140
+
141
+ `permissions.allow` is a deterministic allow tier. The default list is `[]`. A matching rule skips classifier review.
142
+
143
+ Use this tier for a narrow command such as `bash(git status*)`. You can also use it for a side-effect-free extension or MCP tool.
144
+
145
+ The matcher understands primary arguments for `bash`, the file tools, and `grep`. For file tools, it uses the resolved `input.path`. For `grep`, it uses `input.pattern`.
146
+
147
+ For `bash`, pi-automode parses `input.command` with `unbash`. Deny and ask rules inspect each executable command in the Bash syntax tree. This includes pipelines, logical chains, compound commands, substitutions, and literal scripts passed to `bash -c`, `sh -c`, or `eval`. The analysis also follows these literal shell scripts through transparent `command`, `exec`, and `env` dispatch.
148
+
149
+ The matcher normalizes whitespace between Bash tokens. It preserves whitespace and quoting inside each token. Thus, `bash(git push*)` matches `git push origin main`. Quoted operators do not create extra commands.
150
+
151
+ A Bash allow decision requires coverage for each executable command. A multi-command pattern must match the same AST structure and operators. This structure check also applies to one command inside a group, wrapper, control structure, or background statement. Separate single-command patterns only cover top-level foreground chains and plain pipelines. Other supported structure requires one matching structural pattern. A multi-command pattern must match the same number of commands in the same order. Each pattern command must match its corresponding input command. One wildcard cannot hide an additional command or a different operator.
152
+
153
+ A redirect requires explicit coverage in the allow pattern. The redirect operator, file descriptor, variable name, and target pattern must match. Here-documents and dynamic redirect targets continue to the classifier. Parser errors, dynamic command names, and dynamic wrapper scripts cannot use `permissions.allow`.
154
+
155
+ Control nodes with unrepresented semantic values cannot use `permissions.allow`. This includes loops, functions, coprocesses, case statements, test commands, and arithmetic commands. These scripts continue to the classifier.
156
+
157
+ Pi-automode does not execute shell expansions. It cannot resolve aliases, variables, generated scripts, or dynamic `eval` input. These calls continue to the classifier unless a deterministic rule blocks them.
158
+
159
+ For other tools, the matcher uses the serialized input object. Use a bare tool name for an MCP or extension tool. For example, `example-extension-tool` matches every call to that tool.
160
+
161
+ The providing extension or MCP server defines the Pi tool name. Pi-automode does not need a predefined list.
162
+
163
+ A match skips only the classifier call. It cannot skip `permissions.deny`, deterministic hard-deny checks, `deniedPaths`, or protected-path controls. An accepted `permissions.ask` rule also takes precedence. After confirmation, the call continues through deterministic checks and then reaches the classifier. It cannot use `permissions.allow`, the inside-working-directory tier, or the read-only fast path.
164
+
165
+ Pi-automode reads `permissions.allow` only from global configuration, trusted `.pi/automode.local.json`, and `PI_AUTOMODE_SETTINGS_JSON`. Shared `.pi/automode.json` cannot add allow rules.
166
+
167
+ A pattern can contain at most 4,096 UTF-16 code units. Bash analysis accepts at most 1,048,576 UTF-16 code units. A longer Bash input is blocked before parsing.
168
+
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
+
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.
package/docs/defaults.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## `$defaults`
4
4
 
5
- `$defaults` expands to this plugin's built-in entries for the section where it appears. It is section-local: `$defaults` in `allow` means the built-in allow rules, not the built-in hard-deny rules.
5
+ `$defaults` expands to the built-in entries for its section. It is section-local. In `allow`, it means the built-in allow rules, not the built-in hard-deny rules.
6
6
 
7
7
  ### `environment`
8
8
 
@@ -14,7 +14,7 @@
14
14
  - trusted cloud buckets: none configured
15
15
  - key internal services: none configured
16
16
 
17
- Add your own entries when the classifier should know about trusted company infrastructure:
17
+ If the classifier needs information about trusted company infrastructure, add entries such as these:
18
18
 
19
19
  ```json
20
20
  {
@@ -46,17 +46,40 @@ These are exceptions to `soft_deny`, not to `hard_deny`.
46
46
 
47
47
  ### `protectedPaths`
48
48
 
49
- `$defaults` expands to safety-sensitive paths. In the default configuration every `write` and `edit` call goes to the classifier, so `protectedPaths` does not change whether a model call occurs; it remains part of the resolved configuration for compatibility and inspection. With `allowInsideWorkingDirectory: true`, non-protected in-tree file access takes the deterministic allow tier, but writes and edits to these protected paths still reach the classifier; `allow` rules cannot override a classifier hard-deny decision.
49
+ `$defaults` expands to safety-sensitive paths. In the default configuration, every `write` and `edit` call goes to the classifier. Thus, `protectedPaths` does not change classifier routing by default. Pi-automode retains the list for compatibility and inspection.
50
+
51
+ The value `allowInsideWorkingDirectory: true` sends non-protected in-tree file access to the deterministic allow tier. Writes and edits to protected paths still reach the classifier. Classifier `allow` rules cannot override a classifier hard-deny decision.
50
52
 
51
53
  Protected directories: `.git`, `.config/git`, `.vscode`, `.idea`, `.husky`, `.cargo`, `.devcontainer`, `.yarn`, `.mvn`, `.pi`.
52
54
 
53
- Protected files: `.gitconfig`, `.gitmodules`, `.gitignore`, `.gitattributes`, shell profiles (`.bashrc`, `.zshrc`, `.profile`, etc.), `.envrc`, package manager configs (`.npmrc`, `.yarnrc`, `.yarnrc.yml`, `.pnp.cjs`, `bunfig.toml`, etc.), Bazel configs (`.bazelrc`, `.bazelversion`, `.bazeliskrc`), hook configs (`.pre-commit-config.yaml`, `lefthook.yml`), Gradle/Maven wrappers, `.devcontainer.json`, `.ripgreprc`, `pyrightconfig.json`, `.mcp.json`.
55
+ Protected files include:
56
+
57
+ - Git files: `.gitconfig`, `.gitmodules`, `.gitignore`, and `.gitattributes`
58
+ - Bash files: `.bashrc`, `.bash_profile`, `.bash_login`, `.bash_aliases`, and `.bash_logout`
59
+ - Zsh files: `.zshrc`, `.zprofile`, `.zshenv`, `.zlogin`, and `.zlogout`
60
+ - other shell files: `.profile` and `.envrc`
61
+ - package-manager files: `.npmrc`, `.yarnrc`, `.yarnrc.yml`, `.pnp.cjs`, `.pnp.loader.mjs`, and `.pnpmfile.cjs`
62
+ - Bun files: `bunfig.toml` and `.bunfig.toml`
63
+ - Bazel files: `.bazelrc`, `.bazelversion`, and `.bazeliskrc`
64
+ - hook files: `.pre-commit-config.yaml`, `lefthook.yml`, `lefthook.yaml`, `.lefthook.yml`, and `.lefthook.yaml`
65
+ - wrapper files: `gradle-wrapper.properties` and `maven-wrapper.properties`
66
+ - other control files: `.devcontainer.json`, `.ripgreprc`, `pyrightconfig.json`, and `.mcp.json`
67
+
68
+ Read-only tools remain locally allowed after permission and deterministic checks. These tools are `read`, `grep`, `find`, and `ls`.
69
+
70
+ In the default configuration, all writes and edits require classification. The value `allowInsideWorkingDirectory: true` allows non-protected in-tree access locally.
54
71
 
55
- Read-only tools (`read`, `grep`, `find`, `ls`) remain locally allowed after permission and deterministic checks. In the default configuration writes and edits always require classification, regardless of their target; with `allowInsideWorkingDirectory` enabled, only protected targets still require it.
72
+ Protected in-tree writes and edits still require classification. All out-of-tree file access also requires classification.
56
73
 
57
74
  ### `deniedPaths`
58
75
 
59
- `deniedPaths` is a separate opt-in list (default `[]`, no built-in entries, so `$defaults` is a no-op; it is accepted for consistency with the other rule lists) of path glob patterns that are hard-denied for the file tools (`read`, `write`, `edit`, `grep`, `find`, `ls`) before any classifier or fast-path decision. Use it for secrets and system paths that must never reach the model through the file tools; `bash` access to such paths remains classifier-governed. Patterns support `~`/`$HOME`/`${HOME}` expansion and `*` (matches any characters, including `/`). Matching checks both the typed path and its symlink-resolved form. `deniedPaths` only restricts; it never grants access.
76
+ `deniedPaths` is an optional list of path glob patterns. The default list is empty, and it has no built-in entries. Thus, `$defaults` has no effect in this list.
77
+
78
+ A matching pattern blocks a file-tool call before classifier review or an allow tier. The file tools are `read`, `write`, `edit`, `grep`, `find`, and `ls`.
79
+
80
+ Use this list for secrets and system paths that file tools must not send to the model. The classifier continues to govern `bash` access.
81
+
82
+ Patterns support `~`, `$HOME`, and `${HOME}` expansion. The `*` wildcard matches all characters, including `/`. Pi-automode matches the typed path and its symlink-resolved form. `deniedPaths` can only restrict access.
60
83
 
61
84
  ### `soft_deny`
62
85
 
@@ -68,7 +91,7 @@ Read-only tools (`read`, `grep`, `find`, `ls`) remain locally allowed after perm
68
91
  - deleting, truncating, or overwriting pre-existing local files unless the user named the specific target
69
92
  - mass modification/deletion in cloud storage, ticketing systems, collaboration tools, or external APIs
70
93
  - writing through remote shells such as `ssh`, `kubectl exec`, or `docker exec` on shared or production hosts
71
- - changing live/shared infrastructure without a preview, plan, dry-run, or review when one normally exists
94
+ - changing shared or live infrastructure without its normal preview, plan, dry run, or review
72
95
  - killing other users' processes, cancelling shared jobs, or disrupting shared infrastructure
73
96
  - exposing local files, sockets, credentials, or internal services through a network service
74
97
  - outbound messages, purchases, bookings, subscriptions, or other real-world transactions
@@ -84,7 +107,7 @@ Soft blocks can be overridden by a matching `allow` exception or by direct, spec
84
107
  - granting admin, owner, IAM, RBAC, repository, or production access
85
108
  - exfiltrating repository contents, secrets, credentials, private keys, tokens, or internal data to untrusted places
86
109
  - scanning for credentials, tokens, private keys, or secrets beyond what the task requires
87
- - tampering with logs, audit trails, security monitoring, permission rules, auto-mode config, or safety-control files
110
+ - tampering with logs, audit trails, security monitoring, permission rules, auto-mode configuration, or safety-control files
88
111
  - creating services, endpoints, workflows, or autonomous agents that execute arbitrary code without meaningful approval
89
112
  - posting or updating public/external content that is fabricated, misleading, impersonating a user, or claiming approval/action that did not happen
90
113
 
@@ -97,13 +120,25 @@ Classifier evidence has separate approximate-token budgets for user messages and
97
120
  - `maxUserTranscriptTokens`: 4000
98
121
  - `maxToolTranscriptTokens`: 4000
99
122
 
100
- The selector keeps the first and latest user messages as intent anchors, then fills remaining space from the newest eligible entries. Individual entries are capped, and omitted or truncated evidence is marked in the classifier transcript. Assistant prose and tool results are excluded.
123
+ The selector keeps the first and latest user messages as intent anchors. It fills the remaining space from the newest eligible entries.
124
+
125
+ The selector limits individual entries. It marks omitted or truncated evidence in the classifier transcript. It excludes assistant prose and tool results.
101
126
 
102
- These are approximate limits based on character counts, not provider-tokenizer guarantees. Override either value with an integer of at least 32 in a Pi-owned `autoMode` config. The former `maxTranscriptLines` setting is no longer supported.
127
+ These limits use approximate character counts. They do not guarantee the same result as a provider tokenizer.
128
+
129
+ To change a limit, set an integer of at least 32 in a Pi-owned `autoMode` configuration source. Pi-automode does not support the former `maxTranscriptLines` field.
130
+
131
+ ### Classifier request timeout
132
+
133
+ `classifierTimeoutMs` limits each classifier request in milliseconds. The fast and detailed stages have separate budgets. The default is 20000.
134
+
135
+ If a request exceeds its budget, pi-automode aborts the attempt. Then auto mode fails closed and blocks the action.
136
+
137
+ To change the timeout, set an integer of at least 1000 in a Pi-owned `autoMode` configuration source.
103
138
 
104
139
  ### Replacement behavior
105
140
 
106
- Use `$defaults` when you want to keep the built-ins and add your own entries:
141
+ If you want to keep the built-ins and add entries, use `$defaults`:
107
142
 
108
143
  ```json
109
144
  {
@@ -116,7 +151,7 @@ Use `$defaults` when you want to keep the built-ins and add your own entries:
116
151
  }
117
152
  ```
118
153
 
119
- That means: use all built-in `allow` entries, plus the staging rule.
154
+ This configuration uses all built-in `allow` entries and the staging rule.
120
155
 
121
156
  If you omit `$defaults`, you replace the built-ins for that section:
122
157
 
@@ -130,7 +165,13 @@ If you omit `$defaults`, you replace the built-ins for that section:
130
165
  }
131
166
  ```
132
167
 
133
- That means: use only that one `allow` entry. The built-in `allow` entries are not used. Replacing `allow` does not replace `soft_deny`, `hard_deny`, `protectedPaths`, or `environment`.
168
+ This configuration uses only the staging rule. It does not use the built-in `allow` entries.
169
+
170
+ Replacing `allow` does not replace `soft_deny`, `hard_deny`, `protectedPaths`, or `environment`.
171
+
172
+ `permissions.deny`, `permissions.ask`, and `permissions.allow` do not support `$defaults`. These lists contain only explicit Pi tool patterns. All three lists default to `[]`.
173
+
174
+ Global, project-local, and inline configuration can add all three permission lists. Shared project configuration can add only deny and ask rules.
134
175
 
135
- `$defaults` is not used in `permissions.deny` or `permissions.ask`. Those lists contain only explicit Pi tool patterns.
176
+ `autoMode.allow` and `permissions.allow` are different. `autoMode.allow` contains prose exceptions that the classifier weighs against soft-deny rules. `permissions.allow` contains tool patterns that skip classifier review.
136
177
 
@@ -0,0 +1,90 @@
1
+ # Agent diagnostics
2
+
3
+ `automode_inspect` is a model-callable, read-only tool. An agent can use it to inspect active pi-automode state without copied slash-command output.
4
+
5
+ Use this tool to investigate an unexpected allow or block. Do not use it to change a safety control or to bypass a denial.
6
+
7
+ ## Inspection views
8
+
9
+ The tool accepts one `action` value.
10
+
11
+ | Action | Purpose | Result |
12
+ | --- | --- | --- |
13
+ | `status` | Inspect active state and counters. | A readable status summary and the enabled override, last decision, and action counters. |
14
+ | `config` | Inspect the effective configuration. | The active configuration, the resolved observability log path, and configuration diagnostics. |
15
+ | `defaults` | Inspect built-in rule lists. | The built-in environment, allow, protected-path, soft-deny, and hard-deny lists. |
16
+ | `denials` | Find recent rejected actions. | Reverse-chronological timestamps, enforcement kinds, and tool names. |
17
+
18
+ The tool reads the same in-memory configuration and state that pi-automode enforces. Configuration changes take effect after session start or `/automode reload`.
19
+
20
+ ## Enforcement behavior
21
+
22
+ An inspection call is not an unrestricted escape hatch.
23
+
24
+ 1. `permissions.deny` runs first.
25
+ 2. `permissions.ask` runs next.
26
+ 3. Deterministic hard-deny checks run next.
27
+ 4. The extension bypasses classifier routing only for its own registered `automode_inspect` tool.
28
+
29
+ If a local check blocks the call, pi-automode records it as a blocked action. If the call passes, inspection does not change counters, state, or logs.
30
+
31
+ The extension verifies the registered tool source before it applies the bypass. A tool from another extension with the same name does not receive this exemption.
32
+
33
+ The tool cannot enable or disable auto mode, reload configuration, reset state, select a model, or edit configuration. The user must run the related `/automode` command directly.
34
+
35
+ ## Model-visible data
36
+
37
+ Pi sends tool output to the current model. The output has deliberate privacy limits:
38
+
39
+ - `status` does not include the last decision reason.
40
+ - `denials` does not include denial reasons or action payloads.
41
+ - `config` removes the parser detail from invalid JSON diagnostics.
42
+
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
+
45
+ The tool serializes output defensively. Arrays longer than 30 entries become an object with these fields:
46
+
47
+ ```json
48
+ {
49
+ "$truncatedArray": true,
50
+ "items": ["first entries"],
51
+ "omittedEntries": 18,
52
+ "totalEntries": 48
53
+ }
54
+ ```
55
+
56
+ String values and the complete serialized result also have size limits. Treat a truncated result as incomplete. An omitted rule can still exist.
57
+
58
+ ## Observability log path
59
+
60
+ Use the `config` view to get the log path for the current session. Its `logFile` value uses the same resolution as observability logging:
61
+
62
+ - A persisted session uses a sidecar beside its Pi session file.
63
+ - An in-memory session uses the application-owned log directory for the effective session working directory.
64
+
65
+ For in-memory sessions, pi-automode uses this default location:
66
+
67
+ ```text
68
+ ~/.pi/agent/extensions/pi-automode/logs/<encoded-session-cwd>/YYYY-MM-DD/<session-id>-pi-automode.jsonl
69
+ ```
70
+
71
+ This includes `--no-session` runs and non-persisted subagents. The path does not use the launching process working directory.
72
+
73
+ See [Observability logging](observability-logging.md) for log configuration and entry schemas.
74
+
75
+ ## Diagnosis workflow
76
+
77
+ Classifier rules cannot override permission or deterministic denials.
78
+
79
+ 1. Call `automode_inspect` with `status`, `config`, and `denials`.
80
+ 2. If observability logging is enabled, use the reported log path.
81
+ 3. Read a matching decision entry before you propose a rule change.
82
+ 4. Identify the enforcement layer before you change a rule.
83
+ 5. If a configuration change is necessary, explain the change.
84
+ 6. Ask the user to run `/automode off`.
85
+ 7. Make only the requested configuration change during that maintenance window.
86
+ 8. Ask the user to run `/automode reload` and `/automode on`.
87
+ 9. Use `automode_inspect` to make sure that auto mode is enabled.
88
+ 10. Retry only an action that you know is safe.
89
+
90
+ Do not replay an unsafe action because auto mode is off. Do not print sensitive tool input or log content in a diagnosis report.
@@ -1,10 +1,12 @@
1
1
  # Observability logging
2
2
 
3
- Auto mode can write a JSONL observability log next to the current Pi session file, so you can inspect decisions and classifier usage. It is off by default and fail-open: a write error never changes an allow/block decision.
3
+ Auto mode can write a JSONL observability log for decisions and classifier usage. Persisted sessions use a sidecar next to the Pi session file. In-memory sessions use a global application directory.
4
+
5
+ Logging is off by default. Logging fails open, so a write error never changes an allow or block decision.
4
6
 
5
7
  ## Enabling
6
8
 
7
- Set `autoMode.log` in any Pi-owned config source (`~/.pi/agent/automode.json`, `.pi/automode.local.json`, or `PI_AUTOMODE_SETTINGS_JSON`):
9
+ Set `autoMode.log` in a Pi-owned configuration source. These sources include `~/.pi/agent/extensions/pi-automode/config.json`, trusted `.pi/automode.local.json`, and `PI_AUTOMODE_SETTINGS_JSON`.
8
10
 
9
11
  ```json
10
12
  {
@@ -18,44 +20,62 @@ Set `autoMode.log` in any Pi-owned config source (`~/.pi/agent/automode.json`, `
18
20
  ```
19
21
 
20
22
  - `enabled` — write one `decision` line per tool-call decision and one ccusage-compatible `message` line per classifier model response.
21
- - `classifierIo` — also write the classifier's prompt, raw model responses, and parsed decision for classifier-routed actions. Off by default; see [Privacy](#privacy) below.
23
+ - `classifierIo` — also write the classifier prompt, raw responses, and parsed decision for classifier-routed actions. The default is `false`. See [Privacy](#privacy).
24
+
25
+ Fields merge independently across configuration sources. For example, set `enabled` globally and set `classifierIo` for a trusted project.
22
26
 
23
- Fields merge independently across config scopes (set `enabled` globally and `classifierIo` per project, for example). Shared project `.pi/automode.json` cannot set `log` it follows the same `autoMode` exclusion as the rest of the config. Shape is validated and reported by `/automode config`.
27
+ Shared project `.pi/automode.json` cannot set `log`. The shared file cannot set any `autoMode` field. `/automode config` reports an invalid shape.
24
28
 
25
- Logging only writes entries while auto-mode is **enabled**. With auto-mode off, no tool calls reach the hook, so no entries are produced.
29
+ Pi-automode writes log entries only while auto mode is **enabled**. With auto mode off, no tool calls reach the hook. Thus, pi-automode writes no entries.
26
30
 
27
31
  ## Log file location
28
32
 
29
- The log file is colocated with the current Pi session file, with `-pi-automode` inserted before the extension:
33
+ Pi-automode stores the log next to the current Pi session file. It inserts `-pi-automode` before the extension:
30
34
 
31
35
  ```text
32
36
  <session-file> → <dir>/<id>.jsonl
33
37
  <session-file>-pi-automode.jsonl → <dir>/<id>-pi-automode.jsonl
34
38
  ```
35
39
 
36
- For example: `~/.pi/agent/sessions/<slug>/<id>-pi-automode.jsonl`. If no session file is set, it falls back to `<sessionDir>/<sessionId>-pi-automode.jsonl`. Run `/automode config` to see the resolved path.
40
+ For example, pi-automode can use `~/.pi/agent/sessions/<slug>/<id>-pi-automode.jsonl`.
41
+
42
+ If a custom session manager provides an absolute session directory without a session file, pi-automode uses `<sessionDir>/<sessionId>-pi-automode.jsonl`.
43
+
44
+ Pi in-memory sessions have no session file or session directory. These sessions include `--no-session` and non-persisted subagents. Their logs use this absolute application path:
45
+
46
+ ```text
47
+ ~/.pi/agent/extensions/pi-automode/logs/<encoded-session-cwd>/YYYY-MM-DD/<session-id>-pi-automode.jsonl
48
+ ```
49
+
50
+ The project directory uses the same `--path-with-dashes--` encoding as normal Pi session directories. The date partition uses UTC.
51
+
52
+ A custom session manager can supply an absolute `sessionDir` without a session file. In this case, pi-automode continues to use that directory. Run `/automode config` to see the resolved path.
37
53
 
38
- There is one combined file per session. Each line is one JSON object with a `type` discriminator. Entries for the same tool call share a `decisionId`.
54
+ Persisted sessions use one combined file per session. In-memory sessions use one file for each session ID and UTC day.
55
+
56
+ If a session crosses midnight, pi-automode continues in the file for the next day. Each line contains one JSON object with a `type` discriminator. Entries for the same tool call share a `decisionId`.
57
+
58
+ For persisted sessions, `ccusage pi` reports the sidecar as a separate `-pi-automode` session. Inspect in-memory logs directly because they are outside the normal Pi session tree.
39
59
 
40
60
  ## Entry types
41
61
 
42
62
  ### `decision`
43
63
 
44
- One per tool-call decision. Every allow and every block goes through exactly one of these.
64
+ Pi-automode writes one `decision` entry for each tool-call decision. Each allowed or blocked call has exactly one entry.
45
65
 
46
66
  | field | meaning |
47
67
  | --- | --- |
48
68
  | `ts` | ISO timestamp |
49
- | `decisionId` | links to the `classifier` entry (if any) for the same call |
69
+ | `decisionId` | links to the related `classifier` entry. Local decisions have no related entry. |
50
70
  | `sessionId` | Pi session id |
51
71
  | `cwd` | working directory |
52
- | `tool` | tool name, e.g. `bash`, `write` |
72
+ | `tool` | tool name, for example `bash` or `write` |
53
73
  | `summary` | `actionSummary` — tool name + input JSON (truncated) |
54
- | `kind` | enforcement path: `permissions.deny`, `permissions.ask`, `deterministic-hard-deny`, `classifier`, or `read-only` |
74
+ | `kind` | enforcement path: `permissions.deny`, `permissions.ask`, `deterministic-hard-deny`, `deterministic-path-deny`, `permissions.allow`, `inside-working-directory`, `classifier`, `read-only`, or `setup` |
55
75
  | `outcome` | `allow` or `block` |
56
76
  | `reason` | the reason string (classifier reason, or the deterministic/permission reason) |
57
- | `classifierModel` | the configured classifier model, when relevant |
58
- | `reasoning` | classifier reasoning mode and requested/effective level; see below |
77
+ | `classifierModel` | the configured classifier model for a classifier-routed decision |
78
+ | `reasoning` | classifier reasoning mode and requested or effective level. See the examples below. |
59
79
 
60
80
  The reasoning field records either server-default mode:
61
81
 
@@ -69,11 +89,17 @@ or an explicit request after model-level clamping:
69
89
  {"mode":"explicit","requestedLevel":"max","effectiveLevel":"xhigh"}
70
90
  ```
71
91
 
72
- Classifier-routed decisions contain the effective level once the configured model resolves, even when `classifierIo` is off or authentication then fails. If the configured model itself cannot be resolved, an explicit entry records `requestedLevel` without `effectiveLevel` because no model-supported level exists. A local permission, deterministic, or read-only decision does not run the classifier and likewise may omit `effectiveLevel`. In `server-default` mode, the concrete server-selected level is not observable and is not inferred.
92
+ Classifier-routed decisions contain the effective level after model resolution. If `classifierIo` is off or authentication fails later, this field still exists.
93
+
94
+ If pi-automode cannot resolve the model, the entry contains `requestedLevel` without `effectiveLevel`. In this case, no model-supported level exists.
95
+
96
+ Local permission, deterministic, `permissions.allow`, and read-only decisions do not run the classifier. These decisions can omit `effectiveLevel`. Pi-automode cannot observe or infer the server-selected level in `server-default` mode.
73
97
 
74
98
  ### `message` (classifier usage)
75
99
 
76
- One per classifier model response, including malformed responses that trigger a retry. Written whenever logging is enabled, before the matching `decision` line. Its shape is intentionally compatible with `ccusage pi`:
100
+ Pi-automode writes one `message` entry for each classifier response. This includes a malformed response that causes a retry.
101
+
102
+ When logging is enabled, pi-automode writes this entry before the matching `decision` line. The entry shape is compatible with `ccusage pi`:
77
103
 
78
104
  | field | meaning |
79
105
  | --- | --- |
@@ -82,20 +108,23 @@ One per classifier model response, including malformed responses that trigger a
82
108
  | `message.model` | model ID returned by the classifier provider |
83
109
  | `message.usage` | provider-reported input, output, cache, total-token, and cost fields |
84
110
 
85
- `ccusage` reports this sidecar as a separate `-pi-automode` session. This entry contains no prompt or response text, so it is written even when `classifierIo` is off.
111
+ For persisted sessions, `ccusage` reports this sidecar as a separate `-pi-automode` session. This entry contains no prompt or response text. When `classifierIo` is off, pi-automode still writes it.
86
112
 
87
113
  ### `classifier`
88
114
 
89
- Written only for classifier-routed actions, and only when `classifierIo: true`. It follows any classifier-usage `message` entries and precedes the matching `decision` line in the file.
115
+ If `classifierIo: true`, pi-automode writes a `classifier` entry. It writes this entry only for classifier-routed actions.
116
+
117
+ The entry follows all related classifier-usage `message` entries. It precedes the matching `decision` entry.
90
118
 
91
119
  | field | meaning |
92
120
  | --- | --- |
93
121
  | `ts` | ISO timestamp |
94
122
  | `decisionId` | matches the `decision` entry for the same call |
95
- | `model` | classifier model used, e.g. `anthropic/claude-haiku-4` |
123
+ | `model` | classifier model, for example `anthropic/claude-haiku-4` |
96
124
  | `reasoning` | `server-default`, or the explicit requested and effective model-supported level |
97
125
  | `prompt.system` | the full system policy with `environment`/`allow`/`soft_deny`/`hard_deny` rules interpolated |
98
- | `prompt.context` | the shared context message: loaded project instructions + classifier transcript + action |
126
+ | `prompt.context` | the shared context message: loaded project instructions + classifier transcript |
127
+ | `prompt.action` | the complete, untruncated current tool action JSON |
99
128
  | `prompt.fastInstruction` | the exact one-token filter instruction |
100
129
  | `prompt.detailedInstruction` | the exact structured-review instruction |
101
130
  | `attempts` | one entry per classifier model call (see below) |
@@ -106,20 +135,26 @@ Each `attempts[]` entry is `{ stage, attempt, response?, parsed?, error?, durati
106
135
 
107
136
  - `stage` — `fast` for the one-token filter or `detailed` for structured review.
108
137
  - `response` — `{ stopReason, text, model, timestamp, usage, errorMessage? }`, the raw model output and provider-reported usage for that call, including provider-reported errors and aborted requests.
109
- - `parsed` — the decision parsed from the response, or absent if it did not parse.
110
- - `error` — present when the call threw (network/auth); `response` is then absent.
138
+ - `parsed` — the decision parsed from the response, or absent after a parse failure.
139
+ - `error` — present after a network or authentication error. In this case, `response` is absent.
111
140
 
112
- This records both stages, retries, and fail-closed cases verbatim. A fast allow has one entry. A review followed by malformed detailed JSON and a successful retry has three entries.
141
+ The array records both stages, retries, and fail-closed cases. A fast allow has one entry. A review with one successful retry has three entries.
113
142
 
114
143
  ## Privacy
115
144
 
116
- `prompt.context` is the shared content sent to both classifier stages: loaded project instructions, selected transcript evidence, and the action being classified. The stage-specific final instructions are logged separately. See [Auto-mode classifier flow → What is sent to the classifier](automode-classifier-flow.md#what-is-sent-to-the-classifier) for how the evidence is assembled and truncated.
145
+ `prompt.context` contains project instructions and transcript evidence for both classifier stages. `prompt.action` contains the separate, untruncated current action. Pi-automode logs the final stage instructions separately.
146
+
147
+ See [Auto-mode classifier flow → What is sent to the classifier](automode-classifier-flow.md#what-is-sent-to-the-classifier) for the evidence assembly rules.
117
148
 
118
- The log records this payload locally, but the same data is also sent to the classifier model endpoint on every classifier-routed call. If `classifierModel` points at a cloud provider, that payload leaves the machine. Enable `classifierIo` when debugging classifier behavior or tuning rules; leave it off for routine outcome logging.
149
+ The log records this payload locally. Pi-automode also sends the same data to the classifier endpoint for each classifier-routed call.
150
+
151
+ If `classifierModel` uses a cloud provider, the payload leaves the machine. Enable `classifierIo` only for classifier diagnosis or rule tuning. Leave it off for routine outcome logging.
119
152
 
120
153
  ## Sizing
121
154
 
122
155
  - `decision` line: ~0.4–2 KB (driven by `summary`, which carries the tool input, capped at 6 KB).
123
156
  - `classifier` line: ~5 KB fixed policy plus loaded project instructions, selected transcript evidence, stage instructions, and recorded responses.
124
157
 
125
- Transcript evidence is bounded separately by `autoMode.maxUserTranscriptTokens` and `autoMode.maxToolTranscriptTokens`, both 4000 approximate tokens by default. Assistant prose and tool results are not included. Provider cache hits can reduce billed or processed input where supported, but the log still records the full classifier payload.
158
+ `autoMode.maxUserTranscriptTokens` and `autoMode.maxToolTranscriptTokens` limit transcript evidence separately. Both fields default to approximately 4000 tokens.
159
+
160
+ Pi-automode excludes assistant prose and tool results. Provider cache hits can reduce processed or billed input. The log still records the full classifier payload.
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "autoMode": {
3
3
  "classifierReasoningLevel": "low",
4
+ "classifierTimeoutMs": 20000,
4
5
  "allowInsideWorkingDirectory": false,
5
6
  "deniedPaths": [
6
7
  "*.env",
@@ -27,6 +28,10 @@
27
28
  ]
28
29
  },
29
30
  "permissions": {
31
+ "allow": [
32
+ "bash(git status*)",
33
+ "example-extension-tool"
34
+ ],
30
35
  "ask": [
31
36
  "bash(git push *)"
32
37
  ],