pi-claude-supervisor 0.9.1 → 0.10.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 +24 -0
- package/README.cn.md +20 -3
- package/README.md +25 -3
- package/docs/architecture.md +138 -12
- package/docs/autonomy-target.md +2 -1
- package/docs/implementation-review.md +2 -1
- package/docs/optimization-roadmap.md +437 -0
- package/docs/testing.md +42 -3
- package/package.json +2 -1
- package/src/config.ts +11 -0
- package/src/decision-worker.ts +160 -11
- package/src/hooks/relay.ts +28 -15
- package/src/hooks/server.ts +4 -3
- package/src/hooks/settings.ts +7 -2
- package/src/hooks/types.ts +11 -3
- package/src/index.ts +3 -1
- package/src/policy.ts +611 -2
- package/src/redaction.ts +15 -0
- package/src/supervisor.ts +353 -39
- package/src/verifier.ts +23 -12
- package/src/worker/environment.ts +7 -0
- package/src/worker/input-error.ts +20 -0
- package/src/worker/tmux-adapter.ts +207 -14
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented here.
|
|
4
4
|
|
|
5
|
+
## [0.10.0](https://github.com/btnalit/pi-claude-supervisor/compare/v0.9.2...v0.10.0) (2026-09-24)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **policy:** delete/move floor beneath every permission authority ([#75](https://github.com/btnalit/pi-claude-supervisor/issues/75)) ([967f679](https://github.com/btnalit/pi-claude-supervisor/commit/967f6796503b28c58a6523ceae51d066f17d2b56))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **automation:** wait out provider outages; stop parking on binaries and symlinks ([#72](https://github.com/btnalit/pi-claude-supervisor/issues/72)) ([c2969e0](https://github.com/btnalit/pi-claude-supervisor/commit/c2969e08e4070cf977699f3b2bf90697c730cb9e))
|
|
16
|
+
* **hooks:** keep interactive supervision after the Worker changes directory ([#69](https://github.com/btnalit/pi-claude-supervisor/issues/69)) ([a46177a](https://github.com/btnalit/pi-claude-supervisor/commit/a46177a1769c180decef306f78418aa22be52216))
|
|
17
|
+
* **supervisor:** park a self-started verification that throws part-way ([#73](https://github.com/btnalit/pi-claude-supervisor/issues/73)) ([dfc546a](https://github.com/btnalit/pi-claude-supervisor/commit/dfc546a6871a4ed89f0b126661fa96436d5cac78))
|
|
18
|
+
* **supervisor:** stop a stray prompt from pausing unattended automation for good ([#74](https://github.com/btnalit/pi-claude-supervisor/issues/74)) ([7de0c6c](https://github.com/btnalit/pi-claude-supervisor/commit/7de0c6c09c58e20712659a3f24a97efc6eb704b3))
|
|
19
|
+
* **tmux:** deliver interactive input reliably instead of parking on a busy prompt ([#71](https://github.com/btnalit/pi-claude-supervisor/issues/71)) ([46083d2](https://github.com/btnalit/pi-claude-supervisor/commit/46083d27973261a0e51615e9234cb3f682b3a9fc))
|
|
20
|
+
|
|
21
|
+
## [0.9.2](https://github.com/btnalit/pi-claude-supervisor/compare/v0.9.1...v0.9.2) (2026-09-24)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Bug Fixes
|
|
25
|
+
|
|
26
|
+
* **supervisor:** finish stop and startup races; add gated live Decision spike ([#67](https://github.com/btnalit/pi-claude-supervisor/issues/67)) ([648b269](https://github.com/btnalit/pi-claude-supervisor/commit/648b269f185fbeb5ab39fe22ce855cc8d4342747))
|
|
27
|
+
* **supervisor:** stability fixes found by end-to-end runs with a real Decision Worker ([#65](https://github.com/btnalit/pi-claude-supervisor/issues/65)) ([cd0b8ef](https://github.com/btnalit/pi-claude-supervisor/commit/cd0b8efdd0898833036b14acab5ca7b29ca70d04))
|
|
28
|
+
|
|
5
29
|
## [0.9.1](https://github.com/btnalit/pi-claude-supervisor/compare/v0.9.0...v0.9.1) (2026-09-24)
|
|
6
30
|
|
|
7
31
|
|
package/README.cn.md
CHANGED
|
@@ -130,7 +130,10 @@ Pi 会在 Claude 结束一轮对话时(`Stop`;轮中 API/模型失败会以 `Sto
|
|
|
130
130
|
**人机协同。** 如果你在已 attach 的 session 中输入内容,自动化会暂停
|
|
131
131
|
(`human_takeover`,以警告形式呈现),直到你执行 `/supervise resume-auto
|
|
132
132
|
<task-id>`;你接管期间完成的那一轮会在此时重放给 Decision Worker,因此不会
|
|
133
|
-
|
|
133
|
+
丢失已经完成的工作。如果你输入后离开,在你最后一次输入且你的那一轮结束之后,Worker
|
|
134
|
+
持续空闲满 `HUMAN_IDLE_RESUME_MS`(默认 30 分钟)时,自动化会自行恢复(`automation_auto_resumed`);
|
|
135
|
+
停在半路(例如 Claude 对话框)的轮次只会告警一次。显式的 `/supervise takeover`,
|
|
136
|
+
以及恢复出来、尚未 `resume-auto` 的任务,仍需 `resume-auto`。
|
|
134
137
|
|
|
135
138
|
**完成后把会话交还给你。** 与其他 transport 不同,任务完成后默认只是让 Pi 与
|
|
136
139
|
该会话断开,而不是关闭它,方便你在同一窗口中继续工作或查看 Claude 做了什么;
|
|
@@ -178,6 +181,18 @@ stream-json` 的方式运行 Claude,完全没有终端界面;一旦设置
|
|
|
178
181
|
`git commit -m` 就是数据。除此之外的一切(`for f in …; do echo "$f"`、
|
|
179
182
|
`rm -rf ./dist`、写入 Claude 自己的 scratchpad)都按配置的策略处理——由
|
|
180
183
|
Claude 自己的权限模式决定,和你亲自运行 Claude 时一样。
|
|
184
|
+
- 同样始终拒绝的还有**删除底线**:Bash 中的 `rm`/`rmdir`/`unlink`/`shred`/`mv`/
|
|
185
|
+
`rimraf`/`find -delete`/`git clean -C …`/`rsync --delete`——无论出现在语句的哪个
|
|
186
|
+
位置(因此任何包装器都能看穿),以及引号脚本(`sh -lc '…'`、`trap '…'`)、
|
|
187
|
+
`$(…)`、管道喂给 shell 的文本里的——只能作用于任务目录、额外写入根或临时目录
|
|
188
|
+
之内。以下会被拒绝:策略无法按字面读出的目标(`$(pwd)/..`、未知的 `$VAR`)、
|
|
189
|
+
无法解析的 `cd` 之后的相对目标、来自 `xargs` 的目标;任务目录本身或包含它的
|
|
190
|
+
目录;整个临时/写入根;`.git`、隐藏项通配(`.*`)、后跟 `..` 的通配、不带过滤的
|
|
191
|
+
`find .`;`git prune`、`git gc --prune`、`git reflog expire`。日常清理
|
|
192
|
+
(`rm -rf dist node_modules .cache`、`rm -f src/*.js`、
|
|
193
|
+
`find . -name '*.pyc' -delete`、`d=$(mktemp -d); rm -rf "$d"`、
|
|
194
|
+
`rm -f .git/index.lock`)不受影响。不覆盖:解释器或脚本文件执行的删除、覆盖写
|
|
195
|
+
(`cp`、`ln -sf`、`>`)、非受保护分支上的 `git reset --hard`。
|
|
181
196
|
- `autonomy.permissionAuthority`(`policy` | `hybrid` 默认 |
|
|
182
197
|
`decision-worker`)决定谁来回答权限请求——headless 模式下是每一个请求,交互式
|
|
183
198
|
tmux 模式下只是那些 Claude 本来会弹窗问你的请求:`hybrid` 会让策略独自回答
|
|
@@ -326,7 +341,7 @@ commit,通知能说清已验证的 commit 是否在工作树变动之前就已
|
|
|
326
341
|
| `HUMAN_WEBHOOK_SECRET` | 未设置 | HMAC 签名密钥;以 `x-pi-supervisor-signature` header 发送 |
|
|
327
342
|
| `UNATTENDED` | `true` | 任务无需同步人工回调即可运行 |
|
|
328
343
|
| `REQUIRE_LOCAL_COMMIT` | `true` | 完成前要求在候选所在分支上有本地 commit |
|
|
329
|
-
| `MAX_DECISION_RETRIES` | `4`(0–10) | Decision Worker
|
|
344
|
+
| `MAX_DECISION_RETRIES` | `4`(0–10) | Decision Worker 调用超时,或因等待无法解决的原因失败(prompt 过长、会话损坏)时的重试次数;依次等待 15s、45s、60s。已完成轮次遇到暂时性的 provider/网络故障(429/529、5xx、连接重置)时改为持续等待,每分钟重试一次;凭证被拒、计费问题、模型不存在则立即 park |
|
|
330
345
|
| `PERMISSION_AUTHORITY` | `hybrid` | `policy` \| `hybrid` \| `decision-worker` |
|
|
331
346
|
| `REMOTE_AUTHORITY` | `none` | `none` \| `push` \| `pr`;验收通过后开启发布阶段。`--remote` 可按任务覆盖 |
|
|
332
347
|
| `REMOTE_NAME` | `origin` | 发布授权唯一允许的 remote 名 |
|
|
@@ -346,6 +361,7 @@ commit,通知能说清已验证的 commit 是否在工作树变动之前就已
|
|
|
346
361
|
| `DEADLINE_GRACE_MS` | `30m` | 自动任务到期后的收尾窗口:空闲的 Worker 会被验收而不是停止;`0` 恢复到期立即停止 |
|
|
347
362
|
| `DEADLINE_WARNING_MS` | `15m` | 到期前多久提醒并重新询问 Decision Worker;`0` 关闭提醒 |
|
|
348
363
|
| `NO_OUTPUT_TIMEOUT_MS` | `20m` | Worker 多久没有输出就停止;`0` 关闭该检查 |
|
|
364
|
+
| `HUMAN_IDLE_RESUME_MS` | `30m` | 你在受监督会话中输入后,在你最后一次输入且你的那一轮结束之后 Worker 空闲这么久,即自动恢复自动化(1 分钟到 24 小时);`0` 保持暂停直到 `resume-auto`。不适用于显式接管 |
|
|
349
365
|
| `EVENT_LOG_MAX_BYTES` | `67108864`(64 MiB) | `events.jsonl` 达到该大小后滚动,保留 5 份滚动文件 |
|
|
350
366
|
|
|
351
367
|
## 恢复、租约与状态
|
|
@@ -474,7 +490,8 @@ npm run test:install
|
|
|
474
490
|
|
|
475
491
|
详见 [architecture](docs/architecture.md)、[testing](docs/testing.md) 和
|
|
476
492
|
[releasing](docs/releasing.md);`docs/autonomy-target.md` 记录了本项目所
|
|
477
|
-
|
|
493
|
+
围绕的、已确认的无人值守开发目标;[optimization-roadmap](docs/optimization-roadmap.md)
|
|
494
|
+
是 0.9.2 之后的优化路线图(提案)。
|
|
478
495
|
|
|
479
496
|
## License
|
|
480
497
|
|
package/README.md
CHANGED
|
@@ -156,7 +156,12 @@ current work and is judged on its next `Stop` instead.
|
|
|
156
156
|
pauses (`human_takeover`, visible as a warning) until you run `/supervise
|
|
157
157
|
resume-auto <task-id>`; the turn that completed while you were driving is
|
|
158
158
|
replayed to the Decision Worker at that point, so nothing already finished is
|
|
159
|
-
lost.
|
|
159
|
+
lost. If you type and walk away, the pause lifts on its own once the Worker has
|
|
160
|
+
sat idle for `HUMAN_IDLE_RESUME_MS` (default 30 minutes) after both your last
|
|
161
|
+
prompt and the end of your last turn (`automation_auto_resumed`); a turn left
|
|
162
|
+
waiting mid-way (a Claude dialog, say) is reported once instead. An explicit
|
|
163
|
+
`/supervise takeover`, and a recovered task until you resume it, waits for
|
|
164
|
+
`resume-auto`.
|
|
160
165
|
|
|
161
166
|
**Completion hands the session back.** Unlike other transports, a completed
|
|
162
167
|
task by default disconnects Pi from the session instead of closing it, so you
|
|
@@ -214,6 +219,21 @@ Supervisor being able to see it, or when you don't need to attach.
|
|
|
214
219
|
`git commit -m` stores it. Everything else (`for f in …; do echo "$f"`,
|
|
215
220
|
`rm -rf ./dist`, a Write to Claude's own scratchpad) follows the configured
|
|
216
221
|
policy — Claude's own permission mode governs it, as when you run Claude.
|
|
222
|
+
- Also always denied: the **delete floor**. A Bash `rm`/`rmdir`/`unlink`/
|
|
223
|
+
`shred`/`mv`/`rimraf`/`find -delete`/`git clean -C …`/`rsync --delete` —
|
|
224
|
+
wherever it stands in the statement, so behind any wrapper, and inside
|
|
225
|
+
quoted scripts (`sh -lc '…'`, `trap '…'`), `$(…)` and text piped into a
|
|
226
|
+
shell — may only reach paths inside the task directory, its extra write
|
|
227
|
+
roots or the temp directory. Refused: a target the policy cannot read
|
|
228
|
+
literally (`$(pwd)/..`, an unknown `$VAR`), one after an unresolvable `cd`,
|
|
229
|
+
one fed by `xargs`; the task directory itself or anything containing it; a
|
|
230
|
+
whole temp/write root; `.git`, hidden-entry globs (`.*`), globs followed by
|
|
231
|
+
`..`, an unfiltered `find .`; `git prune`, `git gc --prune`,
|
|
232
|
+
`git reflog expire`. Ordinary cleanup (`rm -rf dist node_modules .cache`,
|
|
233
|
+
`rm -f src/*.js`, `find . -name '*.pyc' -delete`, `d=$(mktemp -d); rm -rf
|
|
234
|
+
"$d"`, `rm -f .git/index.lock`) is untouched. Not covered: deletes run by an
|
|
235
|
+
interpreter or script file, overwrites (`cp`, `ln -sf`, `>`), and
|
|
236
|
+
`git reset --hard` outside protected branches.
|
|
217
237
|
- `autonomy.permissionAuthority` (`policy` | `hybrid` default |
|
|
218
238
|
`decision-worker`) controls who answers a permission request — every
|
|
219
239
|
request in headless mode, and in interactive tmux mode only those Claude
|
|
@@ -418,7 +438,7 @@ unparsable keeps the default and is reported once as a
|
|
|
418
438
|
| `HUMAN_WEBHOOK_SECRET` | unset | HMAC signing secret; sent as the `x-pi-supervisor-signature` header |
|
|
419
439
|
| `UNATTENDED` | `true` | Task runs without a synchronous human callback |
|
|
420
440
|
| `REQUIRE_LOCAL_COMMIT` | `true` | Require a local commit on the candidate's branch before completion |
|
|
421
|
-
| `MAX_DECISION_RETRIES` | `4` (0–10) | Retries of a Decision Worker call that times out or fails (
|
|
441
|
+
| `MAX_DECISION_RETRIES` | `4` (0–10) | Retries of a Decision Worker call that times out or fails for a reason waiting does not fix (a prompt that is too long, a corrupted session); waits 15s, 45s, then 60s. A transient provider or network outage (429/529, 5xx, resets) on a completed turn is waited out instead, one attempt a minute; rejected credentials, billing and a missing model park at once |
|
|
422
442
|
| `PERMISSION_AUTHORITY` | `hybrid` | `policy` \| `hybrid` \| `decision-worker` |
|
|
423
443
|
| `REMOTE_AUTHORITY` | `none` | `none` \| `push` \| `pr`; grants the publish phase after verification passes. `--remote` overrides it per task |
|
|
424
444
|
| `REMOTE_NAME` | `origin` | The single remote a publish grant may name |
|
|
@@ -438,6 +458,7 @@ unparsable keeps the default and is reported once as a
|
|
|
438
458
|
| `DEADLINE_GRACE_MS` | `30m` | Close-out window after the deadline for automatic tasks: an idle Worker is verified instead of stopped; `0` restores the immediate stop |
|
|
439
459
|
| `DEADLINE_WARNING_MS` | `15m` | How long before the deadline the Decision Worker is warned and re-asked; `0` disables the warning |
|
|
440
460
|
| `NO_OUTPUT_TIMEOUT_MS` | `20m` | Stop a Worker that has produced no output for this long; `0` disables the check |
|
|
461
|
+
| `HUMAN_IDLE_RESUME_MS` | `30m` | After you type into a supervised session, resume automation once the Worker has been idle this long after your last prompt and the end of your last turn (1 minute to 24 hours); `0` keeps the pause until `resume-auto`. Never applies to an explicit takeover |
|
|
441
462
|
| `EVENT_LOG_MAX_BYTES` | `67108864` (64 MiB) | Rotates `events.jsonl` at this size; 5 rotated files are kept |
|
|
442
463
|
|
|
443
464
|
## Recovery, leases and state
|
|
@@ -588,7 +609,8 @@ from a path that is not group/world-writable.
|
|
|
588
609
|
See [architecture](docs/architecture.md), [testing](docs/testing.md), and
|
|
589
610
|
[releasing](docs/releasing.md) for more detail; `docs/autonomy-target.md`
|
|
590
611
|
records the confirmed unattended-development target this project is built
|
|
591
|
-
around.
|
|
612
|
+
around, and [`docs/optimization-roadmap.md`](docs/optimization-roadmap.md) (in
|
|
613
|
+
Chinese) proposes the post-0.9.2 optimization roadmap.
|
|
592
614
|
|
|
593
615
|
## License
|
|
594
616
|
|
package/docs/architecture.md
CHANGED
|
@@ -138,13 +138,44 @@ An owned manual worker gets a private tmux server/socket and executes the
|
|
|
138
138
|
validated Claude command directly in the pane. An owned automatic worker instead
|
|
139
139
|
starts the Supervisor bridge through a cgroup-joining pane bootstrap, so its
|
|
140
140
|
bridge identity is not a manual adoption target. The worker environment is
|
|
141
|
-
passed through unchanged
|
|
142
|
-
start
|
|
143
|
-
|
|
141
|
+
passed through unchanged, apart from removing `CLAUDECODE` so nested Claude can
|
|
142
|
+
start and defaulting `CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR=1` (see the hook
|
|
143
|
+
relay below); credentials are not copied into a file, and credential-shaped
|
|
144
|
+
command arguments are still rejected.
|
|
144
145
|
`load-buffer`, bracketed `paste-buffer` and `send-keys Enter` provide the input
|
|
145
146
|
boundary without interpolating a task into a shell command. C0/C1 terminal
|
|
146
147
|
control bytes are neutralized (escape sequences removed, a lone CR becomes a newline,
|
|
147
|
-
other C0/C1 bytes become spaces); CRLF is normalized to a newline.
|
|
148
|
+
other C0/C1 bytes become spaces); CRLF is normalized to a newline.
|
|
149
|
+
|
|
150
|
+
A send first waits up to `inputReadyTimeoutMs` (30 s) for an idle prompt; a
|
|
151
|
+
prompt still busy, showing a banner or leftover input after that is refused
|
|
152
|
+
with a retryable `WorkerInputError` (`src/worker/input-error.ts`), since
|
|
153
|
+
nothing was typed. The wait also yields — retryable, nothing typed — as soon
|
|
154
|
+
as another turn starts (a human prompt at the attached pane, Claude's own
|
|
155
|
+
background completion, a turn ending), so a message decided for an earlier
|
|
156
|
+
state is never pasted over it. Just before the paste and before Enter, every
|
|
157
|
+
tmux mode is left (`copy-mode -q`; copy mode swallows Enter and can be stacked
|
|
158
|
+
on the tree chooser). An interactive send is then confirmed by its
|
|
159
|
+
`UserPromptSubmit` hook: if none arrives within `inputConfirmTimeoutMs` (8 s)
|
|
160
|
+
and the input box visibly still holds the message (or Claude's long-paste
|
|
161
|
+
placeholder), Enter is sent again, at most twice — never blindly, since Enter
|
|
162
|
+
on a dialog would pick its default. A message still stuck after that is a
|
|
163
|
+
non-retryable error (a resend would duplicate it); an empty box without the
|
|
164
|
+
hook is accepted and logged, leaving a broken hook channel to the no-output
|
|
165
|
+
watchdog. An `idle_prompt` notification arriving while a send is still
|
|
166
|
+
confirming neither counts as delivery nor closes the turn that is only
|
|
167
|
+
starting. The Supervisor re-applies a Decision `continue`/`redirect`/`answer`/
|
|
168
|
+
`retry` whose message was refused as retryable after 15 s, doubling to at most
|
|
169
|
+
a minute, up to five times (`worker_input_deferred`); any fresh Worker event
|
|
170
|
+
supersedes the retry, as do a later turn, an operator's own message or a
|
|
171
|
+
state change, and only then is the task parked, as a Worker input failure
|
|
172
|
+
(`worker_input_failed`) rather than a Decision Worker failure. An owned
|
|
173
|
+
interactive session turns prompt suggestions off, both in its settings
|
|
174
|
+
(`promptSuggestionEnabled`) and through `CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION`,
|
|
175
|
+
which Claude reads first: the suggestion's ghost text reads as leftover input.
|
|
176
|
+
An adopted session keeps the user's own setting.
|
|
177
|
+
|
|
178
|
+
Automatic agents,
|
|
148
179
|
background tasks, plugins, MCP servers and nested Claude processes stay in the
|
|
149
180
|
same cgroup and are cleaned with the Worker; they are intentionally not rejected
|
|
150
181
|
or polled as a nested-process policy failure. The lexical Bash/file-tool policy
|
|
@@ -204,10 +235,25 @@ are mutually exclusive sub-modes of the automatic tmux transport.
|
|
|
204
235
|
are configured to run a small embedded relay script
|
|
205
236
|
(`src/hooks/relay.ts`'s `HOOK_RELAY_SCRIPT`, written to
|
|
206
237
|
`<stateDir>/hooks/relay.js`). The relay reads the hook event JSON from stdin,
|
|
207
|
-
hashes the
|
|
238
|
+
hashes the task directory to find `<stateDir>/hooks/by-cwd/<sha256(cwd)>` — a
|
|
208
239
|
symlink to a `HookServer`'s unix socket, created only while a Supervisor holds
|
|
209
|
-
that cwd — and forwards the event over the socket
|
|
210
|
-
|
|
240
|
+
that cwd — and forwards the event over the socket together with the directory
|
|
241
|
+
it routed by (`routeCwd`), printing the reply as Claude's hook output. The task
|
|
242
|
+
directory is Claude's `CLAUDE_PROJECT_DIR` (set on every hook command to the
|
|
243
|
+
session's launch directory) when a Supervisor owns it, else the event's `cwd`:
|
|
244
|
+
a Bash `cd` into a subdirectory moves `cwd` but not the project directory, so
|
|
245
|
+
routing by `cwd` alone would silently drop every later event of that Worker.
|
|
246
|
+
A nested Claude launched in a different directory reports that directory as
|
|
247
|
+
its project directory and is not routed to the parent's Supervisor (one
|
|
248
|
+
launched in the task directory itself is, as it always was). A Bash request
|
|
249
|
+
whose shell cwd differs from the task directory is presented to the policy as
|
|
250
|
+
`cd <shell cwd> && <command>`: the policy does not track `cd`, but such a
|
|
251
|
+
command is never classed as routine, so under `hybrid` authority the Decision
|
|
252
|
+
Worker judges it with its real directory shown. Automatic Workers also get
|
|
253
|
+
`CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR=1` (unless the caller set it), which
|
|
254
|
+
returns Claude's Bash to the task directory after each command, so the
|
|
255
|
+
policy's relative-path resolution against the task directory stays true. A
|
|
256
|
+
directory with no owning Supervisor is a fast no-op: the
|
|
211
257
|
relay `stat`s the symlink path and returns before touching `net`. Non-blocking
|
|
212
258
|
events (`SessionStart`, `SessionEnd`, `UserPromptSubmit`, `Notification`) are
|
|
213
259
|
fire-and-forget; `PreToolUse`, `PermissionRequest` and `Stop` block Claude for
|
|
@@ -248,7 +294,21 @@ Worker event; the Supervisor appends a bounded `human_input` event, enters
|
|
|
248
294
|
human takeover (`human_takeover` with `data.source: "worker_prompt"`) if not
|
|
249
295
|
already active, and pauses Decision Worker notification of further
|
|
250
296
|
`turn_completed` events (they are still recorded so `resume-auto` can replay
|
|
251
|
-
the last one) until `/supervise resume-auto`.
|
|
297
|
+
the last one) until `/supervise resume-auto`. That pause (gate `worker_prompt`)
|
|
298
|
+
also lifts on its own once the Worker has sat idle for `humanIdleResumeMs`
|
|
299
|
+
(`PI_CLAUDE_SUPERVISOR_HUMAN_IDLE_RESUME_MS`, default 30 minutes) after both
|
|
300
|
+
the human's last prompt and the end of their last turn: every further prompt
|
|
301
|
+
and every turn end restart the clock, a running turn defers it (one silent for
|
|
302
|
+
a whole period, typically a Claude dialog left open, is reported once as
|
|
303
|
+
`human_session_stalled` with an outbound notice), and `automation_auto_resumed`
|
|
304
|
+
replays the last completed turn like `resume-auto`. A failed status read
|
|
305
|
+
re-arms the check rather than ending it. An explicit takeover, and a recovered
|
|
306
|
+
task until resumed, use gate `other` and never resume on their own. Claude can
|
|
307
|
+
submit a pasted message in another shape than it was pasted (a long paste as a
|
|
308
|
+
placeholder); a prompt that arrives while a paste of the Supervisor's own is
|
|
309
|
+
still confirming is taken as that paste, not as a human; after that only the
|
|
310
|
+
exact text matches, so a later human prompt still reads as human. A
|
|
311
|
+
`pre`-phase request is still
|
|
252
312
|
answered automatically during a human takeover (policy deny or defer) since it
|
|
253
313
|
is not something a human is expected to approve; only a `prompt`-phase request
|
|
254
314
|
pends for `/supervise approve`.
|
|
@@ -516,14 +576,73 @@ approval for ordinary actions; a task that cannot safely produce a candidate is
|
|
|
516
576
|
parked or failed without granting remote/main authority. Model/API failures are
|
|
517
577
|
detected from the Pi `stopReason` (a provider error resolves the prompt normally
|
|
518
578
|
rather than throwing); if the Decision Worker
|
|
519
|
-
API/model call fails, the system
|
|
520
|
-
|
|
579
|
+
API/model call fails, the system classifies the error. Rejected, missing or
|
|
580
|
+
expired credentials, billing and a missing model are configuration errors and
|
|
581
|
+
park at once ("Decision Worker configuration failed"). An explicitly listed
|
|
582
|
+
transient provider or network failure (429/529 overloads and rate limits,
|
|
583
|
+
including a quota that resets, 5xx, gateway, connection and transport timeout
|
|
584
|
+
errors) on a completed turn is waited out, backing off to one attempt a minute
|
|
585
|
+
and recording each `decision_retry`: the task has no human to resume it, and if
|
|
586
|
+
no decision lands the idle watchdog verifies the Worker's finished work — the
|
|
587
|
+
close-out verifies at once rather than wait on a decision that is backing off.
|
|
588
|
+
Everything else stays bounded by `maxDecisionRetries` and then parks: a prompt
|
|
589
|
+
that is too long, a corrupted session, the Decision Worker's own request
|
|
590
|
+
timeout; a permission request, which blocks the Worker mid-turn; and an exit,
|
|
591
|
+
after which only this decision starts verification. A completed turn that a
|
|
592
|
+
later event supersedes while its decision is failing is dropped
|
|
593
|
+
(`decision_ignored`) rather than retried or parked, so the current turn is
|
|
594
|
+
decided next. A
|
|
595
|
+
decision that lands for a turn a later turn, message or verification has since
|
|
596
|
+
superseded is recorded as `decision_ignored` rather than applied. An error
|
|
597
|
+
thrown while applying a decided action is recorded as `decision_action_failed`,
|
|
598
|
+
not as a model failure. The startup
|
|
599
|
+
instructions prompt retries transient provider errors on the same backoff within `maxDecisionRetries`, so a
|
|
600
|
+
provider overload at start does not fail the task before its first turn. An abort is never
|
|
521
601
|
retried, and a `noop` reply on a completed turn or a permission request parks the
|
|
522
602
|
candidate rather than being treated as a resolved decision, while a `noop` on a
|
|
523
|
-
clean Worker exit proceeds to verification.
|
|
603
|
+
clean Worker exit proceeds to verification. A `stop` on a completed turn of an
|
|
604
|
+
unattended task without remote authority stops the Worker (never keeping it open)
|
|
605
|
+
and then verifies its finished work instead of discarding it (`decision_overridden`);
|
|
606
|
+
no repair round may follow, so a failure blocks the candidate. A `stop` on a
|
|
607
|
+
pending permission, on a turn the Worker has already resumed, or on a task with
|
|
608
|
+
remote authority stays a plain stop. Optional alert
|
|
524
609
|
delivery remains independent from event-log persistence, but notification is not
|
|
525
610
|
the control boundary.
|
|
526
611
|
|
|
612
|
+
Beneath every authority sits the delete floor (`deleteFloorViolation` in
|
|
613
|
+
`src/policy.ts`, applied inside `evaluatePermission` to every Bash request, so no
|
|
614
|
+
authority mode, human `/supervise approve` included, can lift it). It reads
|
|
615
|
+
fail-closed rather than modelling each wrapper: any word of a statement that
|
|
616
|
+
names `rm`, `rmdir`, `unlink`, `shred`, `mv` or `rimraf` counts wherever it
|
|
617
|
+
stands (so `sudo`, `busybox`, `env -i`, `command -p`, function and `case` bodies
|
|
618
|
+
are seen through), as do `find -delete`/`-exec rm`, `git clean -C`, `rsync
|
|
619
|
+
--delete` and `git worktree remove --force`. Only text commands (`echo`,
|
|
620
|
+
`grep`, `cat`, …), interpreters, and tools whose `rm` subcommand is not a file
|
|
621
|
+
delete (`git rm`, `npm rm`, `docker rm`) end the scan. Quoted arguments that
|
|
622
|
+
mention a delete (`sh -lc '…'`, `watch '…'`, `trap '…'`), text piped into a
|
|
623
|
+
shell, unquoted heredocs, and `$(…)`/backtick/`<(…)` bodies are judged as
|
|
624
|
+
scripts; beyond six levels of nesting a delete is refused outright. A target is
|
|
625
|
+
refused when it is dynamic, follows a `cd` the reading cannot resolve, or comes
|
|
626
|
+
from `xargs`/`parallel`; when it resolves (parent through realpath, last name as
|
|
627
|
+
written, so `rm link` stays a link removal) outside the task directory, the
|
|
628
|
+
extra write roots and the temp directory; when it is the task directory itself,
|
|
629
|
+
a directory that contains the task, a whole write/temp root or a glob across its
|
|
630
|
+
top; when it is a glob over hidden entries, a glob followed by `..`, or a `find`
|
|
631
|
+
over the task tree whose filter could reach `.git` (a `!`/`-not`/`-o` branch, or
|
|
632
|
+
a pattern matching `.git` or its entries); and when it is Git's own store (a
|
|
633
|
+
stale `.git/*.lock` excepted). Brace alternatives are expanded and judged one by
|
|
634
|
+
one. `git prune`, `git gc --prune*` and `git reflog expire/delete` are refused
|
|
635
|
+
because with no remote authority the local commits are the only copy. Literal
|
|
636
|
+
globs (`rm -rf dist/*`), names bound to literal text, to `$(mktemp …)` or to a
|
|
637
|
+
`for f in *.tmp` glob, and `$HOME`/`$TMPDIR`/`$PWD` are judged as the paths they
|
|
638
|
+
spell. Outside what this floor reads: interpreters and script files
|
|
639
|
+
(`python -c "shutil.rmtree(...)"`, `bash ./cleanup.sh`), overwrites (`cp`,
|
|
640
|
+
`ln -sf`, `install`, `truncate`, `tar -C`, `>`), history rewrites such as
|
|
641
|
+
`git reset --hard` or `git stash clear` (the protected-branch rules cover only
|
|
642
|
+
protected branches), and a sibling task under the same temp directory. The
|
|
643
|
+
cgroup boundary limits resources, not paths; an operator who needs those closed
|
|
644
|
+
runs the Worker in a container or OS sandbox with a read-only view of the rest.
|
|
645
|
+
|
|
527
646
|
Permission requests are not all routed to the Decision Worker model. `autonomy.permissionAuthority`
|
|
528
647
|
(`policy` | `hybrid`, default | `decision-worker`) chooses the authority: `policy` answers
|
|
529
648
|
every request from the deterministic policy alone, `decision-worker` sends every
|
|
@@ -613,10 +732,17 @@ park it without requiring a human to be online. The Reviewer retries provider
|
|
|
613
732
|
errors with a fresh session within a total review budget
|
|
614
733
|
(`PI_CLAUDE_SUPERVISOR_REVIEW_TIMEOUT_MS`, default 10 minutes). Truncated
|
|
615
734
|
(oversize) evidence requests a bounded repair before parking, while incomplete
|
|
616
|
-
evidence
|
|
735
|
+
evidence (an unsafe path, a read or git failure, an untracked hard link, which
|
|
736
|
+
could hide text from the Reviewer) still parks. An untracked binary (with its
|
|
737
|
+
size), a symlink (with its target, not followed) or a special file is named in
|
|
738
|
+
the evidence without its content; that is complete evidence, not a park.
|
|
617
739
|
|
|
618
740
|
A `revise` result produces an audited repair round and sends a bounded corrective
|
|
619
741
|
instruction to a still-live `repairableSession` Worker. Checks and review then run again.
|
|
742
|
+
The Decision Worker sees the last result tagged with the Worker turn it judged, and chooses
|
|
743
|
+
when to verify again; if it keeps steering instead, the Supervisor verifies on its own once
|
|
744
|
+
the Worker has taken three turns since that failure (`decision_overridden`), so a Decision
|
|
745
|
+
Worker reasoning from the stale failure cannot hold a fixed Worker in a loop until the deadline.
|
|
620
746
|
The repair budget defaults to three rounds. P0/P1 findings block a `pass` but are repair
|
|
621
747
|
inputs like any other concrete finding (a `pass` carrying one is treated as `revise`); a
|
|
622
748
|
`human` verdict, repeated findings or an exhausted budget stop automation and park a
|
package/docs/autonomy-target.md
CHANGED
|
@@ -119,7 +119,8 @@ control. They are not entered by ordinary uncertainty, and a legacy approval obj
|
|
|
119
119
|
the deterministic known-command remote push/main merge denial. The existing independent Review
|
|
120
120
|
and protected CI/release paths remain the final external checks. Automatic Claude workers preserve
|
|
121
121
|
Claude Code's normal environment, network, tool, agent and MCP surface; `CLAUDECODE` is removed
|
|
122
|
-
only to permit intentional nested Claude sessions
|
|
122
|
+
only to permit intentional nested Claude sessions, and `CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR=1`
|
|
123
|
+
is defaulted so Bash returns to the task directory after each command. The Supervisor-owned cgroup remains a cleanup
|
|
123
124
|
boundary, not a capability allowlist. Automatic tmux parent-death recovery leaves an empty
|
|
124
125
|
cgroup as evidence and permits `recover --takeover` only after Supervisor ownership, persisted
|
|
125
126
|
Worker/cgroup identity (including the cgroup device/inode), dead tmux-server identity, a gone
|
|
@@ -33,7 +33,8 @@ custom/nested descendants are trusted rather than denied by a nested-process gua
|
|
|
33
33
|
- Manual Worker and verifier processes retain the baseline environment behavior;
|
|
34
34
|
automatic Workers pass the full Supervisor environment, including credentials,
|
|
35
35
|
helpers, custom settings and proxy/network variables, with only `CLAUDECODE`
|
|
36
|
-
removed so nested Claude can start
|
|
36
|
+
removed so nested Claude can start and `CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR=1`
|
|
37
|
+
defaulted so Bash stays in the task directory.
|
|
37
38
|
- Startup failures clean up a worker and do not let event-log failures hide the
|
|
38
39
|
original error.
|
|
39
40
|
- Default wall-clock and no-output watchdogs stop stalled workers.
|