pi-subagents 0.66.0 → 0.68.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.
Files changed (172) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/README.md +5 -4
  3. package/agents/evidence-auditor.md +34 -0
  4. package/agents/reviewer.md +3 -2
  5. package/docs/agents.md +43 -15
  6. package/docs/configuration.md +67 -23
  7. package/docs/extension-api.md +38 -19
  8. package/docs/missions.md +10 -2
  9. package/docs/models.md +11 -79
  10. package/docs/observability.md +22 -12
  11. package/docs/standalone-background.md +59 -0
  12. package/docs/tool-reference.md +33 -20
  13. package/docs/watchdog.md +39 -10
  14. package/docs/workflows.md +37 -13
  15. package/index.ts +5 -2
  16. package/inspector-runner.mjs +2 -2
  17. package/package.json +4 -2
  18. package/prompts/parallel-review.md +1 -1
  19. package/runner-peer-loader.mjs +24 -0
  20. package/runner-peer-preload.mjs +32 -0
  21. package/skills/pi-subagents/SKILL.md +32 -21
  22. package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
  23. package/skills/pi-subagents/references/execution-controls.md +11 -9
  24. package/skills/pi-subagents/references/management-authoring-rpc.md +0 -1
  25. package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  26. package/skills/pi-subagents/references/prompting-and-roles.md +17 -13
  27. package/skills/pi-subagents/references/review-and-validation.md +3 -3
  28. package/src/agents/advertised-agent-prompt.ts +34 -3
  29. package/src/agents/agent-management.ts +57 -58
  30. package/src/agents/agent-serializer.ts +4 -3
  31. package/src/agents/agents.ts +190 -71
  32. package/src/agents/builtin-names.ts +1 -0
  33. package/src/agents/chain-serializer.ts +5 -0
  34. package/src/agents/runtime-agent-registry.ts +7 -6
  35. package/src/api/delegation.ts +4 -0
  36. package/src/api/preflight.ts +94 -59
  37. package/src/api/required-child-extensions.ts +6 -0
  38. package/src/api/shared-types.ts +2 -0
  39. package/src/extension/config.ts +10 -37
  40. package/src/extension/fanout-child.ts +66 -4
  41. package/src/extension/herdr-pi-bridge.ts +160 -0
  42. package/src/extension/index.ts +62 -39
  43. package/src/extension/public-execution.ts +7 -5
  44. package/src/extension/rpc.ts +4 -0
  45. package/src/extension/schemas.ts +81 -81
  46. package/src/extension/tool-description.ts +30 -82
  47. package/src/inspectors/actions.ts +148 -0
  48. package/src/inspectors/ghostty/actions.ts +74 -0
  49. package/src/inspectors/ghostty/plugin.ts +17 -0
  50. package/src/inspectors/herdr/actions.ts +99 -179
  51. package/src/inspectors/herdr/plugin.ts +20 -0
  52. package/src/inspectors/herdr/project-panes.ts +1 -1
  53. package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
  54. package/src/inspectors/plugins.ts +8 -0
  55. package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
  56. package/src/inspectors/types.ts +51 -0
  57. package/src/intercom/intercom-bridge.ts +50 -8
  58. package/src/intercom/native-supervisor-channel.ts +44 -31
  59. package/src/policy/authority.ts +4 -0
  60. package/src/profiles/profiles.ts +12 -6
  61. package/src/runs/background/active-async-capacity.ts +4 -0
  62. package/src/runs/background/active-run-index.ts +17 -1
  63. package/src/runs/background/async-execution.ts +348 -176
  64. package/src/runs/background/async-job-tracker.ts +8 -6
  65. package/src/runs/background/async-resume.ts +17 -12
  66. package/src/runs/background/async-status.ts +15 -4
  67. package/src/runs/background/auto-drain.ts +23 -10
  68. package/src/runs/background/binary-bootstrap.ts +38 -0
  69. package/src/runs/background/chain-append.ts +1 -1
  70. package/src/runs/background/chain-root-attachment.ts +14 -33
  71. package/src/runs/background/fleet-view.ts +30 -2
  72. package/src/runs/background/notify.ts +105 -7
  73. package/src/runs/background/owned-process-tree.ts +29 -2
  74. package/src/runs/background/result-files.ts +8 -4
  75. package/src/runs/background/result-watcher.ts +19 -2
  76. package/src/runs/background/run-child-session.ts +81 -33
  77. package/src/runs/background/run-status.ts +3 -0
  78. package/src/runs/background/runner-aliases.ts +12 -33
  79. package/src/runs/background/runner-child-launch.ts +6 -1
  80. package/src/runs/background/runner-child-sessions.ts +5 -4
  81. package/src/runs/background/runner-http-dispatcher.ts +119 -0
  82. package/src/runs/background/scheduled-runs.ts +51 -18
  83. package/src/runs/background/stale-run-reconciler.ts +35 -11
  84. package/src/runs/background/steering.ts +20 -2
  85. package/src/runs/background/subagent-runner.ts +441 -304
  86. package/src/runs/background/subagent-wait.ts +176 -28
  87. package/src/runs/background/wait-completions.ts +75 -27
  88. package/src/runs/background/wait-subscriptions.ts +9 -3
  89. package/src/runs/background/wait-tool.ts +5 -3
  90. package/src/runs/foreground/async-steering-action.ts +18 -7
  91. package/src/runs/foreground/async-stop-action.ts +93 -3
  92. package/src/runs/foreground/execution.ts +134 -248
  93. package/src/runs/foreground/foreground-history.ts +2 -1
  94. package/src/runs/foreground/prompt-audit.ts +3 -1
  95. package/src/runs/foreground/subagent-executor.ts +374 -178
  96. package/src/runs/foreground/workflow-detach-reconcile.ts +2 -0
  97. package/src/runs/foreground/workflow-foreground-steering.ts +2 -1
  98. package/src/runs/shared/acceptance.ts +38 -11
  99. package/src/runs/shared/async-status-projection.ts +127 -33
  100. package/src/runs/shared/capability-ceiling.ts +2 -0
  101. package/src/runs/shared/child-hooks.ts +25 -10
  102. package/src/runs/shared/child-launch-plan.ts +15 -3
  103. package/src/runs/shared/child-launch.ts +28 -5
  104. package/src/runs/shared/child-lifecycle.ts +6 -3
  105. package/src/runs/shared/child-runtime-config.ts +8 -1
  106. package/src/runs/shared/child-session.ts +127 -52
  107. package/src/runs/shared/child-tool-plan.ts +142 -11
  108. package/src/runs/shared/completion-guard.ts +5 -3
  109. package/src/runs/shared/dynamic-fanout.ts +2 -2
  110. package/src/runs/shared/effective-system-prompt.ts +33 -0
  111. package/src/runs/shared/external-cli-contract.ts +11 -1
  112. package/src/runs/shared/external-cli-preflight.ts +6 -2
  113. package/src/runs/shared/external-cli-runner.ts +9 -7
  114. package/src/runs/shared/herdr-connection.ts +134 -0
  115. package/src/runs/shared/herdr-external-adapters.ts +169 -0
  116. package/src/runs/shared/herdr-machine.ts +279 -0
  117. package/src/runs/shared/herdr-pi-protocol.ts +59 -0
  118. package/src/runs/shared/herdr-placed-run.ts +263 -0
  119. package/src/runs/shared/llm-intent-arbiter.ts +12 -3
  120. package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
  121. package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -235
  122. package/src/runs/shared/model-scope.ts +1 -1
  123. package/src/runs/shared/nested-events.ts +11 -2
  124. package/src/runs/shared/orca-progress-tabs.ts +1 -1
  125. package/src/runs/shared/parallel-utils.ts +7 -2
  126. package/src/runs/shared/pi-spawn.ts +10 -0
  127. package/src/runs/shared/subagent-prompt-runtime.ts +12 -4
  128. package/src/runs/shared/task-intent.ts +46 -13
  129. package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
  130. package/src/runs/shared/worktree-setup-command.ts +27 -4
  131. package/src/runs/shared/worktree.ts +45 -15
  132. package/src/shared/child-cache-retention.ts +43 -0
  133. package/src/shared/fork-context.ts +15 -72
  134. package/src/shared/launch-contract.ts +68 -8
  135. package/src/shared/opencode-session-headers.ts +30 -0
  136. package/src/shared/pruned-fork.ts +1 -1
  137. package/src/shared/required-child-extensions.ts +81 -0
  138. package/src/shared/settings.ts +5 -2
  139. package/src/shared/shortcuts.ts +0 -4
  140. package/src/shared/types.ts +74 -30
  141. package/src/slash/delegation-adapters.ts +3 -1
  142. package/src/slash/delegation-request.ts +14 -0
  143. package/src/slash/slash-commands.ts +2 -7
  144. package/src/slash/subagents-admin.ts +24 -13
  145. package/src/tui/fleet-status.ts +164 -19
  146. package/src/tui/fleet.ts +16 -14
  147. package/src/tui/render.ts +168 -37
  148. package/src/watchdog/child-status.ts +28 -28
  149. package/src/watchdog/lsp-diagnostics.ts +1 -1
  150. package/src/watchdog/model-selection.ts +21 -1
  151. package/src/watchdog/permission-arbiter.ts +3 -1
  152. package/src/watchdog/register-child.ts +10 -2
  153. package/src/watchdog/register-main.ts +39 -35
  154. package/src/watchdog/render.ts +1 -1
  155. package/src/watchdog/review.ts +123 -74
  156. package/src/watchdog/rules.ts +1 -1
  157. package/src/watchdog/runtime.ts +100 -27
  158. package/src/watchdog/scope.ts +1 -1
  159. package/src/watchdog/settings.ts +3 -0
  160. package/src/watchdog/tool-actions.ts +13 -12
  161. package/src/watchdog/turn-delta.ts +23 -0
  162. package/src/watchdog/types.ts +5 -3
  163. package/src/watchdog/warning-format.ts +1 -1
  164. package/src/workflows/scripted-workflow.ts +279 -10
  165. package/src/workflows/workflow-checklist.ts +2 -2
  166. package/src/workflows/workflow-receipt.ts +21 -3
  167. package/src/workflows/workflow-resources.ts +13 -2
  168. package/runner-server-preload.mjs +0 -13
  169. package/src/runs/shared/model-exclusions.ts +0 -374
  170. package/src/runs/shared/readonly-model-continuation.ts +0 -69
  171. package/src/runs/shared/readonly-session-evidence.ts +0 -307
  172. /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
@@ -8,7 +8,7 @@ import { parse as parseYaml } from "yaml";
8
8
  import * as os from "node:os";
9
9
  import * as path from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
- import type { AcceptanceInput, AcceptanceRole, AgentRunnerConfig, OutputMode, ToolBudgetConfig } from "../shared/types.ts";
11
+ import type { AcceptanceInput, AcceptanceRole, AgentRunnerConfig, JsonSchemaObject, OutputMode, ToolBudgetConfig } from "../shared/types.ts";
12
12
  import { CODE_OWNED_EXTERNAL_CLI_ADAPTER_LABEL, isCodeOwnedExternalCliAdapterId, parseExternalCliCapabilityNarrowing, validateCodeOwnedProfileRunner } from "../runs/shared/external-cli-contract.ts";
13
13
  import { getAgentDir, getProjectConfigDir } from "../shared/utils.ts";
14
14
  import { expandHomePath } from "../shared/settings.ts";
@@ -24,6 +24,7 @@ import { parseMemoryFrontmatter } from "./agent-memory.ts";
24
24
  import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
25
25
  import { validatePermissionRules, type PermissionRules } from "../runs/shared/permissions.ts";
26
26
  import { parseThinkingLevel, type ThinkingLevel } from "../shared/thinking-ceiling.ts";
27
+ import { assertJsonSchemaObject } from "../runs/shared/structured-output.ts";
27
28
 
28
29
  export type AgentScope = "user" | "project" | "both";
29
30
 
@@ -52,12 +53,12 @@ export function defaultInheritSkills(): boolean {
52
53
 
53
54
  export interface BuiltinAgentOverrideBase {
54
55
  description?: string;
56
+ machine?: string;
55
57
  output?: string;
56
58
  outputMode?: OutputMode;
57
59
  defaultReads?: string[];
58
60
  model?: string;
59
61
  modelProvider?: string;
60
- fallbackModels?: string[];
61
62
  fast?: boolean;
62
63
  thinking?: string | false;
63
64
  systemPromptMode: SystemPromptMode;
@@ -83,12 +84,12 @@ export interface BuiltinAgentOverrideBase {
83
84
 
84
85
  interface BuiltinAgentOverrideConfig {
85
86
  description?: string;
87
+ machine?: string | false;
86
88
  output?: string | false;
87
89
  outputMode?: OutputMode;
88
90
  defaultReads?: string[] | false;
89
91
  model?: string | false;
90
92
  defaultProvider?: string | false;
91
- fallbackModels?: string[] | false;
92
93
  fast?: boolean;
93
94
  thinking?: string | false;
94
95
  systemPromptMode?: SystemPromptMode;
@@ -143,7 +144,6 @@ export interface AgentConfig {
143
144
  mcpDirectTools?: string[];
144
145
  model?: string;
145
146
  modelProvider?: string;
146
- fallbackModels?: string[];
147
147
  fast?: boolean;
148
148
  thinking?: string | false;
149
149
  systemPromptMode: SystemPromptMode;
@@ -168,6 +168,7 @@ export interface AgentConfig {
168
168
  mutationTools?: string[];
169
169
  output?: string;
170
170
  outputMode?: OutputMode;
171
+ outputSchema?: JsonSchemaObject;
171
172
  defaultReads?: string[];
172
173
  defaultProgress?: boolean;
173
174
  interactive?: boolean;
@@ -176,11 +177,18 @@ export interface AgentConfig {
176
177
  toolBudget?: ToolBudgetConfig;
177
178
  permissions?: PermissionRules;
178
179
  memory?: AgentMemoryConfig;
180
+ machine?: string;
179
181
  disabled?: boolean;
180
182
  extraFields?: Record<string, string>;
181
183
  override?: BuiltinAgentOverrideInfo;
182
184
  modelSource?: AgentModelSourceInfo;
183
185
  maxThinking?: ThinkingLevel;
186
+ /**
187
+ * Digest of the parsed definition, set when a runtime overlay such as the
188
+ * Intercom bridge rewrites launch-affecting fields. Launch identity reads
189
+ * this instead of re-hashing the overlaid copy.
190
+ */
191
+ definitionDigest?: string;
184
192
  }
185
193
 
186
194
  type ProjectRootResolution = "nearest" | "git-root";
@@ -189,6 +197,7 @@ interface SubagentSettings {
189
197
  overrides: Record<string, BuiltinAgentOverrideConfig>;
190
198
  providerOverrides: Record<string, Record<string, BuiltinAgentOverrideConfig>>;
191
199
  agentScanDirs?: string[];
200
+ agentExcludeDirs?: string[];
192
201
  defaultModel?: string;
193
202
  defaultProvider?: string;
194
203
  defaultThinking?: string;
@@ -209,6 +218,7 @@ export interface ChainStepConfig {
209
218
  label?: string;
210
219
  as?: string;
211
220
  outputSchema?: string | Record<string, unknown>;
221
+ machine?: string;
212
222
  output?: string | false;
213
223
  outputMode?: OutputMode;
214
224
  reads?: string[] | false;
@@ -585,8 +595,8 @@ function collectSettingsPackageRoots(settingsFile: string, baseDir: string): str
585
595
  for (const entry of packages) {
586
596
  const packageSource = typeof entry === "string"
587
597
  ? entry
588
- : typeof entry === "object" && entry !== null && typeof (entry as { source?: unknown }).source === "string"
589
- ? (entry as { source: string }).source
598
+ : typeof entry === "object" && entry !== null && "source" in entry && typeof entry.source === "string"
599
+ ? entry.source
590
600
  : undefined;
591
601
  if (!packageSource) continue;
592
602
  const packageRoot = resolveSettingsPackageRoot(packageSource, baseDir);
@@ -694,12 +704,20 @@ function effectiveAgentMatch(matches: AgentConfig[]): { agent?: AgentConfig; err
694
704
 
695
705
  export function resolveAgentName(name: string, agents: AgentConfig[]): { agent?: AgentConfig; error?: string } {
696
706
  const raw = name.trim();
697
- const exact = agents.filter((agent) => agent.name === raw || agent.localName === raw);
698
- if (exact.length === 1) return exact[0] ? { agent: exact[0] } : {};
699
- if (exact.length > 1) {
700
- const effective = effectiveAgentMatch(exact);
707
+ const canonical = agents.filter((agent) => agent.name === raw);
708
+ if (canonical.length === 1) return canonical[0] ? { agent: canonical[0] } : {};
709
+ if (canonical.length > 1) {
710
+ const effective = effectiveAgentMatch(canonical);
701
711
  if (effective.agent) return effective;
702
- return { error: `Ambiguous agent name '${name}': ${exact.map((agent) => agent.name).join(", ")}` };
712
+ return { error: `Ambiguous agent name '${name}': ${canonical.map((agent) => agent.name).join(", ")}` };
713
+ }
714
+
715
+ const local = agents.filter((agent) => agent.localName === raw);
716
+ if (local.length === 1) return local[0] ? { agent: local[0] } : {};
717
+ if (local.length > 1) {
718
+ const effective = effectiveAgentMatch(local);
719
+ if (effective.agent) return effective;
720
+ return { error: `Ambiguous local agent name '${name}': ${local.map((agent) => agent.name).join(", ")}` };
703
721
  }
704
722
 
705
723
  const aliases = agents.filter((agent) => agent.aliases?.includes(raw));
@@ -749,12 +767,12 @@ function arraysEqual(a: string[] | undefined, b: string[] | undefined): boolean
749
767
  function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
750
768
  return {
751
769
  description: agent.description,
770
+ ...(agent.machine !== undefined ? { machine: agent.machine } : {}),
752
771
  ...(agent.output !== undefined ? { output: agent.output } : {}),
753
772
  ...(agent.outputMode !== undefined ? { outputMode: agent.outputMode } : {}),
754
773
  ...(agent.defaultReads !== undefined ? { defaultReads: [...agent.defaultReads] } : {}),
755
774
  ...(agent.model !== undefined ? { model: agent.model } : {}),
756
775
  ...(agent.modelProvider !== undefined ? { modelProvider: agent.modelProvider } : {}),
757
- ...(agent.fallbackModels ? { fallbackModels: [...agent.fallbackModels] } : {}),
758
776
  ...(agent.fast !== undefined ? { fast: agent.fast } : {}),
759
777
  ...(agent.thinking !== undefined ? { thinking: agent.thinking } : {}),
760
778
  systemPromptMode: agent.systemPromptMode,
@@ -782,14 +800,12 @@ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
782
800
  function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentOverrideConfig {
783
801
  return {
784
802
  ...(override.description !== undefined ? { description: override.description } : {}),
803
+ ...(override.machine !== undefined ? { machine: override.machine } : {}),
785
804
  ...(override.output !== undefined ? { output: override.output } : {}),
786
805
  ...(override.outputMode !== undefined ? { outputMode: override.outputMode } : {}),
787
806
  ...(override.defaultReads !== undefined ? { defaultReads: override.defaultReads === false ? false : [...override.defaultReads] } : {}),
788
807
  ...(override.model !== undefined ? { model: override.model } : {}),
789
808
  ...(override.defaultProvider !== undefined ? { defaultProvider: override.defaultProvider } : {}),
790
- ...(override.fallbackModels !== undefined
791
- ? { fallbackModels: override.fallbackModels === false ? false : [...override.fallbackModels] }
792
- : {}),
793
809
  ...(override.fast !== undefined ? { fast: override.fast } : {}),
794
810
  ...(override.thinking !== undefined ? { thinking: override.thinking } : {}),
795
811
  ...(override.systemPromptMode !== undefined ? { systemPromptMode: override.systemPromptMode } : {}),
@@ -818,8 +834,17 @@ function isProjectRootCandidate(dir: string): boolean {
818
834
 
819
835
  function findProjectRootCandidates(cwd: string): string[] {
820
836
  const roots: string[] = [];
837
+ const windowsProfile = process.env.HOMEDRIVE && process.env.HOMEPATH
838
+ ? `${process.env.HOMEDRIVE}${process.env.HOMEPATH}`
839
+ : undefined;
840
+ const homeDirs = new Set([os.homedir(), process.env.HOME, process.env.USERPROFILE, windowsProfile]
841
+ .filter((value): value is string => Boolean(value?.trim()))
842
+ .filter(isDirectory)
843
+ .map((value) => fs.realpathSync.native(value)));
821
844
  let currentDir = cwd;
822
845
  while (true) {
846
+ // ~/.pi and ~/.agents are user configuration, never an implicit project.
847
+ if (isDirectory(currentDir) && homeDirs.has(fs.realpathSync.native(currentDir))) return roots;
823
848
  if (isProjectRootCandidate(currentDir)) roots.push(currentDir);
824
849
 
825
850
  const parentDir = path.dirname(currentDir);
@@ -951,6 +976,15 @@ function parseToolsOverride(
951
976
  throw new Error(`Builtin override '${meta.name}' in '${meta.filePath}' has invalid 'tools'; expected an array of strings, "inherit", or false.`);
952
977
  }
953
978
 
979
+ function validateOptionalMachine(value: unknown, label: string): string | undefined {
980
+ if (value === undefined || value === false) return undefined;
981
+ if (typeof value !== "string" || !value.trim()) throw new Error(label + " must be a non-empty string or false.");
982
+ const machine = value.trim();
983
+ if (machine.length > 128) throw new Error(label + " must be 128 characters or fewer.");
984
+ if (/[\u0000-\u001f\u007f]/u.test(machine)) throw new Error(label + " contains control characters.");
985
+ return machine;
986
+ }
987
+
954
988
  function parseBuiltinOverrideEntry(
955
989
  name: string,
956
990
  value: unknown,
@@ -961,6 +995,9 @@ function parseBuiltinOverrideEntry(
961
995
  }
962
996
 
963
997
  const input = value as Record<string, unknown>;
998
+ if (Object.hasOwn(input, "fallbackModels")) {
999
+ throw new Error(`Builtin override '${name}' in '${filePath}' uses removed field 'fallbackModels'; configure one model instead.`);
1000
+ }
964
1001
  const override: BuiltinAgentOverrideConfig = {};
965
1002
 
966
1003
  if ("description" in input) {
@@ -1078,11 +1115,15 @@ function parseBuiltinOverrideEntry(
1078
1115
  else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'systemPrompt'; expected a string.`);
1079
1116
  }
1080
1117
 
1118
+ if (input.machine === false) override.machine = false;
1119
+ else {
1120
+ const machine = validateOptionalMachine(input.machine, `Builtin override '${name}' in '${filePath}' field 'machine'`);
1121
+ if (machine !== undefined) override.machine = machine;
1122
+ }
1123
+
1081
1124
  const defaultReads = parseOverrideStringArrayOrFalse(input.defaultReads, { filePath, name, field: "defaultReads" });
1082
1125
  if (defaultReads !== undefined) override.defaultReads = defaultReads;
1083
1126
 
1084
- const fallbackModels = parseOverrideStringArrayOrFalse(input.fallbackModels, { filePath, name, field: "fallbackModels" });
1085
- if (fallbackModels !== undefined) override.fallbackModels = fallbackModels;
1086
1127
 
1087
1128
  if ("defaultProvider" in input) {
1088
1129
  if (input.defaultProvider === false) override.defaultProvider = false;
@@ -1185,6 +1226,14 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
1185
1226
  }
1186
1227
  agentScanDirs = subagentsObject.agentScanDirs.map((item) => item.trim());
1187
1228
  }
1229
+ let agentExcludeDirs: string[] | undefined;
1230
+ if ("agentExcludeDirs" in subagentsObject) {
1231
+ if (!Array.isArray(subagentsObject.agentExcludeDirs)
1232
+ || subagentsObject.agentExcludeDirs.some((item) => typeof item !== "string" || !item.trim())) {
1233
+ throw new Error(`Subagent settings in '${filePath}' have invalid 'agentExcludeDirs'; expected an array of non-empty strings.`);
1234
+ }
1235
+ agentExcludeDirs = subagentsObject.agentExcludeDirs.map((item) => item.trim());
1236
+ }
1188
1237
  const modelScope = parseModelScopeConfig(subagentsObject.modelScope, { filePath });
1189
1238
 
1190
1239
  const parsed: Record<string, BuiltinAgentOverrideConfig> = {};
@@ -1200,6 +1249,7 @@ function readSubagentSettings(filePath: string | null): SubagentSettings {
1200
1249
  ...(maxThinking !== undefined ? { maxThinking } : {}),
1201
1250
  ...(defaultExtensions !== undefined ? { defaultExtensions } : {}),
1202
1251
  ...(agentScanDirs !== undefined ? { agentScanDirs } : {}),
1252
+ ...(agentExcludeDirs !== undefined ? { agentExcludeDirs } : {}),
1203
1253
  ...(disableBuiltins !== undefined ? { disableBuiltins } : {}),
1204
1254
  ...(disableThinking !== undefined ? { disableThinking } : {}),
1205
1255
  ...(modelScope !== undefined ? { modelScope } : {}),
@@ -1378,6 +1428,7 @@ function applyBuiltinOverride(
1378
1428
  };
1379
1429
 
1380
1430
  if (override.description !== undefined) next.description = override.description;
1431
+ if (override.machine !== undefined) { if (override.machine === false) delete next.machine; else next.machine = override.machine; }
1381
1432
  if (override.output !== undefined) { if (override.output === false) delete next.output; else next.output = override.output; }
1382
1433
  if (override.outputMode !== undefined) next.outputMode = override.outputMode;
1383
1434
  if (override.defaultReads !== undefined) { if (override.defaultReads === false) delete next.defaultReads; else next.defaultReads = [...override.defaultReads]; }
@@ -1389,7 +1440,6 @@ function applyBuiltinOverride(
1389
1440
  if (override.defaultProvider === false) delete next.modelProvider;
1390
1441
  else next.modelProvider = override.defaultProvider;
1391
1442
  }
1392
- if (override.fallbackModels !== undefined) { if (override.fallbackModels === false) delete next.fallbackModels; else next.fallbackModels = [...override.fallbackModels]; }
1393
1443
  if (override.fast !== undefined) next.fast = override.fast;
1394
1444
  if (override.thinking !== undefined) { if (override.thinking === false) delete next.thinking; else next.thinking = override.thinking; }
1395
1445
  if (override.systemPromptMode !== undefined) next.systemPromptMode = override.systemPromptMode;
@@ -1510,9 +1560,10 @@ function applyCustomAgentOverrides(
1510
1560
 
1511
1561
  export function buildBuiltinOverrideConfig(
1512
1562
  base: BuiltinAgentOverrideBase,
1513
- draft: Pick<AgentConfig, "model" | "modelProvider" | "fallbackModels" | "fast" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritGlobalContext" | "inheritSkills" | "defaultContext" | "acceptanceRole" | "disabled" | "systemPrompt" | "skills" | "tools" | "allowNestedSubagents" | "mcpDirectTools" | "extensions" | "subagentOnlyExtensions" | "mutationTools" | "completionGuard" | "toolBudget"> & Partial<Pick<AgentConfig, "description" | "output" | "outputMode" | "defaultReads" | "excludeTools">>,
1563
+ draft: Pick<AgentConfig, "model" | "modelProvider" | "fast" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritGlobalContext" | "inheritSkills" | "defaultContext" | "acceptanceRole" | "disabled" | "systemPrompt" | "skills" | "tools" | "allowNestedSubagents" | "mcpDirectTools" | "extensions" | "subagentOnlyExtensions" | "mutationTools" | "completionGuard" | "toolBudget"> & Partial<Pick<AgentConfig, "description" | "machine" | "output" | "outputMode" | "defaultReads" | "excludeTools">>,
1514
1564
  ): BuiltinAgentOverrideConfig | undefined {
1515
1565
  const override: BuiltinAgentOverrideConfig = {};
1566
+ if (draft.machine !== base.machine) override.machine = draft.machine ?? false;
1516
1567
 
1517
1568
  if (draft.description !== undefined) {
1518
1569
  const description = draft.description.trim();
@@ -1523,7 +1574,6 @@ export function buildBuiltinOverrideConfig(
1523
1574
  if (!arraysEqual(draft.defaultReads, base.defaultReads)) override.defaultReads = draft.defaultReads ? [...draft.defaultReads] : false;
1524
1575
  if (draft.model !== base.model) override.model = draft.model ?? false;
1525
1576
  if (draft.modelProvider !== base.modelProvider) override.defaultProvider = draft.modelProvider ?? false;
1526
- if (!arraysEqual(draft.fallbackModels, base.fallbackModels)) override.fallbackModels = draft.fallbackModels ? [...draft.fallbackModels] : false;
1527
1577
  if (draft.fast !== base.fast) override.fast = draft.fast === true;
1528
1578
  if (draft.thinking !== base.thinking) override.thinking = draft.thinking ?? false;
1529
1579
  if (draft.systemPromptMode !== base.systemPromptMode) override.systemPromptMode = draft.systemPromptMode;
@@ -1578,21 +1628,25 @@ export function saveBuiltinAgentOverride(
1578
1628
  return filePath;
1579
1629
  }
1580
1630
 
1581
- export function removeBuiltinAgentOverride(cwd: string, name: string, scope: "user" | "project"): { path: string; removed: boolean } {
1631
+ export function removeBuiltinAgentOverride(cwd: string, name: string, scope: "user" | "project", options?: { preserveMachine?: boolean }): { path: string; removed: boolean; machinePreserved: boolean } {
1582
1632
  const filePath = scope === "project" ? getProjectAgentSettingsPath(cwd) : getUserAgentSettingsPath();
1583
1633
  if (!filePath) throw new Error("Project override is not available here. No project config root was found.");
1584
- if (!fs.existsSync(filePath)) return { path: filePath, removed: false };
1634
+ if (!fs.existsSync(filePath)) return { path: filePath, removed: false, machinePreserved: false };
1585
1635
 
1586
1636
  const settings = readSettingsFileStrict(filePath);
1587
1637
  const subagents = settings.subagents;
1588
- if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return { path: filePath, removed: false };
1638
+ if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return { path: filePath, removed: false, machinePreserved: false };
1589
1639
  const nextSubagents = { ...(subagents as Record<string, unknown>) };
1590
1640
  const agentOverrides = nextSubagents.agentOverrides;
1591
- if (!agentOverrides || typeof agentOverrides !== "object" || Array.isArray(agentOverrides)) return { path: filePath, removed: false };
1641
+ if (!agentOverrides || typeof agentOverrides !== "object" || Array.isArray(agentOverrides)) return { path: filePath, removed: false, machinePreserved: false };
1592
1642
 
1593
1643
  const nextOverrides = { ...(agentOverrides as Record<string, unknown>) };
1594
- if (!Object.prototype.hasOwnProperty.call(nextOverrides, name)) return { path: filePath, removed: false };
1595
- delete nextOverrides[name];
1644
+ const current = nextOverrides[name];
1645
+ if (!Object.prototype.hasOwnProperty.call(nextOverrides, name)) return { path: filePath, removed: false, machinePreserved: false };
1646
+ const configuredMachine = current && typeof current === "object" && !Array.isArray(current) ? (current as Record<string, unknown>).machine : undefined;
1647
+ const machine = configuredMachine === false || (typeof configuredMachine === "string" && configuredMachine.trim()) ? configuredMachine : undefined;
1648
+ if (options?.preserveMachine && machine !== undefined) nextOverrides[name] = { machine };
1649
+ else delete nextOverrides[name];
1596
1650
  if (Object.keys(nextOverrides).length > 0) nextSubagents.agentOverrides = nextOverrides;
1597
1651
  else delete nextSubagents.agentOverrides;
1598
1652
 
@@ -1600,7 +1654,7 @@ export function removeBuiltinAgentOverride(cwd: string, name: string, scope: "us
1600
1654
  else delete settings.subagents;
1601
1655
 
1602
1656
  writeSettingsFile(filePath, settings);
1603
- return { path: filePath, removed: true };
1657
+ return { path: filePath, removed: true, machinePreserved: options?.preserveMachine === true && machine !== undefined };
1604
1658
  }
1605
1659
 
1606
1660
  export function mergeBuiltinAgentOverride(
@@ -1772,8 +1826,9 @@ const DEFAULT_AGENT_DEFINITION_INSPECTION_FS: AgentDefinitionInspectionFs = {
1772
1826
  * state. A nested unreadable directory makes the whole inspection unavailable,
1773
1827
  * preventing a partial traversal from being reported as empty or complete.
1774
1828
  */
1775
- export function inspectAgentDefinitionDirectory(dir: string, operations: AgentDefinitionInspectionFs = DEFAULT_AGENT_DEFINITION_INSPECTION_FS): AgentDefinitionInspection {
1829
+ export function inspectAgentDefinitionDirectory(dir: string, operations: AgentDefinitionInspectionFs = DEFAULT_AGENT_DEFINITION_INSPECTION_FS, isExcluded: (filePath: string) => boolean = () => false): AgentDefinitionInspection {
1776
1830
  const root = path.resolve(dir);
1831
+ if (isExcluded(root)) return rememberAgentDefinitionInspection({ files: [], state: "empty" }, []);
1777
1832
  try {
1778
1833
  if (!operations.existsSync(root)) return rememberAgentDefinitionInspection({ files: [], state: "absent" }, [root]);
1779
1834
  if (!operations.statSync(root).isDirectory()) return rememberAgentDefinitionInspection({ files: [], state: "not-directory" }, [root]);
@@ -1805,6 +1860,7 @@ export function inspectAgentDefinitionDirectory(dir: string, operations: AgentDe
1805
1860
  }
1806
1861
  for (const entry of entries) {
1807
1862
  const filePath = path.join(current, entry.name);
1863
+ if (isExcluded(filePath)) continue;
1808
1864
  let isDirectory = entry.isDirectory();
1809
1865
  if (entry.isSymbolicLink()) {
1810
1866
  try {
@@ -1904,7 +1960,7 @@ function parseAgentRunnerFrontmatter(raw: string | undefined, agentName: string)
1904
1960
 
1905
1961
  function validateExternalRunnerProfile(frontmatter: Record<string, string>, agentName: string, runner: AgentRunnerConfig | undefined): void {
1906
1962
  if (runner?.type !== "external-cli" && runner?.type !== "external-job") return;
1907
- const unsupported = ["tools", "excludeTools", "allowNestedSubagents", "model", "fallbackModels", "thinking", "extensions", "subagentOnlyExtensions", "mutationTools", "maxSubagentDepth", "completionGuard", "skills", "skill", "skillPath", "toolBudget", "permission", "permissions"]
1963
+ const unsupported = ["tools", "excludeTools", "allowNestedSubagents", "model", "thinking", "extensions", "subagentOnlyExtensions", "mutationTools", "maxSubagentDepth", "completionGuard", "skills", "skill", "skillPath", "toolBudget", "permission", "permissions"]
1908
1964
  .filter((field) => frontmatter[field] !== undefined);
1909
1965
  if (unsupported.length > 0) {
1910
1966
  throw new Error(`Agent '${agentName}' uses runner.type='${runner.type}' and declares unsupported Pi-only fields: ${unsupported.join(", ")}.`);
@@ -2000,7 +2056,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
2000
2056
  const skillStr = frontmatter.skill || frontmatter.skills;
2001
2057
  const skills = parseFrontmatterList(skillStr);
2002
2058
  const skillPath = parseFrontmatterList(frontmatter.skillPath);
2003
- const fallbackModels = parseFrontmatterList(frontmatter.fallbackModels);
2059
+ if (frontmatter.fallbackModels !== undefined) throw new Error(`Agent '${filePath}' uses removed frontmatter field 'fallbackModels'. Configure one model instead.`);
2004
2060
  const systemPromptMode = frontmatter.systemPromptMode === "replace"
2005
2061
  ? "replace"
2006
2062
  : frontmatter.systemPromptMode === "append"
@@ -2093,6 +2149,12 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
2093
2149
  }
2094
2150
  toolBudget = parsed as ToolBudgetConfig;
2095
2151
  }
2152
+ let outputSchema: JsonSchemaObject | undefined;
2153
+ if (frontmatter.outputSchema !== undefined && frontmatter.outputSchema.trim()) {
2154
+ const parsed: unknown = JSON.parse(frontmatter.outputSchema);
2155
+ assertJsonSchemaObject(parsed, `Agent '${localName}' outputSchema`);
2156
+ outputSchema = parsed;
2157
+ }
2096
2158
  const completionGuard = frontmatter.completionGuard === "false"
2097
2159
  ? false
2098
2160
  : frontmatter.completionGuard === "true"
@@ -2103,6 +2165,7 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
2103
2165
  ? parsedMaxSubagentDepth
2104
2166
  : undefined;
2105
2167
  const memory = parseMemoryFrontmatter(frontmatter.memory);
2168
+ const machine = validateOptionalMachine(frontmatter.machine, `Agent '${runtimeName}' frontmatter 'machine'`);
2106
2169
  const agent: AgentConfig = {
2107
2170
  name: runtimeName,
2108
2171
  ...(runner !== undefined ? { runner } : {}),
@@ -2119,7 +2182,6 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
2119
2182
  ...(allowNestedSubagents !== undefined ? { allowNestedSubagents } : {}),
2120
2183
  ...(mcpDirectTools.length > 0 ? { mcpDirectTools } : {}),
2121
2184
  ...(frontmatter.model !== undefined ? { model: frontmatter.model } : {}),
2122
- ...(fallbackModels?.length ? { fallbackModels } : {}),
2123
2185
  ...(fast !== undefined ? { fast } : {}),
2124
2186
  ...(frontmatter.thinking !== undefined ? { thinking: frontmatter.thinking === "false" ? false : frontmatter.thinking } : {}),
2125
2187
  systemPromptMode,
@@ -2141,8 +2203,10 @@ function loadAgentsFromDefinitionFiles(files: AgentDefinitionFile[], source: Age
2141
2203
  ...(extensions !== undefined ? { extensions } : {}),
2142
2204
  ...(subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions } : {}),
2143
2205
  ...(mutationTools?.length ? { mutationTools } : {}),
2206
+ ...(machine !== undefined ? { machine } : {}),
2144
2207
  ...(frontmatter.output !== undefined ? { output: frontmatter.output } : {}),
2145
2208
  ...(outputMode !== undefined ? { outputMode } : {}),
2209
+ ...(outputSchema !== undefined ? { outputSchema } : {}),
2146
2210
  ...(defaultReads?.length ? { defaultReads } : {}),
2147
2211
  defaultProgress: frontmatter.defaultProgress === "true",
2148
2212
  interactive: frontmatter.interactive === "true",
@@ -2262,31 +2326,60 @@ interface AgentScanDirs {
2262
2326
  watchPaths: string[];
2263
2327
  }
2264
2328
 
2265
- function readConfiguredAgentScanDirs(filePath: string | null): string[] {
2329
+ function readConfiguredAgentScanDirs(filePath: string | null, key: "agentScanDirs" | "agentExcludeDirs" = "agentScanDirs"): string[] {
2266
2330
  if (!filePath) return [];
2267
2331
  try {
2268
2332
  const settings = readSettingsFileStrict(filePath);
2269
2333
  const subagents = settings.subagents;
2270
2334
  if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return [];
2271
- const dirs = (subagents as { agentScanDirs?: unknown }).agentScanDirs;
2335
+ const dirs = (subagents as Record<string, unknown>)[key];
2272
2336
  return Array.isArray(dirs) ? dirs.filter((dir): dir is string => typeof dir === "string" && dir.trim().length > 0) : [];
2273
2337
  } catch {
2274
2338
  return [];
2275
2339
  }
2276
2340
  }
2277
2341
 
2278
- function expandAgentScanDirPattern(pattern: string): AgentScanDirs {
2342
+ // Resolve existing ancestors to canonicalize missing descendants of symlinks.
2343
+ function canonicalAgentPath(filePath: string): string {
2344
+ const resolved = path.resolve(filePath);
2345
+ try {
2346
+ return fs.realpathSync(resolved);
2347
+ } catch {
2348
+ const parent = path.dirname(resolved);
2349
+ return parent === resolved ? resolved : path.join(canonicalAgentPath(parent), path.basename(resolved));
2350
+ }
2351
+ }
2352
+
2353
+ function agentExclusionRoots(userSettingsPath: string, projectSettingsPath: string | null): Array<{ resolved: string; real: string }> {
2354
+ return [userSettingsPath, projectSettingsPath].flatMap((settingsPath) => settingsPath
2355
+ ? readConfiguredAgentScanDirs(settingsPath, "agentExcludeDirs").map((entry) => {
2356
+ const resolved = path.resolve(path.dirname(settingsPath), expandHomePath(entry.trim()).replace(/[\\/]+/g, path.sep));
2357
+ return { resolved, real: canonicalAgentPath(resolved) };
2358
+ }) : []);
2359
+ }
2360
+
2361
+ function agentExclusions(roots: Array<{ resolved: string; real: string }>): (filePath: string) => boolean {
2362
+ return (filePath) => {
2363
+ if (!roots.length) return false;
2364
+ if (roots.some((root) => isPathWithin(root.resolved, filePath))) return true;
2365
+ const real = canonicalAgentPath(filePath);
2366
+ return roots.some((root) => isPathWithin(root.real, real));
2367
+ };
2368
+ }
2369
+
2370
+ function expandAgentScanDirPattern(pattern: string, isExcluded: (filePath: string) => boolean): AgentScanDirs {
2279
2371
  const expanded = expandHomePath(pattern.trim()).replace(/[\\/]+/g, path.sep);
2280
2372
  if (!expanded) return { dirs: [], watchPaths: [] };
2281
2373
  const wildcardMatches = [...expanded.matchAll(/\*/g)];
2282
2374
  if (wildcardMatches.length === 0) {
2283
2375
  const dir = path.resolve(expanded);
2284
- return { dirs: fs.existsSync(dir) ? [dir] : [], watchPaths: [dir] };
2376
+ return { dirs: !isExcluded(dir) && fs.existsSync(dir) ? [dir] : [], watchPaths: [dir] };
2285
2377
  }
2286
2378
  const parts = expanded.split(path.sep);
2287
2379
  const wildcardIndex = parts.findIndex((part) => part.includes("*"));
2288
2380
  if (wildcardMatches.length !== 1 || wildcardIndex === -1 || parts[wildcardIndex] !== "*") return { dirs: [], watchPaths: [] };
2289
2381
  const base = path.resolve(parts.slice(0, wildcardIndex).join(path.sep) || path.sep);
2382
+ if (isExcluded(base)) return { dirs: [], watchPaths: [base] };
2290
2383
  const rest = parts.slice(wildcardIndex + 1);
2291
2384
  let entries: fs.Dirent[];
2292
2385
  try {
@@ -2296,16 +2389,16 @@ function expandAgentScanDirPattern(pattern: string): AgentScanDirs {
2296
2389
  }
2297
2390
  const candidateDirs = entries.filter((entry) => entry.isDirectory()).map((entry) => path.join(base, entry.name, ...rest));
2298
2391
  return {
2299
- dirs: candidateDirs.filter((dir) => fs.existsSync(dir)),
2392
+ dirs: candidateDirs.filter((dir) => !isExcluded(dir) && fs.existsSync(dir)),
2300
2393
  watchPaths: [base, ...candidateDirs],
2301
2394
  };
2302
2395
  }
2303
2396
 
2304
- function settingsAgentScanDirs(entries: string[]): AgentScanDirs {
2397
+ function settingsAgentScanDirs(entries: string[], isExcluded: (filePath: string) => boolean): AgentScanDirs {
2305
2398
  const dirs = new Set<string>();
2306
2399
  const watchPaths = new Set<string>();
2307
2400
  for (const entry of entries) {
2308
- const expanded = expandAgentScanDirPattern(entry);
2401
+ const expanded = expandAgentScanDirPattern(entry, isExcluded);
2309
2402
  for (const dir of expanded.dirs) dirs.add(dir);
2310
2403
  for (const watchPath of expanded.watchPaths) watchPaths.add(watchPath);
2311
2404
  }
@@ -2372,6 +2465,10 @@ interface AgentDiscoverySources {
2372
2465
  userChains?: ReturnType<typeof loadChainsFromDir>;
2373
2466
  projectChainLoaded?: Array<{ dir: string; loaded: ReturnType<typeof loadChainsFromDir> }>;
2374
2467
  packageChainLoaded?: Array<{ entry: PackageChainPath; loaded: ReturnType<typeof loadChainsFromDir> }>;
2468
+ isExcluded: (filePath: string) => boolean;
2469
+ exclusionRoots: Array<{ resolved: string; real: string }>;
2470
+ identityWatchPaths: string[];
2471
+ chainWatchPaths?: Set<string>;
2375
2472
  watchPaths: string[];
2376
2473
  }
2377
2474
 
@@ -2384,7 +2481,7 @@ const agentDiscoveryCache = new Map<string, AgentDiscoveryCacheEntry>();
2384
2481
 
2385
2482
  function isPathWithin(root: string, candidate: string): boolean {
2386
2483
  const relative = path.relative(path.resolve(root), path.resolve(candidate));
2387
- return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
2484
+ return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
2388
2485
  }
2389
2486
 
2390
2487
  function addDirectoryWatchPaths(paths: Set<string>, root: string, files: readonly string[], directories: readonly string[] = []): void {
@@ -2407,13 +2504,14 @@ function addDirectoryWatchPaths(paths: Set<string>, root: string, files: readonl
2407
2504
  }
2408
2505
  }
2409
2506
 
2410
- function watchPathSignature(filePath: string): string {
2507
+ function watchPathSignature(filePath: string, isExcluded?: (filePath: string) => boolean): string {
2411
2508
  try {
2412
2509
  const stat = fs.statSync(filePath);
2413
2510
  if (stat.isDirectory()) {
2414
2511
  const entries = fs.readdirSync(filePath, { withFileTypes: true })
2512
+ .filter((entry) => !isExcluded || !isExcluded(path.join(filePath, entry.name)))
2415
2513
  .sort((left, right) => left.name.localeCompare(right.name))
2416
- .map((entry) => `${entry.name}:${entry.isDirectory() ? "d" : entry.isSymbolicLink() ? "l" : "f"}`)
2514
+ .map((entry) => `${entry.name}:${entry.isDirectory() ? "d" : entry.isSymbolicLink() ? (isExcluded ? `l:${canonicalAgentPath(path.join(filePath, entry.name))}` : "l") : "f"}`)
2417
2515
  .join("|");
2418
2516
  return `directory:${entries}`;
2419
2517
  }
@@ -2423,10 +2521,17 @@ function watchPathSignature(filePath: string): string {
2423
2521
  }
2424
2522
  }
2425
2523
 
2426
- function discoveryFingerprint(paths: readonly string[]): string {
2427
- return [...new Set(paths)]
2524
+ function discoveryFingerprint(sources: AgentDiscoverySources): string {
2525
+ const exclusionIdentity = JSON.stringify(sources.exclusionRoots.map((root) => ({ resolved: root.resolved, real: canonicalAgentPath(root.resolved) })));
2526
+ // A retargeted exclusion needs a fresh scan before fingerprinting old roots.
2527
+ if (exclusionIdentity !== JSON.stringify(sources.exclusionRoots)) return exclusionIdentity;
2528
+ const rootIdentities = JSON.stringify(sources.identityWatchPaths.map((root) => [root, canonicalAgentPath(root)]));
2529
+ // Package metadata can redirect agents outside an excluded tree without chains.
2530
+ const unfilteredPaths = new Set([...sources.packageSubagentPaths.watchPaths, ...(sources.chainWatchPaths ?? [])]);
2531
+ return exclusionIdentity + "\n" + rootIdentities + "\n" + [...new Set([...sources.watchPaths, ...unfilteredPaths])]
2532
+ .filter((filePath) => unfilteredPaths.has(filePath) || filePath === sources.userSettingsPath || filePath === sources.projectSettingsPath || !sources.isExcluded(filePath))
2428
2533
  .sort((left, right) => left.localeCompare(right))
2429
- .map((filePath) => `${filePath}:${watchPathSignature(filePath)}`)
2534
+ .map((filePath) => `${filePath}:${watchPathSignature(filePath, sources.exclusionRoots.length && !unfilteredPaths.has(filePath) ? sources.isExcluded : undefined)}`)
2430
2535
  .join("\n");
2431
2536
  }
2432
2537
 
@@ -2472,21 +2577,23 @@ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string
2472
2577
  const userSettingsPath = getUserAgentSettingsPath();
2473
2578
  const projectSettingsPath = getProjectAgentSettingsPath(effectiveCwd);
2474
2579
  const packageSubagentPaths = collectPackageSubagentPaths(effectiveCwd);
2475
- const userScanDirs = settingsAgentScanDirs(readConfiguredAgentScanDirs(userSettingsPath));
2476
- const projectScanDirs = settingsAgentScanDirs(readConfiguredAgentScanDirs(projectSettingsPath));
2580
+ const exclusionRoots = agentExclusionRoots(userSettingsPath, projectSettingsPath);
2581
+ const isExcluded = agentExclusions(exclusionRoots);
2582
+ const userScanDirs = settingsAgentScanDirs(readConfiguredAgentScanDirs(userSettingsPath), isExcluded);
2583
+ const projectScanDirs = settingsAgentScanDirs(readConfiguredAgentScanDirs(projectSettingsPath), isExcluded);
2477
2584
 
2478
2585
  const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
2479
- const userLoaded = [...extraUserAgentDirs(), ...userScanDirs.dirs, userDirOld, userDirNew].map((dir, discoveryPriority): LoadedAgentDirectory => {
2480
- const inspection = inspectAgentDefinitionDirectory(dir);
2586
+ const userLoaded = [...extraUserAgentDirs(), ...userScanDirs.dirs, userDirOld, userDirNew].filter((dir) => !isExcluded(dir)).map((dir, discoveryPriority): LoadedAgentDirectory => {
2587
+ const inspection = inspectAgentDefinitionDirectory(dir, undefined, isExcluded);
2481
2588
  return { dir, inspection, loaded: loadAgentsFromDir(dir, "user", discoveryPriority, undefined, inspection) };
2482
2589
  });
2483
- const projectInspections = new Map(projectCandidateDirs.map((dir) => [dir, inspectAgentDefinitionDirectory(dir)]));
2484
- const projectLoaded = [...projectScanDirs.dirs, ...projectAgentDirs].map((dir, discoveryPriority): LoadedAgentDirectory => {
2485
- const inspection = projectInspections.get(dir) ?? inspectAgentDefinitionDirectory(dir);
2590
+ const projectInspections = new Map(projectCandidateDirs.filter((dir) => !isExcluded(dir)).map((dir) => [dir, inspectAgentDefinitionDirectory(dir, undefined, isExcluded)]));
2591
+ const projectLoaded = [...projectScanDirs.dirs, ...projectAgentDirs].filter((dir) => !isExcluded(dir)).map((dir, discoveryPriority): LoadedAgentDirectory => {
2592
+ const inspection = projectInspections.get(dir) ?? inspectAgentDefinitionDirectory(dir, undefined, isExcluded);
2486
2593
  return { dir, inspection, loaded: loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 : discoveryPriority, undefined, inspection) };
2487
2594
  });
2488
- const packageLoaded = packageSubagentPaths.agents.map((entry, index): LoadedAgentDirectory => {
2489
- const inspection = inspectAgentDefinitionDirectory(entry.dir);
2595
+ const packageLoaded = packageSubagentPaths.agents.filter((entry) => !isExcluded(entry.dir)).map((entry, index): LoadedAgentDirectory => {
2596
+ const inspection = inspectAgentDefinitionDirectory(entry.dir, undefined, isExcluded);
2490
2597
  return { dir: entry.dir, inspection, packageEntry: entry, loaded: loadAgentsFromDir(entry.dir, "package", packageSubagentPaths.agents.length - index, entry, inspection) };
2491
2598
  });
2492
2599
 
@@ -2516,7 +2623,7 @@ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string
2516
2623
  userDirNew,
2517
2624
  userChainDir,
2518
2625
  projectAgentDirs,
2519
- projectCandidateDirs,
2626
+ projectCandidateDirs: projectCandidateDirs.filter((dir) => !isExcluded(dir)),
2520
2627
  projectAgentsDir,
2521
2628
  projectChainDirs,
2522
2629
  projectChainDir,
@@ -2528,7 +2635,17 @@ function buildAgentDiscoverySources(cwd: string, preferredModelProvider?: string
2528
2635
  projectLoaded,
2529
2636
  projectInspections,
2530
2637
  packageLoaded,
2531
- watchPaths: [...watchPaths],
2638
+ isExcluded,
2639
+ exclusionRoots,
2640
+ // Aliases outside excluded lexical subtrees can be retargeted into allowed
2641
+ // storage. Watch only their identity, never the excluded target's contents.
2642
+ identityWatchPaths: [...new Set([
2643
+ ...userScanDirs.watchPaths, ...projectScanDirs.watchPaths,
2644
+ ...extraUserAgentDirs(), userDirOld, userDirNew, ...projectCandidateDirs,
2645
+ ...packageSubagentPaths.agents.map((entry) => entry.dir),
2646
+ ])]
2647
+ .filter((dir) => !exclusionRoots.some((root) => isPathWithin(root.resolved, dir)) && isExcluded(dir)),
2648
+ watchPaths: [...watchPaths].filter((filePath) => !isExcluded(filePath) || filePath === userSettingsPath || filePath === projectSettingsPath),
2532
2649
  };
2533
2650
  }
2534
2651
 
@@ -2537,29 +2654,30 @@ function ensureDiscoveryChains(sources: AgentDiscoverySources): void {
2537
2654
  sources.packageChainLoaded = sources.packageSubagentPaths.chains.map((entry) => ({ entry, loaded: loadChainsFromDir(entry.dir, "package") }));
2538
2655
  sources.userChains = loadChainsFromDir(sources.userChainDir, "user");
2539
2656
  sources.projectChainLoaded = sources.projectChainDirs.map((dir) => ({ dir, loaded: loadChainsFromDir(dir, "project") }));
2540
- const watchPaths = new Set(sources.watchPaths);
2657
+ const watchPaths = new Set(sources.packageSubagentPaths.watchPaths);
2541
2658
  addDirectoryWatchPaths(watchPaths, sources.userChainDir, sources.userChains.files, sources.userChains.directories);
2542
2659
  for (const chain of sources.projectChainLoaded) addDirectoryWatchPaths(watchPaths, chain.dir, chain.loaded.files, chain.loaded.directories);
2543
2660
  for (const chain of sources.packageChainLoaded) addDirectoryWatchPaths(watchPaths, chain.entry.dir, chain.loaded.files, chain.loaded.directories);
2544
- sources.watchPaths = [...watchPaths];
2661
+ sources.chainWatchPaths = watchPaths;
2662
+ sources.watchPaths = [...new Set([...sources.watchPaths, ...watchPaths])];
2545
2663
  }
2546
2664
 
2547
2665
  function getAgentDiscoverySources(cwd: string, preferredModelProvider?: string, includeChains = false): AgentDiscoverySources {
2548
2666
  const key = discoveryCacheKey(cwd, preferredModelProvider);
2549
2667
  const cached = agentDiscoveryCache.get(key);
2550
- if (cached && cached.fingerprint === discoveryFingerprint(cached.sources.watchPaths)) {
2668
+ if (cached && cached.fingerprint === discoveryFingerprint(cached.sources)) {
2551
2669
  if (includeChains) {
2552
2670
  ensureDiscoveryChains(cached.sources);
2553
- cached.fingerprint = discoveryFingerprint(cached.sources.watchPaths);
2671
+ cached.fingerprint = discoveryFingerprint(cached.sources);
2554
2672
  }
2555
2673
  return cached.sources;
2556
2674
  }
2557
2675
  const sources = buildAgentDiscoverySources(cwd, preferredModelProvider);
2558
- const entry = { sources, fingerprint: discoveryFingerprint(sources.watchPaths) };
2676
+ const entry = { sources, fingerprint: discoveryFingerprint(sources) };
2559
2677
  agentDiscoveryCache.set(key, entry);
2560
2678
  if (includeChains) {
2561
2679
  ensureDiscoveryChains(sources);
2562
- entry.fingerprint = discoveryFingerprint(sources.watchPaths);
2680
+ entry.fingerprint = discoveryFingerprint(sources);
2563
2681
  }
2564
2682
  return sources;
2565
2683
  }
@@ -2755,27 +2873,28 @@ function discoverAgentsUncached(cwd: string, scope: AgentScope, preferredModelPr
2755
2873
  const defaultExtensions = resolveSubagentDefaultExtensions(userSettings, projectSettings, projectSettingsPath);
2756
2874
  const modelScope = projectSettings.modelScope ?? userSettings.modelScope;
2757
2875
  const packageSubagentPaths = collectPackageSubagentPaths(effectiveCwd, { includeUser: scope !== "project", includeProject: scope !== "user" });
2876
+ const isExcluded = agentExclusions(agentExclusionRoots(userSettingsPath, projectSettingsPath));
2758
2877
  const directories: AgentDefinitionDirectoryReport[] = [reportAgentDefinitionDirectory("builtin", BUILTIN_AGENTS_DIR, BUILTIN_AGENT_DEFINITION_INSPECTION)];
2759
2878
  const builtinLoaded = loadAgentsFromDefinitionFiles(BUILTIN_AGENT_DEFINITION_FILES, "builtin");
2760
2879
  const builtinAgents = applyBuiltinOverrides(applySubagentDefaults(builtinLoaded.agents, defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
2761
- const userScanDirs = settingsAgentScanDirs(userSettings.agentScanDirs ?? []);
2762
- const projectScanDirs = settingsAgentScanDirs(projectSettings.agentScanDirs ?? []);
2763
- const userLoaded = scope === "project" ? [] : [...extraUserAgentDirs(), ...userScanDirs.dirs, userDirOld, userDirNew].map((dir, discoveryPriority) => {
2764
- const inspection = inspectAgentDefinitionDirectory(dir);
2880
+ const userScanDirs = settingsAgentScanDirs(userSettings.agentScanDirs ?? [], isExcluded);
2881
+ const projectScanDirs = settingsAgentScanDirs(projectSettings.agentScanDirs ?? [], isExcluded);
2882
+ const userLoaded = scope === "project" ? [] : [...extraUserAgentDirs(), ...userScanDirs.dirs, userDirOld, userDirNew].filter((dir) => !isExcluded(dir)).map((dir, discoveryPriority) => {
2883
+ const inspection = inspectAgentDefinitionDirectory(dir, undefined, isExcluded);
2765
2884
  directories.push(reportAgentDefinitionDirectory("user", dir, inspection));
2766
2885
  return loadAgentsFromDir(dir, "user", discoveryPriority, undefined, inspection);
2767
2886
  });
2768
2887
  const userAgents = applyCustomAgentOverrides(applySubagentDefaults(userLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
2769
- const projectInspections = scope === "user" ? new Map<string, AgentDefinitionInspection>() : new Map(projectCandidateDirs.map((dir) => [dir, inspectAgentDefinitionDirectory(dir)]));
2770
- if (scope !== "user") for (const dir of projectCandidateDirs) directories.push(reportAgentDefinitionDirectory("project", dir, projectInspections.get(dir)!));
2771
- const projectLoaded = scope === "user" ? [] : [...projectScanDirs.dirs, ...projectAgentDirs].map((dir, discoveryPriority) => {
2772
- const inspection = projectInspections.get(dir) ?? inspectAgentDefinitionDirectory(dir);
2888
+ const projectInspections = scope === "user" ? new Map<string, AgentDefinitionInspection>() : new Map(projectCandidateDirs.filter((dir) => !isExcluded(dir)).map((dir) => [dir, inspectAgentDefinitionDirectory(dir, undefined, isExcluded)]));
2889
+ if (scope !== "user") for (const [dir, inspection] of projectInspections) directories.push(reportAgentDefinitionDirectory("project", dir, inspection));
2890
+ const projectLoaded = scope === "user" ? [] : [...projectScanDirs.dirs, ...projectAgentDirs].filter((dir) => !isExcluded(dir)).map((dir, discoveryPriority) => {
2891
+ const inspection = projectInspections.get(dir) ?? inspectAgentDefinitionDirectory(dir, undefined, isExcluded);
2773
2892
  if (!projectInspections.has(dir)) directories.push(reportAgentDefinitionDirectory("project", dir, inspection));
2774
2893
  return loadAgentsFromDir(dir, "project", dir === projectAgentsDir ? 1 : discoveryPriority, undefined, inspection);
2775
2894
  });
2776
2895
  const projectAgents = applyCustomAgentOverrides(applySubagentDefaults(projectLoaded.flatMap((loaded) => loaded.agents), defaultModel, defaultProvider, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
2777
- const packageLoaded = packageSubagentPaths.agents.map((entry, index) => {
2778
- const inspection = inspectAgentDefinitionDirectory(entry.dir);
2896
+ const packageLoaded = packageSubagentPaths.agents.filter((entry) => !isExcluded(entry.dir)).map((entry, index) => {
2897
+ const inspection = inspectAgentDefinitionDirectory(entry.dir, undefined, isExcluded);
2779
2898
  directories.push(reportAgentDefinitionDirectory("package", entry.dir, inspection));
2780
2899
  return loadAgentsFromDir(entry.dir, "package", packageSubagentPaths.agents.length - index, entry, inspection);
2781
2900
  });