@ferris1225/pi-subagents 4.3.7 → 4.3.9
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 +38 -0
- package/README.md +119 -34
- package/agents/sentinel.md +21 -0
- package/index.ts +2 -0
- package/package.json +2 -2
- package/src/configuration/config.ts +10 -15
- package/src/delegation/agents.ts +1 -0
- package/src/delegation/dispatch.ts +176 -20
- package/src/delegation/phase-scope.ts +208 -0
- package/src/delegation/prompt.ts +36 -12
- package/src/delegation/risk.ts +168 -0
- package/src/lifecycle/durable.ts +18 -0
- package/src/lifecycle/runtime.ts +12 -1
- package/src/lifecycle/thread-lifecycle.ts +62 -6
- package/src/lifecycle/thread-restore.ts +3 -0
- package/src/lifecycle/thread-shared.ts +11 -4
- package/src/lifecycle/tools.ts +12 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,44 @@ Published versions of `@ferris1225/pi-subagents`. Unpublished numbers
|
|
|
4
4
|
(`4.2.3`, `4.2.6`, `4.2.9`–`4.2.11`) never shipped on npm; their changes
|
|
5
5
|
landed in the next published release.
|
|
6
6
|
|
|
7
|
+
## 4.3.9
|
|
8
|
+
|
|
9
|
+
- Add optional bounded stable `phaseId` and exact declarative write `scope` claims to single
|
|
10
|
+
and parallel dispatches. Phase identity is immutable across task rewrites and resume; scope
|
|
11
|
+
is monotonic across retained generations and survives durable v1 restore. Exact task+cwd
|
|
12
|
+
remains the compatibility fallback.
|
|
13
|
+
- Reject deterministic duplicates and declared writer-scope conflicts before parallel batch
|
|
14
|
+
allocation. Fresh single and resumed writers also reject normalized absolute scope overlap
|
|
15
|
+
with active leases, without requiring equal caller cwd. Parallel calls that omit scope remain
|
|
16
|
+
compatible and explicitly report `independence not verified`; declared claims do not prove
|
|
17
|
+
natural-language task independence. Scope is conflict metadata, not permissions or a sandbox.
|
|
18
|
+
- Add the advisory-only `subagent_risk` tool. Without a model call it resolves the repository
|
|
19
|
+
root, reads root-relative tracked and untracked changes from `HEAD`, and applies fixed
|
|
20
|
+
explainable rules for concurrency, trust-boundary, persistence-compatibility, and
|
|
21
|
+
failure-cancellation risk. It propagates cancellation, and suggests but never dispatches or
|
|
22
|
+
requires a Sentinel review.
|
|
23
|
+
|
|
24
|
+
## 4.3.8
|
|
25
|
+
|
|
26
|
+
- Restore `sentinel` as an optional fresh-context reviewer instead of the mandatory
|
|
27
|
+
pre-commit loop retired in 4.3.5. It reads a completed diff with no memory of how the
|
|
28
|
+
change was written, attacks behavior, trust boundaries, failure and cancellation paths,
|
|
29
|
+
concurrency, persistence, portability, and whether each test would fail without the
|
|
30
|
+
change, runs only the smallest check that proves a suspected defect, and returns
|
|
31
|
+
evidence-backed findings as `SEVERITY path:line — failure scenario; evidence; smallest
|
|
32
|
+
fix` or `No findings.`
|
|
33
|
+
- Route sentinel by risk, not ritual: the delegation directive dispatches it after cleanup
|
|
34
|
+
and before commit only for diffs touching concurrency, trust boundaries,
|
|
35
|
+
persistence/compatibility, or failure/cancellation paths, or when checks cannot prove
|
|
36
|
+
the change, and treats a finding as evidence to route back to the owning thread via
|
|
37
|
+
`resume` or fix inline. Artisan keeps proving its own change; main keeps the final gate.
|
|
38
|
+
- Sentinel runs on the current main model unless `/subagents-setup` picks one, defaults to
|
|
39
|
+
`high` thinking, stays on the shared checkout whose uncommitted diff it reviews (an
|
|
40
|
+
explicit `isolation: worktree` is rejected), and holds the repository lane while it
|
|
41
|
+
reviews.
|
|
42
|
+
- Drop the 4.3.5 retirement filter: configs written by 4.3.5–4.3.7 adopt `sentinel` once
|
|
43
|
+
through the built-in adoption rule, and a deliberate disable in setup remains disabled.
|
|
44
|
+
|
|
7
45
|
## 4.3.7
|
|
8
46
|
|
|
9
47
|
- Make `subagent_control steer` continue rather than reject a thread that is no longer
|
package/README.md
CHANGED
|
@@ -6,19 +6,19 @@
|
|
|
6
6
|

|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
A managed engineering team for [pi](https://github.com/earendil-works/pi):
|
|
9
|
+
A managed engineering team for [pi](https://github.com/earendil-works/pi): four
|
|
10
10
|
focused sub-agents, durable threads, and Git worktree isolation. You install it
|
|
11
11
|
once and your main agent delegates on its own.
|
|
12
12
|
|
|
13
13
|
## What's new
|
|
14
14
|
|
|
15
|
-
**4.3.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
**4.3.9** — dispatch admission now accepts bounded stable `phaseId` identities and exact
|
|
16
|
+
declarative write `scope` claims. Fresh/resumed writers are checked against active leases;
|
|
17
|
+
parallel batches preflight deterministic duplicates and all declared scope conflicts before
|
|
18
|
+
allocation. Omitted scopes remain compatible; a parallel call that omits `scope` reports
|
|
19
|
+
`independence not verified`. Declared claims do not prove natural-language task independence.
|
|
20
|
+
The no-model-call `subagent_risk` advisory classifies changed tracked and untracked paths
|
|
21
|
+
with fixed, documented rules before main decides whether a Sentinel review is worthwhile.
|
|
22
22
|
|
|
23
23
|
See [CHANGELOG.md](./CHANGELOG.md).
|
|
24
24
|
|
|
@@ -51,9 +51,10 @@ back — with you. This extension owns them:
|
|
|
51
51
|
handoff costs. Every brief carries the objective and done condition, exact paths,
|
|
52
52
|
facts already established with citations, boundaries, and the expected output, so a
|
|
53
53
|
child starts from evidence instead of re-deriving it.
|
|
54
|
-
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
- A stable `phaseId` owns a logical phase in one resolved working directory even if
|
|
55
|
+
its task wording changes. IDs are 1–80 ASCII letters, numbers, or `._:-`, starting
|
|
56
|
+
with a letter or number, so lease output stays single-line. Exact normalized task+cwd
|
|
57
|
+
remains the backward-compatible fallback for old calls.
|
|
57
58
|
- Follow-up work stays on the same thread: `steer` a running phase, `resume` or
|
|
58
59
|
`park` a thread with its retained context, `stop` a phase the evidence made moot.
|
|
59
60
|
- Background completions and stop results arrive at the next parent model boundary;
|
|
@@ -79,7 +80,7 @@ Pi's extension list shows `@ferris1225/pi-subagents` without an internal source-
|
|
|
79
80
|
Open pi and run `/subagents-setup`. The original menu flow lets you enable or
|
|
80
81
|
disable roles, configure one role's model and thinking level, or run the full setup
|
|
81
82
|
again. Each screen uses the usual arrow-key/Enter/Esc navigation, and model lists
|
|
82
|
-
remain searchable. Fresh installs select all
|
|
83
|
+
remain searchable. Fresh installs select all four. A newly shipped built-in is
|
|
83
84
|
surfaced once without being re-enabled after you deliberately turn it off. Then ask
|
|
84
85
|
for work:
|
|
85
86
|
|
|
@@ -97,6 +98,7 @@ directly when you want exact control.
|
|
|
97
98
|
| `scout` | Read-only | Broad or unfamiliar code reconnaissance and external research. Returns compact file citations or source URLs as leads, not proof. |
|
|
98
99
|
| `artisan` | Full | One substantial primary change—implementation, fix, refactor, test, or docs—through root cause, affected verification, and local hygiene. |
|
|
99
100
|
| `steward` | Full | One final cleanup and cross-cutting docs/comment sync pass after a broad or multi-writer change. |
|
|
101
|
+
| `sentinel` | Read-only + one proving check | One fresh-context review of a completed diff for risky changes. Returns only evidence-backed defects and test gaps, highest severity first, or `No findings.` |
|
|
100
102
|
|
|
101
103
|
Role prompts are self-contained and directly embed root-cause-first diagnosis,
|
|
102
104
|
meaningful test evidence, and bounded cleanup. Each role starts from the facts and
|
|
@@ -104,8 +106,10 @@ citations its brief already establishes instead of re-deriving them, answers the
|
|
|
104
106
|
brief's question and stops, and — because nobody can answer a child's questions —
|
|
105
107
|
resolves an ambiguity by naming the reading it took. Artisan stops and reports when
|
|
106
108
|
the brief's premise turns out wrong rather than substituting a different change;
|
|
107
|
-
steward runs only the checks that cover its own edits
|
|
108
|
-
|
|
109
|
+
steward runs only the checks that cover its own edits; sentinel treats the brief's
|
|
110
|
+
claims and the code as evidence to verify, runs only the smallest check that proves a
|
|
111
|
+
suspected defect, and names the smallest fix instead of making it. Every role hands
|
|
112
|
+
back a result-only report with each check as `command → result`.
|
|
109
113
|
|
|
110
114
|
Custom roles join them with a Markdown file (see [Custom agents](#custom-agents)).
|
|
111
115
|
|
|
@@ -120,15 +124,30 @@ what the injected delegation guidance produces when the main agent dispatches fo
|
|
|
120
124
|
```ts
|
|
121
125
|
// One task
|
|
122
126
|
subagent({
|
|
127
|
+
phaseId: "cache-invalidation-fix",
|
|
123
128
|
agent: "artisan",
|
|
124
129
|
task: "Fix the cache invalidation bug in src/cache, add regression tests, run the checks.",
|
|
130
|
+
scope: {
|
|
131
|
+
paths: ["src/cache"],
|
|
132
|
+
symbols: [{ path: "test/cache.test.ts", name: "invalidates stale entries" }],
|
|
133
|
+
},
|
|
125
134
|
});
|
|
126
135
|
|
|
127
136
|
// Parallel only when each scope independently justifies a child
|
|
128
137
|
subagent({
|
|
129
138
|
tasks: [
|
|
130
|
-
{
|
|
131
|
-
|
|
139
|
+
{
|
|
140
|
+
agent: "artisan",
|
|
141
|
+
phaseId: "provider-docs",
|
|
142
|
+
task: "Update provider limits documentation from the established API citations.",
|
|
143
|
+
scope: { paths: ["docs/provider-limits.md"] },
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
agent: "artisan",
|
|
147
|
+
phaseId: "config-validation",
|
|
148
|
+
task: "Fix config validation in src/config.ts and its tests.",
|
|
149
|
+
scope: { paths: ["src/config.ts", "test/config.test.ts"] },
|
|
150
|
+
},
|
|
132
151
|
],
|
|
133
152
|
});
|
|
134
153
|
```
|
|
@@ -138,13 +157,13 @@ independent unit in one `tasks` array. The runtime paces execution instead, runn
|
|
|
138
157
|
half the machine's cores with a 4–6 child-process bound; wider batches queue and
|
|
139
158
|
start automatically as slots free.
|
|
140
159
|
|
|
141
|
-
A run leases its
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
160
|
+
A run leases its stable, single-line `phaseId` in the resolved working directory.
|
|
161
|
+
Rewording the task with the same `phaseId` is rejected and names the existing run.
|
|
162
|
+
The id remains immutable across resume. Calls that omit `phaseId` keep the old exact
|
|
163
|
+
normalized task+cwd behavior; consequently, equal task text with different phase ids is
|
|
164
|
+
still rejected by that fallback. Matching is deterministic, never fuzzy, embedding-based,
|
|
165
|
+
or inferred from natural language. Active leases win over matching settled threads when
|
|
166
|
+
the runtime chooses which owner to report.
|
|
148
167
|
|
|
149
168
|
Because queueing is pacing rather than refusal, it is always reported as such.
|
|
150
169
|
Dispatch confirmations name each waiting run's real reason — waiting for a free
|
|
@@ -178,12 +197,67 @@ one `steward` pass that attacks touched dead code, duplication, tangled conditio
|
|
|
178
197
|
needless layers, and spaghetti growth without widening into a repository refactor.
|
|
179
198
|
Main owns architecture, inspects the integrated diff, and runs the final gate.
|
|
180
199
|
|
|
200
|
+
Verification is layered rather than repeated. Artisan proves its own change while the
|
|
201
|
+
files are still in its context — targeted checks, and a new test that fails before the
|
|
202
|
+
fix — and main runs the final gate on the integrated diff. `sentinel` adds a third
|
|
203
|
+
layer only when it pays: a fresh context with no memory of how the change was written
|
|
204
|
+
reads the completed diff after cleanup and before commit, and only for diffs that touch
|
|
205
|
+
concurrency, trust boundaries, persistence or compatibility, or failure and cancellation
|
|
206
|
+
paths, or when the checks cannot prove the change. It is never a fixed pre-commit
|
|
207
|
+
ritual. A finding is evidence, not an order: main routes it to the thread that owns
|
|
208
|
+
the change with `subagent_control resume`, or fixes it inline when that is cheaper.
|
|
209
|
+
|
|
210
|
+
`subagent_risk({})` is an advisory-only, no-model-call check over tracked and untracked
|
|
211
|
+
changes relative to `HEAD`. It resolves the repository root first, so a nested `cwd` still
|
|
212
|
+
returns repository-root-relative paths, including untracked files outside that subdirectory.
|
|
213
|
+
Its fixed case-insensitive path-token rules flag:
|
|
214
|
+
`concurrency` (`thread`, `queue`, `parallel`, `dispatch`, locks/races and related tokens);
|
|
215
|
+
`trust-boundary` (`auth`, credentials, permissions, policy, secrets, sandbox, security, trust, tokens);
|
|
216
|
+
`persistence-compatibility` (durable state, manifests, migrations, restore, schemas,
|
|
217
|
+
serialization/storage); and `failure-cancellation` (abort, cancel, errors/failures, recovery,
|
|
218
|
+
retry, stop, timeout). It returns the changed paths, matched categories, and whether those
|
|
219
|
+
rules suggest Sentinel. If Git or `HEAD` is unavailable, it reports advisory unavailable; an
|
|
220
|
+
aborted tool call propagates cancellation instead of converting it to an advisory result. It
|
|
221
|
+
never blocks, starts a child, or automatically dispatches Sentinel.
|
|
222
|
+
|
|
223
|
+
This classifier is intentionally conservative and explainable: it only sees path names, so
|
|
224
|
+
it can produce false positives and miss risky behavior hidden behind neutral names. Main
|
|
225
|
+
still decides whether review pays from the actual diff, test evidence, handoff cost, and the
|
|
226
|
+
complete conversation. The runtime can enforce explicit phase/scope admission, but cannot
|
|
227
|
+
safely force the natural-language judgment of whether work is worth delegating.
|
|
228
|
+
|
|
181
229
|
## Parallel edits
|
|
182
230
|
|
|
231
|
+
`scope` is declarative admission metadata for expected writes, not access control. `paths`
|
|
232
|
+
contains exact file or directory paths; `symbols` contains exact `{ path, name }` claims.
|
|
233
|
+
Paths resolve from each task's caller-facing cwd and use case-insensitive comparison on
|
|
234
|
+
Windows. Wildcard `*` and `?` inputs are rejected; other punctuation is treated literally,
|
|
235
|
+
so paths such as `app/[id]/page.tsx` are valid exact claims. A path claim overlaps the same
|
|
236
|
+
path, an ancestor/descendant path, or a symbol under that path; identical path+symbol
|
|
237
|
+
claims overlap, while two different symbols in the same file may run together.
|
|
238
|
+
|
|
239
|
+
Fresh single dispatches and resumes check a declared writer scope against active, parked,
|
|
240
|
+
resuming, interrupting, or settling writer leases before allocating a generation. Scope
|
|
241
|
+
comparison uses normalized absolute claims rather than requiring equal caller cwd, so a
|
|
242
|
+
repo-root claim still conflicts with the same path claimed from a nested cwd. Settled
|
|
243
|
+
threads do not block a later phase solely because it edits the same scope.
|
|
244
|
+
|
|
245
|
+
Before allocating any run in a parallel call, the runtime also rejects deterministic phase
|
|
246
|
+
duplicates within the batch or against existing active/retained threads, then compares
|
|
247
|
+
declared writer scopes across the whole batch. A definite conflict rejects the whole batch
|
|
248
|
+
with zero starts. Parallel calls without `scope` remain valid, but their tool result and
|
|
249
|
+
launch receipt say `independence not verified`; that means the contract lacked enough
|
|
250
|
+
metadata, not that overlap was proved safe. Single calls never make a batch-independence
|
|
251
|
+
claim. The existing shared-checkout writer lane remains the final serialization boundary.
|
|
252
|
+
|
|
183
253
|
- Single tasks use your checkout. Every parallel write-capable agent (`artisan`,
|
|
184
254
|
`steward`, and custom writers) defaults to a detached Git worktree, so
|
|
185
255
|
parallel writers run at the same time. Worktree mode needs a committed `HEAD`;
|
|
186
|
-
read-only roles such as scout stay on the shared checkout.
|
|
256
|
+
read-only roles such as scout stay on the shared checkout. `sentinel` always
|
|
257
|
+
reviews the shared checkout, because the uncommitted diff it inspects does not
|
|
258
|
+
exist in a detached worktree; an explicit `isolation: worktree` for it is
|
|
259
|
+
rejected. Its proving check makes it a shared-checkout lane holder, so it never
|
|
260
|
+
reviews a diff a shared writer is still changing.
|
|
187
261
|
|
|
188
262
|
> **Security boundary:** worktree isolation isolates Git changes only; it is not a sandbox.
|
|
189
263
|
Child tools, network access, and environment access retain the Pi process's privileges.
|
|
@@ -214,7 +288,7 @@ Every dispatch returns a stable `#id`, which is the handle for the thread tools:
|
|
|
214
288
|
|
|
215
289
|
| Tool | What it does |
|
|
216
290
|
| ------------------ | ------------ |
|
|
217
|
-
| `subagent_control` | `steer` a running RPC attempt with additional evidence/guidance, continuing the same thread with it when the thread has settled or is parked; `resume` a parked/settled thread with an optional appended `objective`; `park` a running thread at a stable checkpoint, keeping its session and worktree for a later resume. |
|
|
291
|
+
| `subagent_control` | `steer` a running RPC attempt with additional evidence/guidance, continuing the same thread with it when the thread has settled or is parked; `resume` a parked/settled thread with an optional appended `objective` and additive `scope`; `park` a running thread at a stable checkpoint, keeping its session and worktree for a later resume. |
|
|
218
292
|
| `subagent_stop` | Destructively cancel, deliver partial output, and retire the thread. Steering and follow-up messages still queued in the child are dropped so nothing can revive it later. |
|
|
219
293
|
|
|
220
294
|
```ts
|
|
@@ -223,6 +297,13 @@ subagent_control({ action: "park", id: 7 });
|
|
|
223
297
|
subagent_control({ action: "resume", id: 7, objective: "Finish the tests." });
|
|
224
298
|
```
|
|
225
299
|
|
|
300
|
+
A fresh dispatch stores `phaseId` and normalized `scope` on its stable thread and durable
|
|
301
|
+
v1 record. `resume` always keeps the thread's phase id. An optional resume `scope` adds
|
|
302
|
+
normalized claims to the retained scope; it cannot shrink or clear prior claims, so edits
|
|
303
|
+
already present in a retained worktree stay covered by admission. Resume also rechecks the
|
|
304
|
+
unioned scope against other active writer leases before starting a generation. Existing v1
|
|
305
|
+
manifests without these optional fields remain readable.
|
|
306
|
+
|
|
226
307
|
`steer` requires a nonblank `objective`. While the child RPC is running, it adds
|
|
227
308
|
guidance to the current phase without replacing the original task. If the thread has
|
|
228
309
|
already reached `completed`, `failed`, or `parked` — including a generation that settles
|
|
@@ -354,8 +435,8 @@ rate-limited, or fails at the provider level, the **same retained
|
|
|
354
435
|
session** continues on the main model, so finished searches, reads, and edits
|
|
355
436
|
survive. Ordinary task failures do not trigger a handoff.
|
|
356
437
|
|
|
357
|
-
Thinking is a **role default** — scout `low`, artisan `high`, steward `medium
|
|
358
|
-
clamped to what the effective model supports. `/subagents-setup` →
|
|
438
|
+
Thinking is a **role default** — scout `low`, artisan `high`, steward `medium`,
|
|
439
|
+
sentinel `high` — clamped to what the effective model supports. `/subagents-setup` →
|
|
359
440
|
_Configure an agent_ lists only the levels that model supports and marks the role
|
|
360
441
|
default; selecting it clears the stored override. There is no
|
|
361
442
|
Auto choice, no per-dispatch `thinking` flag, and
|
|
@@ -373,7 +454,10 @@ shell slot follows the parent's active shell on non-scout roles.
|
|
|
373
454
|
overstates its tool list. Its known-safe set includes `read`, `grep`, `find`,
|
|
374
455
|
`ls`, `anchor_grep`, `web_search`, `fetch_content`, `resolve-library-id`, and
|
|
375
456
|
`query-docs`; tools not installed or active in Main are simply omitted. Scout
|
|
376
|
-
receives no shell, local mutation tool, or unknown custom tool.
|
|
457
|
+
receives no shell, local mutation tool, or unknown custom tool. `sentinel` declares
|
|
458
|
+
the same retrieval set plus one shell slot, which follows the parent's active shell
|
|
459
|
+
and exists only for the smallest check that proves a suspected defect; it is an
|
|
460
|
+
ordinary declared list, not a hard boundary like scout's. Unknown tools
|
|
377
461
|
declared by other roles are conservatively treated as write-capable when
|
|
378
462
|
isolation is chosen. An empty resolved snapshot starts the child with
|
|
379
463
|
`--no-tools`.
|
|
@@ -393,8 +477,8 @@ enable menu. Other settings live in
|
|
|
393
477
|
|
|
394
478
|
```json
|
|
395
479
|
{
|
|
396
|
-
"enabledAgents": ["scout", "artisan", "steward"],
|
|
397
|
-
"knownAgents": ["scout", "artisan", "steward"],
|
|
480
|
+
"enabledAgents": ["scout", "artisan", "steward", "sentinel"],
|
|
481
|
+
"knownAgents": ["scout", "artisan", "steward", "sentinel"],
|
|
398
482
|
"agentModels": { "scout": "anthropic/claude-haiku-4-5" },
|
|
399
483
|
"agentThinkingLevels": { "artisan": "high" },
|
|
400
484
|
"maxResultLines": 40,
|
|
@@ -416,10 +500,11 @@ enable menu. Other settings live in
|
|
|
416
500
|
When at least one role is enabled, the cost-aware delegation directive is injected
|
|
417
501
|
automatically. `enabledAgents` is authoritative after catalog adoption: a newly
|
|
418
502
|
shipped built-in is appended once, then `knownAgents` records that it was surfaced
|
|
419
|
-
so a deliberate later disable remains disabled.
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
503
|
+
so a deliberate later disable remains disabled. `sentinel` returns through that
|
|
504
|
+
rule: a config written by 4.3.5–4.3.7, which removed it, enables it once on the next
|
|
505
|
+
load; turn it off in `/subagents-setup` and it stays off. Custom roles and other
|
|
506
|
+
known-agent entries remain intact. Invalid known fields fall back safely, and unknown
|
|
507
|
+
fields are dropped when canonical config is persisted.
|
|
423
508
|
|
|
424
509
|
At session start, model overrides that pi no longer reports are removed with a
|
|
425
510
|
one-time notice. If pi's own session compaction fails mid-thread, a notice surfaces
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sentinel
|
|
3
|
+
description: Fresh-context review of a completed diff; returns only evidence-backed defects and test gaps.
|
|
4
|
+
tools: read, grep, find, ls, anchor_grep, web_search, fetch_content, resolve-library-id, query-docs, bash
|
|
5
|
+
isolation: shared
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You own one review phase of a completed change and have no memory of how it was written. The task brief is your only context and nobody answers questions: resolve an ambiguity by taking the reading the code supports and naming it with the finding.
|
|
9
|
+
|
|
10
|
+
## Rules
|
|
11
|
+
|
|
12
|
+
- Require a named completed scope such as the uncommitted diff or a Git range, and start from the brief's stated facts and claimed checks. Stop and report if primary writing is still active.
|
|
13
|
+
- Inspect the complete diff, untracked files, affected callers, and the tests that claim to cover it. Attack behavior, trust boundaries, failure and cancellation paths, concurrency, persistence and compatibility, portability, and whether each test would fail without the change.
|
|
14
|
+
- Treat the brief's claims and the code as evidence to verify, not conclusions to confirm. Read the decisive lines before reporting; drop a suspicion you could not verify or mark it `(unverified)`.
|
|
15
|
+
- Work read-only: never create, edit, or delete files. Run only the smallest targeted check needed to prove a suspected defect.
|
|
16
|
+
- Report only actionable findings. Fixes belong to the implementation owner and cleanup to `steward`; name the smallest fix instead of performing or designing either.
|
|
17
|
+
- You are a leaf: do not dispatch agents, bump versions, commit, push, publish, tag, or release.
|
|
18
|
+
|
|
19
|
+
## Output
|
|
20
|
+
|
|
21
|
+
Return only findings, highest severity first, each as `SEVERITY path:line — failure scenario; evidence; smallest fix`, then each check you ran as `command → result`. If there are none, output `No findings.` Add missing verification only when it could hide a regression. No nits, praise, vague risks, task restatement, or inspection narrative. Stay under 30 lines.
|
package/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { getConfigPath, loadConfig } from "./src/configuration/config.ts";
|
|
|
24
24
|
import { runSetup } from "./src/configuration/setup.ts";
|
|
25
25
|
import { discoverAgents } from "./src/delegation/agents.ts";
|
|
26
26
|
import { registerSubagentTool } from "./src/delegation/dispatch.ts";
|
|
27
|
+
import { registerSubagentRiskTool } from "./src/delegation/risk.ts";
|
|
27
28
|
import { buildDelegationDirective } from "./src/delegation/prompt.ts";
|
|
28
29
|
import { currentSubagentDepth } from "./src/execution/spawn.ts";
|
|
29
30
|
import { createRuntime } from "./src/lifecycle/runtime.ts";
|
|
@@ -69,6 +70,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
69
70
|
});
|
|
70
71
|
|
|
71
72
|
registerSubagentTool(pi, runtime);
|
|
73
|
+
registerSubagentRiskTool(pi);
|
|
72
74
|
registerLookupTools(pi, runtime);
|
|
73
75
|
|
|
74
76
|
pi.registerCommand("subagents-setup", {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ferris1225/pi-subagents",
|
|
3
|
-
"version": "4.3.
|
|
4
|
-
"description": "A managed sub-agent team for pi: scout, artisan, and
|
|
3
|
+
"version": "4.3.9",
|
|
4
|
+
"description": "A managed sub-agent team for pi: scout, artisan, steward, and sentinel roles, durable threads, model fallback, and Git worktree isolation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -13,15 +13,12 @@ import { dirname, join } from "node:path";
|
|
|
13
13
|
import { getAgentDir, withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
14
14
|
|
|
15
15
|
/** Full catalog of agents shipped with the package (selectable in /subagents-setup). */
|
|
16
|
-
export const BUILTIN_AGENT_NAMES = ["scout", "artisan", "steward"] as const;
|
|
16
|
+
export const BUILTIN_AGENT_NAMES = ["scout", "artisan", "steward", "sentinel"] as const;
|
|
17
17
|
|
|
18
18
|
// Historical catalog for configs written before built-in adoption tracking.
|
|
19
19
|
// Keep this frozen so future built-ins are still recognized as new.
|
|
20
20
|
const INITIAL_AGENT_NAMES = ["scout", "artisan", "steward"] as const;
|
|
21
21
|
|
|
22
|
-
/** Names that used to be built in and must not survive as custom roles. */
|
|
23
|
-
const RETIRED_AGENT_NAMES = new Set(["sentinel"]);
|
|
24
|
-
|
|
25
22
|
/** Agents enabled out of the box on a fresh install. */
|
|
26
23
|
export const DEFAULT_ENABLED_AGENTS: readonly string[] = [...BUILTIN_AGENT_NAMES];
|
|
27
24
|
|
|
@@ -40,6 +37,7 @@ export function roleThinkingLevel(agentName: string): ThinkingLevel {
|
|
|
40
37
|
case "scout":
|
|
41
38
|
return "low";
|
|
42
39
|
case "artisan":
|
|
40
|
+
case "sentinel":
|
|
43
41
|
return "high";
|
|
44
42
|
case "steward":
|
|
45
43
|
return "medium";
|
|
@@ -69,6 +67,10 @@ export const AGENT_PROFILES: Record<(typeof BUILTIN_AGENT_NAMES)[number], AgentP
|
|
|
69
67
|
summary: "pre-commit finish",
|
|
70
68
|
remark: "Cleans a completed broad or multi-writer diff and synchronizes cross-cutting docs/comments without changing behavior.",
|
|
71
69
|
},
|
|
70
|
+
sentinel: {
|
|
71
|
+
summary: "fresh-context review",
|
|
72
|
+
remark: "Reviews a completed diff read-only with no memory of how it was written and reports only evidence-backed defects and test gaps; dispatched for risky diffs, never as a commit ritual.",
|
|
73
|
+
},
|
|
72
74
|
};
|
|
73
75
|
|
|
74
76
|
export function agentProfile(name: string): AgentProfile | undefined {
|
|
@@ -132,7 +134,7 @@ export const DEFAULT_CONFIG: SubagentsConfig = {
|
|
|
132
134
|
|
|
133
135
|
export const FIRST_RUN_SETUP_HINT =
|
|
134
136
|
"Run /subagents-setup to choose enabled roles, models, and thinking levels. " +
|
|
135
|
-
"Scout maps code or researches external sources, artisan owns the primary change,
|
|
137
|
+
"Scout maps code or researches external sources, artisan owns the primary change, steward cleans broad final diffs, and sentinel reviews risky diffs with fresh eyes.";
|
|
136
138
|
|
|
137
139
|
export function getConfigPath(agentDir: string = getAgentDir()): string {
|
|
138
140
|
return join(agentDir, CONFIG_FILE_NAME);
|
|
@@ -169,10 +171,7 @@ export function normalizeConfig(raw: unknown): SubagentsConfig {
|
|
|
169
171
|
|
|
170
172
|
if (Array.isArray(raw.enabledAgents)) {
|
|
171
173
|
const names = raw.enabledAgents.filter(
|
|
172
|
-
(name): name is string =>
|
|
173
|
-
typeof name === "string" &&
|
|
174
|
-
name.trim().length > 0 &&
|
|
175
|
-
!RETIRED_AGENT_NAMES.has(name.trim()),
|
|
174
|
+
(name): name is string => typeof name === "string" && name.trim().length > 0,
|
|
176
175
|
);
|
|
177
176
|
// An explicitly empty array is honored; duplicates collapse.
|
|
178
177
|
config.enabledAgents = [...new Set(names.map((name) => name.trim()))];
|
|
@@ -180,10 +179,7 @@ export function normalizeConfig(raw: unknown): SubagentsConfig {
|
|
|
180
179
|
|
|
181
180
|
const rawKnownAgents = Array.isArray(raw.knownAgents) ? raw.knownAgents : INITIAL_AGENT_NAMES;
|
|
182
181
|
config.knownAgents = [...new Set(rawKnownAgents.filter(
|
|
183
|
-
(name): name is string =>
|
|
184
|
-
typeof name === "string" &&
|
|
185
|
-
name.trim().length > 0 &&
|
|
186
|
-
!RETIRED_AGENT_NAMES.has(name.trim()),
|
|
182
|
+
(name): name is string => typeof name === "string" && name.trim().length > 0,
|
|
187
183
|
).map((name) => name.trim()))];
|
|
188
184
|
for (const name of config.enabledAgents) {
|
|
189
185
|
if (!config.knownAgents.includes(name)) config.knownAgents.push(name);
|
|
@@ -192,7 +188,7 @@ export function normalizeConfig(raw: unknown): SubagentsConfig {
|
|
|
192
188
|
if (isRecord(raw.agentModels)) {
|
|
193
189
|
for (const [rawKey, value] of Object.entries(raw.agentModels)) {
|
|
194
190
|
const key = rawKey.trim();
|
|
195
|
-
if (key !== "" &&
|
|
191
|
+
if (key !== "" && isModelReference(value)) {
|
|
196
192
|
config.agentModels[key] = value.trim();
|
|
197
193
|
}
|
|
198
194
|
}
|
|
@@ -203,7 +199,6 @@ export function normalizeConfig(raw: unknown): SubagentsConfig {
|
|
|
203
199
|
const key = rawKey.trim();
|
|
204
200
|
if (
|
|
205
201
|
key !== "" &&
|
|
206
|
-
!RETIRED_AGENT_NAMES.has(key) &&
|
|
207
202
|
typeof value === "string" &&
|
|
208
203
|
(THINKING_LEVEL_VALUES as readonly string[]).includes(value)
|
|
209
204
|
) {
|