@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 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
  ![platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey)
7
7
  ![pi](https://img.shields.io/badge/pi-extension-orange)
8
8
 
9
- A managed engineering team for [pi](https://github.com/earendil-works/pi): three
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.7** — never pay for the same phase twice: the delegation directive now carries a
16
- full brief contract and effort scaling, `steer` continues a settled or parked thread with
17
- its guidance instead of rejecting it, `subagent_control park` pauses a running thread at
18
- a stable checkpoint for a later resume, an exact re-run of a finished brief is rejected in
19
- favor of resuming its retained context, and resumed children re-read files the workspace
20
- may have changed underneath them. Role prompts start from the brief's evidence and stop
21
- at its done condition.
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
- - One normalized task and working directory owns its phase: an exact duplicate of an
55
- active run is rejected, and an exact re-run of a finished brief with retained
56
- context is rejected in favor of resuming it, so the same work is never bought twice.
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 three. A newly shipped built-in is
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. Every role hands back a
108
- result-only report with each check as `command → result`.
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
- { agent: "scout", task: "Research current provider API limits in primary sources and cite URLs." },
131
- { agent: "artisan", task: "Fix config validation in src/config.ts and its tests." },
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 normalized task and resolved working directory across agent
142
- names. Dispatching the same pair again while the run is active is rejected and
143
- names the existing run id. Once the run has finished in this session and still
144
- holds its retained session, the same pair is rejected too, pointing at
145
- `subagent_control resume` the thread that already did the work continues for a
146
- fraction of a fresh run or at restating the brief with what changed. Matching is
147
- exact, never fuzzy.
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. Unknown tools
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. On upgrade, the retired `sentinel`
420
- entry is removed from enabled and known agents plus model and thinking overrides;
421
- all other custom roles and known-agent entries remain intact. Invalid known fields
422
- fall back safely, and unknown fields are dropped when canonical config is persisted.
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.7",
4
- "description": "A managed sub-agent team for pi: scout, artisan, and steward roles, durable threads, model fallback, and Git worktree isolation.",
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, and steward cleans broad final diffs.";
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 !== "" && !RETIRED_AGENT_NAMES.has(key) && isModelReference(value)) {
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
  ) {
@@ -54,6 +54,7 @@ export const SUBAGENT_TOOL_NAMES = [
54
54
  "subagent",
55
55
  "subagent_control",
56
56
  "subagent_stop",
57
+ "subagent_risk",
57
58
  ] as const;
58
59
  const SUBAGENT_TOOL_NAME_SET = new Set<string>(SUBAGENT_TOOL_NAMES);
59
60