@ferris1225/pi-subagents 2.3.1 → 4.0.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/src/dispatch.ts CHANGED
@@ -1,11 +1,8 @@
1
1
  /**
2
- * The `subagent` tool: dispatches explore/worker/cleaner/reviewer agents as isolated pi
2
+ * The `subagent` tool: dispatches explorer/worker/cleaner/reviewer agents as isolated pi
3
3
  * child processes, single or parallel. Owns the public dispatch contract,
4
4
  * per-run status tracking, the auto-fix chain (REVIEW_FAIL → worker → re-review),
5
5
  * and completion delivery. Stable thread generations live in thread-lifecycle.ts.
6
- *
7
- * Vision: a task flagged `vision: true` uses the configured vision model, then
8
- * hands directly to the current main-window model on model/provider failure.
9
6
  */
10
7
 
11
8
  import { StringEnum } from "@earendil-works/pi-ai";
@@ -58,9 +55,6 @@ export { FORK_CONTINUATION_PROMPT, isWorktreeCapableAgent } from "./thread-lifec
58
55
 
59
56
  const NON_BLANK_TASK_OPTIONS = { minLength: 1, pattern: "\\S" } as const;
60
57
 
61
- const VISION_DESCRIPTION =
62
- "Set true when the task may require viewing images (screenshots, mockups, designs) — the configured vision model is used first, then model-level failures hand directly to the current main-window model. A task that names an image file or describes frontend/UI work (Vue/React, mockups, screenshots, page styling) is dispatched as vision automatically";
63
-
64
58
  const ISOLATION_DESCRIPTION =
65
59
  "Filesystem isolation: shared uses the caller's working tree; worktree creates a detached temporary Git worktree (write-capable agents, including worker and cleaner, only)";
66
60
 
@@ -75,7 +69,6 @@ const TaskItem = Type.Object({
75
69
  description: "Self-contained task to delegate (the agent has no memory of this conversation)",
76
70
  }),
77
71
  cwd: Type.Optional(Type.String({ description: "Working directory for the agent process" })),
78
- vision: Type.Optional(Type.Boolean({ description: VISION_DESCRIPTION })),
79
72
  isolation: IsolationSchema,
80
73
  });
81
74
 
@@ -86,7 +79,6 @@ const SubagentParams = Type.Object({
86
79
  ),
87
80
  tasks: Type.Optional(Type.Array(TaskItem, { description: "Array of {agent, task} for parallel execution" })),
88
81
  cwd: Type.Optional(Type.String({ description: "Working directory for the agent process (single mode)" })),
89
- vision: Type.Optional(Type.Boolean({ description: VISION_DESCRIPTION })),
90
82
  isolation: IsolationSchema,
91
83
  });
92
84
 
@@ -95,27 +87,6 @@ export function defaultIsolationMode(mode: "single" | "parallel", agentName: str
95
87
  return mode === "parallel" && agentName === "worker" ? "worktree" : "shared";
96
88
  }
97
89
 
98
- const IMAGE_FILE_PATTERN = /\.(?:png|jpe?g|webp|gif|bmp|tiff?|avif)\b/i;
99
- /** Frontend/UI work: the agent may screenshot and visually verify what it
100
- * builds, and the model cannot be swapped mid-run — vision must be on at
101
- * spawn. Only language-neutral signals: code terms and file names stay in
102
- * English even in non-English briefs; purely semantic wording is the LLM's
103
- * job via the vision flag, not a per-language keyword list. Word boundaries
104
- * keep ordinary words (build, guide, reactor) from matching. */
105
- const FRONTEND_PATTERN = /\b(?:vue|react|svelte|angular|nuxt|next\.?js|vite|tailwind|screenshots?|mockups?|wireframes?|UI)\b/i;
106
- const FRONTEND_FILE_PATTERN = /\.(?:vue|html?|css|svg)\b/i;
107
-
108
- /** Deterministic vision fallback: a brief that names an image file or describes
109
- * frontend/UI work is vision work even when the dispatcher forgot the flag —
110
- * prompting alone cannot guarantee `vision: true`, and a non-vision model can
111
- * neither see reference images nor visually verify what it builds. */
112
- export function taskImpliesVision(task: string): boolean {
113
- return IMAGE_FILE_PATTERN.test(task) || FRONTEND_FILE_PATTERN.test(task) || FRONTEND_PATTERN.test(task);
114
- }
115
-
116
- const wantsVision = (flag: boolean | undefined, task: string): boolean =>
117
- flag === true || taskImpliesVision(task);
118
-
119
90
  const autoFixRootTails = new Map<string, Promise<void>>();
120
91
 
121
92
  async function canonicalAutoFixRoot(cwd: string): Promise<string> {
@@ -163,34 +134,15 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
163
134
  name: "subagent",
164
135
  label: "Subagent",
165
136
  description: [
166
- "Delegate a discrete, self-contained task to a specialized sub-agent running in an ISOLATED context window.",
167
- "Built-in roles (only configured enabled agents can dispatch): explore (read-only codebase recon), worker (implement/fix/refactor/test, full tools), cleaner (explicit evidence-first cleanup, full/write tools), reviewer (adversarial pre-commit gate, read-only).",
168
- "When cleaner is enabled, route explicit cleanup intent in any language (for example dead code, redundancy, simplification, or over-engineering), including a requested periodic cleanup pass. Audit/find/inspect/report is read-only evidence, while explicit remove/clean/simplify/refactor permits verified edits. Generic code review goes to reviewer. Never route cleaner by PR count or as the pre-commit gate; reviewer reviews its edits.",
169
- "Modes: single ({agent, task}) or parallel ({tasks: [{agent, task}, ...]}).",
170
- "Isolation: single tasks default to shared; parallel worker tasks default to detached Git worktrees unless isolation: shared is explicit. Cleaner is write-capable and can opt into worktree isolation; explore/reviewer cannot use it.",
171
- "Use subagent_control to steer, retarget, park, resume, or fork a thread by its stable run id.",
172
- "It starts agents in the background and immediately returns control to the main window; completion messages automatically wake the main agent to continue.",
173
- "Each agent has no memory of this conversation — brief it fully (goal, exact paths, constraints, expected output).",
174
- "Results arrive as wake-up messages automatically — you do NOT need to wait. If you must get a result in-turn, subagent_wait is a non-blocking lookup by default (pass timeoutMs to block).",
175
- "Vision: set vision: true when the task may require viewing images (screenshots, mockups, design files — e.g. frontend work) — the configured vision model is used first, then model-level failures hand directly to the current main-window model.",
137
+ "Dispatch enabled specialized agents as isolated leaf Pi child processes, singly or in parallel.",
138
+ "Built-ins: explorer for broad read-only reconnaissance; worker for implementation; cleaner only for explicitly authorized cleanup/removal/simplification edits; reviewer for generic read-only assessments and pre-commit gates.",
139
+ "Work starts in the background; completion automatically resumes the main agent and is already shown to the user, so do not poll or restate it. Give each child a self-contained brief because it has no conversation memory.",
140
+ "Single tasks default to shared; parallel workers default to detached Git worktrees. Only write-capable agents can use worktree isolation, and failures never fall back silently to shared.",
141
+ "A selected-model or provider failure continues the retained session on the current main model; ordinary tool/task failures do not.",
142
+ "Use subagent_control to steer, retarget, park, resume, or fork by stable run id.",
176
143
  ].join(" "),
177
144
  promptSnippet:
178
- "Start background subagents: explore (read-only search), worker (implement), cleaner (explicit evidence-first cleanup), reviewer (pre-commit review); completion automatically resumes the main agent. Simple tasks: use direct tools, not subagents.",
179
- promptGuidelines: [
180
- "Delegate only when an isolated context genuinely pays: broad exploration, a self-contained implementation, explicit evidence-first cleanup, or a review gate. Handle simple lookups and one-line edits inline with direct tools — never spawn a sub-agent for them.",
181
- "Use subagent with agent 'explore' for broad or open-ended code search before large changes; a targeted 'where is X' is a direct grep/read.",
182
- "Treat explore output as a retrieval index, not authority: re-read load-bearing files before editing or deciding deletion, security, compatibility, persistence, or dynamic reachability. The cheapest model can cost more through rework on complex dynamic, concurrent, migration, or security-sensitive code; choose a stronger model or specialist there.",
183
- "Use subagent with agent 'worker' for a self-contained implementation task worth a separate context; it plans internally.",
184
- "When cleaner is enabled, use subagent with agent 'cleaner' only for explicit cleanup intent in any language (for example dead code, redundancy, simplification, or over-engineering) or a requested periodic cleanup pass. Audit/find/inspect/report means read-only ranked evidence; apply only for explicit remove/clean/simplify/refactor wording. Generic code review goes to reviewer. Never trigger cleaner from PR count or as a pre-commit gate; send non-trivial cleaner edits to reviewer.",
185
- "Use subagent with agent 'reviewer' for the fresh read-only gate before reporting non-trivial work done or committing, including after cleaner edits.",
186
- "subagent launches work in the background and ends the current turn; when a result arrives, the main agent is automatically resumed with it.",
187
- "Run independent tasks in parallel by passing a tasks array to subagent; parallel worker items default to isolation: worktree so their edits are integrated independently. Pass isolation: shared only when workers intentionally need the caller's live uncommitted tree.",
188
- "Use isolation: worktree only for worker, cleaner, or another write-capable agent and only inside a Git repository with a committed HEAD; parallel worker tasks default to worktree, while cleaner must opt in. Setup or integration failures never silently fall back to shared.",
189
- "NEVER sleep or poll, and do NOT call subagent_wait to hold the turn — subagent ends the turn immediately and the result arrives as a message that wakes you automatically (even mid-turn). Ending your turn is the default and the only correct way to wait.",
190
- "If you must keep the turn for a result, call subagent_wait with an explicit timeoutMs (non-blocking by default) — never bash sleep/timeout to wait for a sub-agent.",
191
- "When a delegated task may require viewing images (frontend screenshots, mockups, design comparisons), pass vision: true and give the sub-agent the exact image paths — it reads them with its read tool. The configured vision model is used first; model-level failures hand directly to the current main-window model.",
192
- "When a sub-agent result arrives it is already shown to the user — do NOT restate, paraphrase, or summarize it; reply with only your own conclusion or next action (often just one line), since duplicating the result wastes tokens for nothing.",
193
- ],
145
+ "Dispatch isolated background agents: explorer (recon), worker (implementation), cleaner (authorized cleanup), reviewer (read-only assessment/gate); results resume automatically. Use direct tools for trivial work.",
194
146
  parameters: SubagentParams,
195
147
 
196
148
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -320,13 +272,10 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
320
272
  executionCwd: string,
321
273
  signal: AbortSignal,
322
274
  meta: RunChainMeta,
323
- vision = false,
324
275
  ): Promise<{ runId?: number; result: SingleResult }> => {
325
276
  const agent = agents.find((candidate) => candidate.name === agentName);
326
277
  if (!agent) return { result: failedStartResult(agentName, task, `Unknown agent: "${agentName}".`) };
327
- // Vision chains use the vision override first; every model-level failure
328
- // hands directly to the current main model with re-clamped thinking.
329
- const route = resolveDispatchModelRoute(agent, config, ctx, vision);
278
+ const route = resolveDispatchModelRoute(agent, config, ctx);
330
279
  const thinkingLevel = route.thinkingLevel;
331
280
  const runId = monitor.addRun(agent.name, task, route.agent.model, thinkingLevel, meta);
332
281
  const onLive = makeLiveHandler(runId);
@@ -397,7 +346,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
397
346
  parentGroupId: string,
398
347
  parentRunId: number,
399
348
  executionCwd: string,
400
- vision = false,
401
349
  ): void => {
402
350
  const parentThreadAtStart = runtime.threads.get(parentRunId);
403
351
  if (!parentThreadAtStart) return;
@@ -446,7 +394,7 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
446
394
  groupId: parentGroupId,
447
395
  relationLabel: `fix round ${round}`,
448
396
  parentRunId,
449
- }, vision);
397
+ });
450
398
  // Preserve the newest sub-step before checking chain ownership. A
451
399
  // destructive stop invalidates ownsParent() while this child is
452
400
  // aborting, and its partial output must become the parent's stopped
@@ -470,7 +418,7 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
470
418
  groupId: parentGroupId,
471
419
  relationLabel: `re-review round ${round}`,
472
420
  parentRunId,
473
- }, vision);
421
+ });
474
422
  if (
475
423
  runtime.threads.get(parentRunId) === parentThreadAtStart &&
476
424
  parentThreadAtStart.generation === parentGeneration
@@ -649,7 +597,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
649
597
  item.agent,
650
598
  item.task,
651
599
  item.cwd,
652
- wantsVision(item.vision, item.task),
653
600
  defaultIsolationMode("parallel", item.agent, item.isolation as IsolationMode | undefined),
654
601
  ));
655
602
  }
@@ -687,7 +634,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
687
634
  params.agent as string,
688
635
  params.task as string,
689
636
  params.cwd,
690
- wantsVision(params.vision, params.task as string),
691
637
  defaultIsolationMode("single", params.agent as string, params.isolation as IsolationMode | undefined),
692
638
  );
693
639
  if (result.exitCode !== -1) {
package/src/index.ts CHANGED
@@ -69,7 +69,7 @@ export default function (pi: ExtensionAPI): void {
69
69
  registerLookupTools(pi, runtime);
70
70
 
71
71
  pi.registerCommand("subagents-setup", {
72
- description: "Configure pi-subagents: agents, selected models, capability-aware thinking, vision, and runtime settings",
72
+ description: "Configure pi-subagents: agents, selected models, capability-aware thinking, and runtime settings",
73
73
  handler: async (_args, ctx) => {
74
74
  await runSetup(ctx, configPath);
75
75
  },
package/src/models.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * Model routing, capability-aware thinking, and setup-picker helpers.
3
3
  *
4
- * Runtime has one explicit fallback only: a configured agent/vision model hands
4
+ * Runtime has one explicit fallback only: a configured agent model hands
5
5
  * off directly to the current main-window model. Setup lists only currently
6
6
  * available models and derives thinking choices from Pi's model metadata.
7
7
  */
@@ -20,8 +20,6 @@ export type ModelContext = Pick<ExtensionContext, "model" | "modelRegistry"> &
20
20
 
21
21
  export const CURRENT_MAIN_MODEL = "__current_main_model__";
22
22
 
23
- export type ModelPickerSlot = "agent" | "vision";
24
-
25
23
  export interface ModelPickerItem {
26
24
  value: string;
27
25
  label: string;
@@ -136,11 +134,10 @@ function modelCapabilities(model: ModelListEntry): string {
136
134
  return `${input} · thinking: ${thinking}`;
137
135
  }
138
136
 
139
- /** Build one searchable list for agent or vision selection. Only models Pi
137
+ /** Build one searchable list for agent model selection. Only models Pi
140
138
  * currently reports as available are supplied by setup. */
141
139
  export function buildModelPickerItems(options: {
142
140
  models: readonly ModelListEntry[];
143
- slot: ModelPickerSlot;
144
141
  configuredRef?: string;
145
142
  mainRef?: string;
146
143
  }): ModelPickerItem[] {
@@ -153,7 +150,6 @@ export function buildModelPickerItems(options: {
153
150
  }
154
151
 
155
152
  const refs = [...byRef.keys()]
156
- .filter((ref) => options.slot !== "vision" || byRef.get(ref)?.input.includes("image") === true)
157
153
  .sort((left, right) => {
158
154
  const leftRank = left === configuredRef ? 0 : left === mainRef ? 1 : 2;
159
155
  const rightRank = right === configuredRef ? 0 : right === mainRef ? 1 : 2;
@@ -163,9 +159,7 @@ export function buildModelPickerItems(options: {
163
159
  const dynamic: ModelPickerItem = {
164
160
  value: CURRENT_MAIN_MODEL,
165
161
  label: "Current main model (dynamic)",
166
- description: options.slot === "vision"
167
- ? "Clear vision override; use the current main model for image tasks"
168
- : "Clear agent override; use the current main model dynamically",
162
+ description: "Clear agent override; use the current main model dynamically",
169
163
  };
170
164
  const items: ModelPickerItem[] = [dynamic];
171
165
  for (const ref of refs) {
package/src/monitor.ts CHANGED
@@ -165,7 +165,7 @@ function tailGraphemes(segments: string[], maxWidth: number): string {
165
165
  * One-line task preview, capped by `maxWidth` display columns (default 80).
166
166
  * `keysOnly` (default): extracted key fragments (paths, quoted phrases,
167
167
  * symbols) are shown bare — the agent name is already displayed next to the
168
- * task line, so templated prose ("explore: trace how ...") adds nothing.
168
+ * task line, so templated prose ("explorer: trace how ...") adds nothing.
169
169
  * `keysOnly: false` keeps the prose as `head…tail` (used for completion
170
170
  * messages, where the Task line is the reader's only context).
171
171
  * Grapheme-safe — CJK, ZWJ emoji and combining sequences are never split.
package/src/prompt.ts CHANGED
@@ -1,84 +1,97 @@
1
1
  /**
2
- * Builds the delegation directive injected into the parent model's system prompt
3
- * via the `before_agent_start` hook. This is the lever that makes the main model
4
- * actually USE the subagent tool proactively (pi never shows it the per-agent
5
- * descriptions otherwise).
6
- *
7
- * The directive is a self-contained replacement for the "Sub-agent Dispatch" and
8
- * "Review, Verification & Commit" sections users otherwise keep in a global
9
- * AGENTS.md — so installing this extension lets them delete those sections without
10
- * losing the behavior. (Other AGENTS.md sections — Behavior, Git & Security,
11
- * platform/language rules — are unrelated and stay put.)
2
+ * Builds the authoritative delegation directive injected into the parent model's
3
+ * system prompt via `before_agent_start`. Tool metadata stays intentionally
4
+ * minimal so role/process guidance is not paid for twice.
12
5
  */
13
6
 
14
7
  import type { AgentConfig } from "./agents.ts";
15
8
  import { formatCatalogEntry } from "./agents.ts";
16
9
 
17
- /** Compact role routing hints, emitted only for roles that are enabled. */
18
- const ROLE_ROUTING: Record<string, string> = {
19
- explore: "explore — codebase reconnaissance: broad/open-ended search, multi-file lookups, mapping unfamiliar code, tracing symbols/dependencies (read-only, competent fast model); NOT for one-line lookups.",
20
- worker: "worker — implement/fix/refactor/test a self-contained task worth a separate context (full tools; plans internally).",
21
- cleaner: "cleaner — evidence-first cleanup for explicit cleanup intent in any language (for example dead code, redundancy, simplification, or over-engineering) or a requested periodic cleanup pass; audit/find/inspect/report is read-only, while explicit remove/clean/simplify/refactor wording permits verified edits; never PR-count or pre-commit driven (reviewer remains the gate).",
22
- reviewer: "reviewer — adversarial pre-commit review of a diff (read-only; independent context).",
23
- };
10
+ function bullets(lines: readonly string[]): string {
11
+ return lines.map((line) => `- ${line}`).join("\n");
12
+ }
24
13
 
25
14
  export function buildDelegationDirective(agents: AgentConfig[]): string {
26
15
  if (agents.length === 0) return "";
27
16
 
28
17
  const catalog = agents.map(formatCatalogEntry).join("\n");
29
- const routing = agents
30
- .map((a) => ROLE_ROUTING[a.name])
31
- .filter((line): line is string => Boolean(line))
32
- .map((line) => `- ${line}`)
33
- .join("\n");
34
- const hasExplore = agents.some((a) => a.name === "explore");
35
- const hasCleaner = agents.some((a) => a.name === "cleaner");
36
- const hasReviewer = agents.some((a) => a.name === "reviewer");
18
+ const hasExplorer = agents.some((agent) => agent.name === "explorer");
19
+ const hasWorker = agents.some((agent) => agent.name === "worker");
20
+ const hasCleaner = agents.some((agent) => agent.name === "cleaner");
21
+ const hasReviewer = agents.some((agent) => agent.name === "reviewer");
37
22
  const hasMultiple = agents.length > 1;
23
+ const worktreeTargets = hasWorker && hasCleaner
24
+ ? "worker, cleaner, or another"
25
+ : hasWorker
26
+ ? "worker or another"
27
+ : hasCleaner
28
+ ? "cleaner or another"
29
+ : "a";
30
+
31
+ const dispatchRules = [
32
+ "Handle simple work inline with direct tools: one-line lookups, known-target reads/edits, and quick questions do not justify a child process.",
33
+ ...(hasExplorer
34
+ ? [
35
+ "Use `explorer` proactively when reconnaissance becomes broad or crosses files: mapping unfamiliar code, tracing symbols/dependencies, or answering multi-file location/reference questions. Treat its output only as a retrieval index; re-read load-bearing files before edits or decisions about deletion, security, compatibility, persistence, or dynamic reachability. Use a stronger model/specialist for complex dynamic, concurrent, migration, or security-sensitive analysis.",
36
+ ]
37
+ : []),
38
+ ...(hasWorker
39
+ ? ["Use `worker` for a self-contained implementation, fix, refactor, or test task whose separate context pays for itself."]
40
+ : []),
41
+ ...(hasCleaner
42
+ ? [
43
+ `Use \`cleaner\` only when the user explicitly authorizes cleanup/removal/simplification edits, including a requested maintenance pass. It gathers evidence, then applies every safe proven in-scope cut; zero edits is valid. Generic or read-only audit, inspect, report, review, code-health, plan, proposed-solution, or cleanup-candidate assessment goes to ${hasReviewer ? "`reviewer`" : "direct main-context inspection because `reviewer` is disabled"}. Never dispatch cleaner by PR count or as the pre-commit gate.`,
44
+ ]
45
+ : []),
46
+ ...(hasReviewer
47
+ ? [
48
+ `Use \`reviewer\` for generic/read-only assessments and as the fresh independent pre-commit gate for non-trivial diffs${hasCleaner ? ", including cleaner edits" : ""}. Advisory findings do not authorize follow-up edits; only gate verdicts can enter auto-fix.`,
49
+ ]
50
+ : []),
51
+ "Brief every child with the complete goal, exact paths, constraints, and expected output. It has no memory of this conversation.",
52
+ "Children are leaf processes without delegation tools. Do not ask them to spawn sub-agents; use `subagent_control fork` on a parked/settled retained thread for an independent continuation.",
53
+ ...(hasMultiple
54
+ ? [
55
+ "Dispatch independent work in one `tasks` array and let the resumed main agent start dependent work only after prerequisites finish.",
56
+ ]
57
+ : []),
58
+ `Filesystem isolation: single tasks default to shared${hasWorker ? "; parallel worker tasks default to detached Git worktrees" : ""}${hasCleaner ? "; cleaner defaults to shared" : ""}. Request \`isolation: "worktree"\` only for ${worktreeTargets} write-capable agent in a Git repository with committed HEAD. Read-only agents reject it, and setup/integration failure never falls back silently to shared.`,
59
+ "A configured child model/provider failure automatically continues the same retained session on the current main model; do not redispatch. Ordinary tool/task failures stay on the selected model.",
60
+ "Trust but verify: inspect actual changes/results before reporting completion.",
61
+ ];
62
+
63
+ const handoffRules = [
64
+ "Dispatch returns immediately and ends this turn. Never sleep, poll, or call `subagent_wait` to hold the turn; results arrive as messages that automatically resume the main agent, even mid-turn.",
65
+ "Use `subagent_wait` with explicit `timeoutMs` only when the user specifically asks you to remain in-turn and wait. Its default lookup is non-blocking.",
66
+ "A result is already shown to the user. Do not restate, paraphrase, or re-summarize it; add only your conclusion or next action, often one line.",
67
+ "A delivered result does not mean siblings are finished. Before declaring the overall task done, use `subagent_status` to confirm that no runs remain active.",
68
+ ];
69
+
70
+ const verificationRules = [
71
+ "Never report an unrun check as passed; identify unavailable checks and pre-existing failures honestly.",
72
+ ...(hasReviewer
73
+ ? [
74
+ "Send every non-trivial diff through one fresh read-only `reviewer` gate before reporting done. Resolve every finding; do not bypass the configured auto-fix/re-review cap.",
75
+ "Use multi-model cross-review only when explicitly requested or for genuinely high-risk security, unsafe/FFI, persistence-migration, or concurrency changes.",
76
+ ]
77
+ : []),
78
+ "Commit or push only when explicitly requested, applicable checks pass, and no review finding remains unresolved.",
79
+ ];
38
80
 
39
81
  return `
40
82
  ## Sub-agent delegation (pi-subagents)
41
83
 
42
- You have a \`subagent\` tool that starts specialized agents in ISOLATED background processes.
43
- It immediately ends the current main-agent turn so the user can keep working. When a child
44
- finishes, its result is sent back as a message that automatically resumes the main agent;
45
- if the main agent is busy, the result waits as a follow-up.
46
-
47
- NEVER run sleep, wait, or polling commands (e.g. Start-Sleep, sleep, timeout), and do NOT
48
- call subagent_wait to hold the turn — dispatching already ended it, and results arrive as
49
- messages that resume the main agent automatically (even mid-turn). Ending your turn is the
50
- default and the only correct way to wait; subagent_wait blocks the turn so the user cannot
51
- give you other work meanwhile. It is non-blocking by default: settled results return
52
- immediately, active runs return a "still running — end your turn" note. Pass an explicit
53
- timeoutMs only when you must stay in the turn (e.g. the user asked you to wait).
84
+ The \`subagent\` tool starts specialized leaf agents in isolated Pi child processes and context windows. It returns immediately; completion messages automatically resume the main agent.
54
85
 
55
86
  Available agents:
56
87
  ${catalog}
57
88
 
58
- ${routing ? `Routing:\n${routing}\n` : ""}Dispatch discipline:
59
- - Handle SIMPLE work INLINE with direct tools: a one-line lookup, single edit, or quick question is a grep/read/edit in the main context — never a sub-agent. Sub-agents cost startup time, tokens, and a context switch.
60
- - Use \`explore\` PROACTIVELY for codebase reconnaissance: mapping an unfamiliar area, multi-file lookups, tracing symbols across modules, or any "where is X / which files reference Y" question that would take several greps or reading multiple files. It should run on a competent fast code model and returns compressed findings, saving main-context space.
61
- - Delegate only when isolation genuinely pays: a self-contained implementation/fix with its own validation (worker)${hasCleaner ? ", explicit evidence-first cleanup (cleaner)" : ""}, or a fresh-context review gate (reviewer).
62
- ${hasCleaner ? "- Route explicit cleanup intent in any language to `cleaner` (for example dead code, redundancy, simplification, or over-engineering), including a requested periodic maintenance pass. Audit/find/inspect/report wording means read-only evidence; apply only for explicit remove/clean/simplify/refactor wording. Generic code review without cleanup intent goes to `reviewer`. Never dispatch cleaner by PR count or automatically as the pre-commit gate; `reviewer` separately reviews cleaner edits.\n" : ""}- When in doubt, start with a direct tool call in the main context; escalate to \`explore\` as soon as the search turns broad or crosses multiple files.
63
- - For an already-known or trivial target, use a direct search/read tool (e.g. grep/find/read) — do not over-delegate a one-line lookup.
64
- ${hasMultiple ? `- Run INDEPENDENT tasks in parallel: one subagent call with a \`tasks\` array, and track them with your todo list. Parallel worker items default to detached Git worktree isolation; pass \`isolation: "shared"\` only when a worker intentionally needs the caller's live uncommitted tree.${hasCleaner ? " Cleaner is also write-capable and may use explicit worktree isolation." : ""} Let the automatically resumed main agent launch dependent work only after its prerequisite result arrives (e.g. explore, then ${hasCleaner ? "worker/cleaner" : "worker"}, then reviewer).\n` : ""}- Single dispatch stays in the shared working tree by default. Use \`isolation: "worktree"\` only for ${hasCleaner ? "worker, cleaner, or another" : "worker or another"} write-capable agent in a Git repository; never request it for explore/reviewer, and never silently retry shared after setup fails.
65
- - Brief each sub-agent as self-contained: goal, exact paths, constraints, expected output. It has NO memory of this conversation.
66
- - Treat delegated agents as leaf workers: do not ask a sub-agent to dispatch another sub-agent; child processes do not have this tool. Use \`subagent_control fork\` on a parked/settled retained thread when you need an independent continuation with preserved context and a new run id.
67
- - Trust but verify: a sub-agent's summary describes intent, not outcome. Check the actual changes/results before reporting work done.
68
- ${hasExplore ? "- Treat `explore` findings as a retrieval index, never as sole proof for edits, deletion, security, compatibility, persistence, or dynamic reachability. Re-read load-bearing files before acting. An underpowered model can be false economy on complex dynamic, concurrent, migration, or security-sensitive code; use a stronger model or specialist there.\n" : ""}
69
- Vision tasks:
70
- - Judge whether a delegated task may require viewing images (frontend screenshots, mockups, design files, visual regression comparisons). If it might, pass \`vision: true\` in the subagent call and give the sub-agent the exact image paths — it reads them with its read tool. Naming an image file or describing frontend/UI work (Vue/React page, mockup, page styling) dispatches as vision automatically, but keep passing the flag for image work described without those signals.
71
- - \`vision: true\` runs the sub-agent on the vision-capable model configured in /subagents-setup; when none is configured it falls back to the main session's current model. Do not skip the flag because the agent's default model looks fast — a non-vision model cannot see the images.
89
+ Dispatch:
90
+ ${bullets(dispatchRules)}
72
91
 
73
- Result handoff (do not re-state):
74
- - A sub-agent's result arrives as a message that is already shown to the user. Do NOT restate, paraphrase, or re-summarize its findings in your reply — that just burns tokens duplicating what is already visible. The user can read the result above.
75
- - Reply only with what you ADD: your own conclusion, the next action you are taking, or a one-line acknowledgement. When the result already answers the user, a single sentence is enough — then end your turn or proceed.
76
- - Read the result and act on it (verify, continue, commit). Keep your own output short.
77
- - A result arriving does NOT mean all work is finished: sub-agents run in the background and siblings may still be active (a delivery names any still-running runs). Do not report the overall task complete until no runs are active — call subagent_status to confirm before saying Done.
92
+ Result handoff:
93
+ ${bullets(handoffRules)}
78
94
 
79
- Review & verification:
80
- - Never report an unrun check as passed; report it as unavailable or as a pre-existing failure.
81
- ${hasReviewer ? `- For non-trivial diffs${hasCleaner ? " (including cleaner edits)" : ""}, run one fresh read-only \`reviewer\` sub-agent before reporting done. Fix every finding the reviewer reports and re-review at most once.
82
- - Use multi-model cross-review only when explicitly requested or for genuinely high-risk changes (security, unsafe/FFI, persistence-migration, concurrency). Reviewers are read-only; only the main agent edits.
83
- ` : ""}- Commit or push only when explicitly requested, applicable checks pass, and no unresolved review findings remain.`;
95
+ Review and verification:
96
+ ${bullets(verificationRules)}`;
84
97
  }
package/src/runtime.ts CHANGED
@@ -52,7 +52,6 @@ export interface SubagentThread {
52
52
  cwd: string;
53
53
  /** Actual child cwd (the equivalent path inside an isolated worktree). */
54
54
  executionCwd: string;
55
- vision: boolean;
56
55
  thinkingLevel?: ThinkingLevel;
57
56
  isolation: IsolationMode;
58
57
  worktree?: WorktreeIsolation;