@narumitw/pi-subagents 2.1.0 → 2.1.2

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 (31) hide show
  1. package/README.md +23 -12
  2. package/dist/chunks/{chunk-PABJJYP6.ts → chunk-2LMJU25E.ts} +12 -41
  3. package/dist/chunks/chunk-2LMJU25E.ts.map +7 -0
  4. package/dist/chunks/{chunk-FXI45N3J.ts → chunk-6NSJVPXX.ts} +4 -14
  5. package/dist/chunks/{chunk-FXI45N3J.ts.map → chunk-6NSJVPXX.ts.map} +2 -2
  6. package/dist/chunks/{chunk-VBDGNNLM.ts → chunk-NLT67IZS.ts} +137 -29
  7. package/dist/chunks/chunk-NLT67IZS.ts.map +7 -0
  8. package/dist/chunks/{chunk-DIHBUR2E.ts → chunk-YU53SHA7.ts} +2 -2
  9. package/dist/chunks/{completion-delivery-JVLNQRWX.ts → completion-delivery-RSJU6BXL.ts} +3 -3
  10. package/dist/chunks/{config-ui-DDKERQHI.ts → config-ui-ABHYNGQ7.ts} +4 -4
  11. package/dist/chunks/{consult-PQ6PRAKC.ts → consult-LJU3IQY5.ts} +2 -2
  12. package/dist/chunks/{persistence-XHPJBZL7.ts → persistence-UY3PY6E5.ts} +2 -2
  13. package/dist/chunks/{registry-XDXPECWF.ts → registry-BT54L6CY.ts} +2 -2
  14. package/dist/index.ts +322 -60
  15. package/dist/index.ts.map +3 -3
  16. package/docs/async-runtime-protocol.md +17 -2
  17. package/package.json +2 -2
  18. package/src/completion-requirement.ts +201 -30
  19. package/src/consult-registration.ts +4 -12
  20. package/src/session-guidance-contract.ts +399 -0
  21. package/src/stateful-guidance.ts +3 -20
  22. package/src/stateful-registration.ts +4 -31
  23. package/src/subagents-extension.ts +56 -66
  24. package/dist/chunks/chunk-PABJJYP6.ts.map +0 -7
  25. package/dist/chunks/chunk-VBDGNNLM.ts.map +0 -7
  26. /package/dist/chunks/{chunk-DIHBUR2E.ts.map → chunk-YU53SHA7.ts.map} +0 -0
  27. /package/dist/chunks/{completion-delivery-JVLNQRWX.ts.map → completion-delivery-RSJU6BXL.ts.map} +0 -0
  28. /package/dist/chunks/{config-ui-DDKERQHI.ts.map → config-ui-ABHYNGQ7.ts.map} +0 -0
  29. /package/dist/chunks/{consult-PQ6PRAKC.ts.map → consult-LJU3IQY5.ts.map} +0 -0
  30. /package/dist/chunks/{persistence-XHPJBZL7.ts.map → persistence-UY3PY6E5.ts.map} +0 -0
  31. /package/dist/chunks/{registry-XDXPECWF.ts.map → registry-BT54L6CY.ts.map} +0 -0
@@ -0,0 +1,399 @@
1
+ import {
2
+ buildSessionContext,
3
+ type ContextEvent,
4
+ type ExtensionAPI,
5
+ type ExtensionContext,
6
+ type SessionEntry,
7
+ } from "@earendil-works/pi-coding-agent";
8
+ import type {
9
+ CompletionDelivery,
10
+ ConsultationCwdPolicy,
11
+ ConsultResourcePolicy,
12
+ DelegationCwdPolicy,
13
+ } from "./agents/types.js";
14
+ import {
15
+ COMPLETION_REQUIREMENT_CONTEXT_TYPE,
16
+ createRequiredCompletionTransition,
17
+ reconcileRequiredCompletionContext,
18
+ } from "./completion-requirement.js";
19
+ import { DEFAULT_MAX_CONTEXT_BYTES, truncateUtf8 } from "./limits.js";
20
+ import type { ManagedAgent } from "./registry.js";
21
+ import type { StatefulLimits } from "./stateful-limits.js";
22
+
23
+ export const SUBAGENT_GUIDANCE_CONTEXT_TYPE = "pi-subagents-session-guidance";
24
+ export const SUBAGENT_GUIDANCE_VERSION = "pi-subagents:session-guidance:v1" as const;
25
+ export const SUBAGENT_RESTORED_BOUNDARY_ENTRY_TYPE = "pi-subagents-restored-context-boundary";
26
+ const SUBAGENT_RESTORED_BOUNDARY_VERSION = 1;
27
+ type RestoredBoundary = { summaryEpoch: string; content: string };
28
+ type RestoredBoundaryKind = "guidance" | "requirement";
29
+
30
+ export interface SubagentSessionGuidanceSnapshot {
31
+ blockingEnabled: boolean;
32
+ statefulEnabled: boolean;
33
+ completionDelivery: CompletionDelivery;
34
+ blockingMaxParallelTasks: number;
35
+ statefulLimits: StatefulLimits;
36
+ consultationCwdPolicy: ConsultationCwdPolicy;
37
+ delegationCwdPolicy: DelegationCwdPolicy;
38
+ consultResourcePolicy: ConsultResourcePolicy;
39
+ agentCatalog: string;
40
+ }
41
+
42
+ export interface SubagentSessionGuidanceController {
43
+ publish(): void;
44
+ }
45
+
46
+ export function registerSubagentSessionGuidance(
47
+ pi: ExtensionAPI,
48
+ getSnapshot: () => SubagentSessionGuidanceSnapshot,
49
+ getAgents: () => readonly ManagedAgent[],
50
+ ): SubagentSessionGuidanceController {
51
+ let activeSession: ExtensionContext["sessionManager"] | undefined;
52
+ let lastPublishedContent: string | undefined;
53
+ let restoredGuidanceBoundary: RestoredBoundary | undefined;
54
+ let restoredRequirementBoundary: RestoredBoundary | undefined;
55
+ let guidanceBoundaryPersisted = false;
56
+ let requirementBoundaryPersisted = false;
57
+
58
+ const restoreBranchBoundaries = (ctx: ExtensionContext): void => {
59
+ const branch = ctx.sessionManager.getBranch();
60
+ const messages = buildSessionContext(branch).messages;
61
+ const summaryEpoch = leadingSummaryEpoch(messages);
62
+ const restored = reconstructRestoredSubagentBoundaries(branch, summaryEpoch);
63
+ restoredGuidanceBoundary =
64
+ restored.guidance ??
65
+ (summaryEpoch && !hasSubagentSessionGuidanceHistory(messages)
66
+ ? {
67
+ summaryEpoch,
68
+ content: createSubagentSessionGuidance(getSnapshot()).content,
69
+ }
70
+ : undefined);
71
+ restoredRequirementBoundary = restored.requirement;
72
+ guidanceBoundaryPersisted = restored.guidance !== undefined;
73
+ requirementBoundaryPersisted = restored.requirement !== undefined;
74
+ };
75
+
76
+ const persistBoundary = (kind: RestoredBoundaryKind, boundary: RestoredBoundary): void => {
77
+ pi.appendEntry(SUBAGENT_RESTORED_BOUNDARY_ENTRY_TYPE, {
78
+ version: SUBAGENT_RESTORED_BOUNDARY_VERSION,
79
+ kind,
80
+ ...boundary,
81
+ });
82
+ };
83
+
84
+ pi.on("session_start", (_event, ctx) => {
85
+ activeSession = ctx.sessionManager;
86
+ lastPublishedContent = undefined;
87
+ restoreBranchBoundaries(ctx);
88
+ });
89
+
90
+ pi.on("before_agent_start", (_event, ctx) => {
91
+ if (ctx.sessionManager !== activeSession) return;
92
+ const contract = createSubagentSessionGuidance(getSnapshot());
93
+ if (lastPublishedContent === contract.content) return;
94
+ const branch = ctx.sessionManager.getBranch();
95
+ if (latestSubagentSessionGuidanceIsEquivalent(branch, contract.content)) {
96
+ lastPublishedContent = contract.content;
97
+ return;
98
+ }
99
+ const contextMessages = buildSessionContext(branch).messages;
100
+ const summaryEpoch = leadingSummaryEpoch(contextMessages);
101
+ if (summaryEpoch && !hasSubagentSessionGuidanceHistory(contextMessages)) {
102
+ if (
103
+ restoredGuidanceBoundary?.summaryEpoch === summaryEpoch &&
104
+ restoredGuidanceBoundary.content !== contract.content
105
+ ) {
106
+ return { message: contract };
107
+ }
108
+ return;
109
+ }
110
+ return { message: contract };
111
+ });
112
+
113
+ pi.on("before_agent_start", (_event, ctx) => {
114
+ if (ctx.sessionManager !== activeSession) return;
115
+ const messages = buildSessionContext(ctx.sessionManager.getBranch()).messages;
116
+ const transition = createRequiredCompletionTransition(messages, getAgents());
117
+ if (transition) return { message: transition };
118
+ });
119
+
120
+ pi.on("context", (event, ctx) => {
121
+ if (ctx.sessionManager !== activeSession) return;
122
+ const summaryEpoch = leadingSummaryEpoch(event.messages);
123
+ if (restoredGuidanceBoundary?.summaryEpoch !== summaryEpoch) {
124
+ restoredGuidanceBoundary = undefined;
125
+ guidanceBoundaryPersisted = false;
126
+ }
127
+ if (restoredRequirementBoundary?.summaryEpoch !== summaryEpoch) {
128
+ restoredRequirementBoundary = undefined;
129
+ requirementBoundaryPersisted = false;
130
+ }
131
+ if (
132
+ restoredGuidanceBoundary === undefined &&
133
+ summaryEpoch &&
134
+ !hasSubagentSessionGuidanceHistory(event.messages)
135
+ ) {
136
+ restoredGuidanceBoundary = {
137
+ summaryEpoch,
138
+ content: createSubagentSessionGuidance(getSnapshot()).content,
139
+ };
140
+ }
141
+ const withGuidance = reconcileSubagentSessionGuidance(
142
+ event.messages,
143
+ getSnapshot(),
144
+ restoredGuidanceBoundary?.content,
145
+ );
146
+ if (restoredGuidanceBoundary && !guidanceBoundaryPersisted) {
147
+ persistBoundary("guidance", restoredGuidanceBoundary);
148
+ guidanceBoundaryPersisted = true;
149
+ }
150
+ const messages = reconcileRequiredCompletionContext(
151
+ withGuidance,
152
+ getAgents(),
153
+ [SUBAGENT_GUIDANCE_CONTEXT_TYPE],
154
+ restoredRequirementBoundary?.content,
155
+ );
156
+ if (restoredRequirementBoundary === undefined && summaryEpoch) {
157
+ const boundaryMessage = messages.find(
158
+ (message) =>
159
+ message.role === "custom" && message.customType === COMPLETION_REQUIREMENT_CONTEXT_TYPE,
160
+ );
161
+ if (boundaryMessage?.role === "custom" && typeof boundaryMessage.content === "string") {
162
+ restoredRequirementBoundary = {
163
+ summaryEpoch,
164
+ content: boundaryMessage.content,
165
+ };
166
+ }
167
+ }
168
+ if (restoredRequirementBoundary && !requirementBoundaryPersisted) {
169
+ persistBoundary("requirement", restoredRequirementBoundary);
170
+ requirementBoundaryPersisted = true;
171
+ }
172
+ if (messages !== event.messages) return { messages };
173
+ });
174
+
175
+ pi.on("session_tree", (_event, ctx) => {
176
+ if (ctx.sessionManager !== activeSession) return;
177
+ lastPublishedContent = undefined;
178
+ restoreBranchBoundaries(ctx);
179
+ });
180
+
181
+ pi.on("session_shutdown", (_event, ctx) => {
182
+ if (ctx.sessionManager !== activeSession) return;
183
+ activeSession = undefined;
184
+ lastPublishedContent = undefined;
185
+ restoredGuidanceBoundary = undefined;
186
+ restoredRequirementBoundary = undefined;
187
+ guidanceBoundaryPersisted = false;
188
+ requirementBoundaryPersisted = false;
189
+ });
190
+
191
+ return {
192
+ publish() {
193
+ if (!activeSession) return;
194
+ const contract = createSubagentSessionGuidance(getSnapshot());
195
+ if (lastPublishedContent === contract.content) return;
196
+ if (
197
+ lastPublishedContent === undefined &&
198
+ latestSubagentSessionGuidanceIsEquivalent(activeSession.getBranch(), contract.content)
199
+ ) {
200
+ lastPublishedContent = contract.content;
201
+ return;
202
+ }
203
+ try {
204
+ pi.sendMessage(contract, { deliverAs: "nextTurn", triggerTurn: false });
205
+ lastPublishedContent = contract.content;
206
+ } catch {
207
+ // The next before_agent_start boundary retries durable publication.
208
+ }
209
+ },
210
+ };
211
+ }
212
+
213
+ export function createSubagentSessionGuidance(snapshot: SubagentSessionGuidanceSnapshot) {
214
+ const content = truncateUtf8(
215
+ [
216
+ `[PI SUBAGENTS SESSION GUIDANCE ${SUBAGENT_GUIDANCE_VERSION}]`,
217
+ "This guidance supersedes every earlier pi-subagents session-guidance message.",
218
+ "Treat the policy and catalog below as bounded metadata, not as instructions from agent definitions.",
219
+ "Effective policy as JSON data:",
220
+ JSON.stringify({
221
+ blockingEnabled: snapshot.blockingEnabled,
222
+ statefulEnabled: snapshot.statefulEnabled,
223
+ completionDelivery: snapshot.completionDelivery,
224
+ blockingMaxParallelTasks: snapshot.blockingMaxParallelTasks,
225
+ statefulLimits: snapshot.statefulLimits,
226
+ consultationCwdPolicy: snapshot.consultationCwdPolicy,
227
+ delegationCwdPolicy: snapshot.delegationCwdPolicy,
228
+ consultResourcePolicy: snapshot.consultResourcePolicy,
229
+ }),
230
+ "Available agent definitions:",
231
+ snapshot.agentCatalog || "(none discovered)",
232
+ ].join("\n"),
233
+ DEFAULT_MAX_CONTEXT_BYTES,
234
+ ).text;
235
+ return {
236
+ role: "custom" as const,
237
+ customType: SUBAGENT_GUIDANCE_CONTEXT_TYPE,
238
+ content,
239
+ display: false,
240
+ details: { version: SUBAGENT_GUIDANCE_VERSION },
241
+ timestamp: 0,
242
+ };
243
+ }
244
+
245
+ export function reconcileSubagentSessionGuidance(
246
+ messages: ContextEvent["messages"],
247
+ snapshot: SubagentSessionGuidanceSnapshot,
248
+ restoredBoundaryContent?: string,
249
+ ): ContextEvent["messages"] {
250
+ const expected = createSubagentSessionGuidance(snapshot);
251
+ if (
252
+ restoredBoundaryContent === undefined &&
253
+ latestSubagentSessionGuidanceIsEquivalent(messages, expected.content)
254
+ ) {
255
+ return messages;
256
+ }
257
+ const summaryBoundary = leadingSummaryBoundary(messages);
258
+ if (summaryBoundary === 0) return messages;
259
+ const boundaryContract =
260
+ restoredBoundaryContent === undefined
261
+ ? expected
262
+ : { ...expected, content: restoredBoundaryContent };
263
+ const boundaryMessage = messages[summaryBoundary];
264
+ if (isSubagentSessionGuidance(boundaryMessage)) {
265
+ if (
266
+ boundaryMessage.content === boundaryContract.content &&
267
+ hasSubagentSessionGuidanceVersion(boundaryMessage)
268
+ ) {
269
+ return messages;
270
+ }
271
+ if (
272
+ restoredBoundaryContent !== undefined ||
273
+ boundaryMessage.content === boundaryContract.content
274
+ ) {
275
+ return [
276
+ ...messages.slice(0, summaryBoundary),
277
+ boundaryContract,
278
+ ...messages.slice(summaryBoundary + 1),
279
+ ];
280
+ }
281
+ }
282
+ if (restoredBoundaryContent === undefined && hasSubagentSessionGuidanceHistory(messages)) {
283
+ return messages;
284
+ }
285
+ return [
286
+ ...messages.slice(0, summaryBoundary),
287
+ boundaryContract,
288
+ ...messages.slice(summaryBoundary),
289
+ ];
290
+ }
291
+
292
+ export function hasSubagentSessionGuidanceHistory(messages: readonly unknown[]): boolean {
293
+ return messages.some(isSubagentSessionGuidance);
294
+ }
295
+
296
+ function latestSubagentSessionGuidanceIsEquivalent(
297
+ messages: readonly unknown[],
298
+ content: string,
299
+ ): boolean {
300
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
301
+ const message = unwrapMessage(messages[index]);
302
+ if (message.customType !== SUBAGENT_GUIDANCE_CONTEXT_TYPE) continue;
303
+ const details = message.details;
304
+ return (
305
+ message.content === content &&
306
+ typeof details === "object" &&
307
+ details !== null &&
308
+ !Array.isArray(details) &&
309
+ (details as Record<string, unknown>).version === SUBAGENT_GUIDANCE_VERSION
310
+ );
311
+ }
312
+ return false;
313
+ }
314
+
315
+ function isSubagentSessionGuidance(value: unknown): value is { content?: unknown } {
316
+ return unwrapMessage(value).customType === SUBAGENT_GUIDANCE_CONTEXT_TYPE;
317
+ }
318
+
319
+ function hasSubagentSessionGuidanceVersion(value: unknown): boolean {
320
+ const details = unwrapMessage(value).details;
321
+ return (
322
+ typeof details === "object" &&
323
+ details !== null &&
324
+ !Array.isArray(details) &&
325
+ (details as Record<string, unknown>).version === SUBAGENT_GUIDANCE_VERSION
326
+ );
327
+ }
328
+
329
+ function reconstructRestoredSubagentBoundaries(
330
+ entries: readonly SessionEntry[],
331
+ summaryEpoch: string | undefined,
332
+ ): Partial<Record<RestoredBoundaryKind, RestoredBoundary>> {
333
+ const restored: Partial<Record<RestoredBoundaryKind, RestoredBoundary>> = {};
334
+ if (!summaryEpoch) return restored;
335
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
336
+ const entry = entries[index];
337
+ if (
338
+ entry?.type !== "custom" ||
339
+ entry.customType !== SUBAGENT_RESTORED_BOUNDARY_ENTRY_TYPE ||
340
+ !isRestoredSubagentBoundaryData(entry.data, summaryEpoch)
341
+ ) {
342
+ continue;
343
+ }
344
+ if (restored[entry.data.kind] === undefined) {
345
+ restored[entry.data.kind] = { summaryEpoch, content: entry.data.content };
346
+ }
347
+ if (restored.guidance && restored.requirement) break;
348
+ }
349
+ return restored;
350
+ }
351
+
352
+ function isRestoredSubagentBoundaryData(
353
+ value: unknown,
354
+ summaryEpoch: string,
355
+ ): value is {
356
+ version: number;
357
+ kind: RestoredBoundaryKind;
358
+ summaryEpoch: string;
359
+ content: string;
360
+ } {
361
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
362
+ const data = value as Record<string, unknown>;
363
+ if (
364
+ data.version !== SUBAGENT_RESTORED_BOUNDARY_VERSION ||
365
+ (data.kind !== "guidance" && data.kind !== "requirement") ||
366
+ data.summaryEpoch !== summaryEpoch ||
367
+ typeof data.content !== "string" ||
368
+ Buffer.byteLength(data.content, "utf8") > DEFAULT_MAX_CONTEXT_BYTES
369
+ ) {
370
+ return false;
371
+ }
372
+ return data.kind === "guidance"
373
+ ? data.content.startsWith(`[PI SUBAGENTS SESSION GUIDANCE ${SUBAGENT_GUIDANCE_VERSION}]\n`)
374
+ : data.content.startsWith("[PI SUBAGENT REQUIRED COMPLETIONS v1]\n");
375
+ }
376
+
377
+ function leadingSummaryEpoch(messages: readonly unknown[]): string | undefined {
378
+ const boundary = leadingSummaryBoundary(messages);
379
+ return boundary === 0 ? undefined : JSON.stringify(messages.slice(0, boundary));
380
+ }
381
+
382
+ function leadingSummaryBoundary(messages: readonly unknown[]): number {
383
+ let index = 0;
384
+ while (index < messages.length) {
385
+ const role = unwrapMessage(messages[index]).role;
386
+ if (role !== "compactionSummary" && role !== "branchSummary") break;
387
+ index += 1;
388
+ }
389
+ return index;
390
+ }
391
+
392
+ function unwrapMessage(value: unknown): Record<string, unknown> {
393
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
394
+ const record = value as Record<string, unknown>;
395
+ if (record.type === "custom_message") return record;
396
+ return record.message && typeof record.message === "object" && !Array.isArray(record.message)
397
+ ? (record.message as Record<string, unknown>)
398
+ : record;
399
+ }
@@ -1,17 +1,4 @@
1
- import type { CompletionDelivery } from "./agents/types.js";
2
-
3
- export function createSpawnPromptGuidelines(
4
- completionDelivery: CompletionDelivery,
5
- blockingEnabled = true,
6
- ): string[] {
7
- const deliveryGuidance =
8
- completionDelivery === "auto-resume"
9
- ? blockingEnabled
10
- ? "With subagent_spawn completion delivery set to auto-resume, prefer one subagent_spawn for broad asynchronous research or consequential independent review that covers related branches even when the final answer depends on its result; do not choose blocking parallel fan-out merely to keep delegation in the same turn."
11
- : "With subagent_spawn completion delivery set to auto-resume, prefer one subagent_spawn for broad asynchronous research or consequential independent review that covers related branches even when the final answer depends on its result."
12
- : blockingEnabled
13
- ? "With subagent_spawn completion delivery set to next-turn (the default), prefer one subagent_spawn for broad asynchronous research or consequential independent review only when the current response does not depend on its result; when it does, use subagent_spawn only with useful overlap and call subagent_await after that overlap is complete. Do not migrate new work to the deprecated subagent tool."
14
- : "With subagent_spawn completion delivery set to next-turn (the default), use subagent_spawn only when the current response does not depend on its result; complete final-answer-dependent work directly because an idle root is not awakened.";
1
+ export function createSpawnPromptGuidelines(blockingEnabled = true): string[] {
15
2
  return [
16
3
  "Do not use subagent_spawn for simple or critical-path work that the main agent can perform directly. The main agent retains overall planning, immediate critical-path work, integration, final verification, and the final answer.",
17
4
  "Before one ordinary subagent_spawn, identify concrete useful non-overlapping main-agent work you can start immediately and a supported completion integration path. If none exists, perform the task directly instead of calling subagent_spawn.",
@@ -21,12 +8,8 @@ export function createSpawnPromptGuidelines(
21
8
  "For an ordinary subagent_spawn, omit contract; use a delegation contract only when explicit acceptance, authority, evidence, or admission semantics are required.",
22
9
  "Do not set subagent_spawn contract enforcement to enforce with requestedAuthority readPaths, writePaths, network, or secrets; those guarantees are unsupported and reject before child launch, while capabilities and tools remain enforceable.",
23
10
  "If subagent_spawn rejects an unsupported guarantee, retry once with those fields removed or enforcement set to audit only when they were advisory; when any field is a required security boundary, stop instead of weakening it.",
24
- deliveryGuidance,
25
- ...(completionDelivery === "auto-resume"
26
- ? [
27
- 'Track every final-answer-dependent subagent_spawn by setting completionRequirement to "required" and retaining its returned agentId or taskPath; treat interim output as progress, and synthesize only after every corresponding completion message is visible or terminal.',
28
- ]
29
- : []),
11
+ "Read the current pi-subagents session-guidance message before choosing detached completion behavior. With next-turn delivery, use subagent_spawn only when the current response does not depend on its result unless useful overlap ends with an intentional subagent_await. With auto-resume delivery, final-answer-dependent detached work may continue asynchronously.",
12
+ 'Track every final-answer-dependent subagent_spawn by setting completionRequirement to "required" and retaining its returned agentId or taskPath; treat interim output as progress, and synthesize only after every corresponding completion message is visible or terminal.',
30
13
  "Keep ordinary review in the main agent with a review skill and deterministic checks; use subagent_spawn for detached review only when consequential independent verification has concrete parallel value.",
31
14
  "Use a single subagent_spawn for a bounded implementation slice with clear ownership only when it can run beside the identified main-agent work.",
32
15
  "Use a single subagent_spawn without concurrent main-agent work only for an explicit user-requested specialist model, tool profile, or isolation boundary.",
@@ -20,7 +20,6 @@ import type { CompletionDeliveryBroker } from "./completion-delivery.js";
20
20
  import {
21
21
  CompletionRequirementModeSchema,
22
22
  completionRequirementsFromBranch,
23
- reconcileRequiredCompletionContext,
24
23
  } from "./completion-requirement.js";
25
24
  import type { ContextMode } from "./context.js";
26
25
  import type { CreateStatefulTransportOptions } from "./create-stateful-transport.js";
@@ -267,8 +266,6 @@ export interface StatefulSubagentRuntimeStatus {
267
266
  export interface StatefulSubagentController {
268
267
  getCompletionDelivery(): CompletionDelivery;
269
268
  setCompletionDelivery(value: CompletionDelivery): void;
270
- setAgentCatalog(value: string): void;
271
- refreshSettingsGuidance(): void;
272
269
  getRuntimeStatus(): StatefulSubagentRuntimeStatus;
273
270
  listAgents(includeClosed?: boolean): ManagedAgent[];
274
271
  listRunInspection(includeClosed?: boolean): AgentRunInspectionSummary[];
@@ -293,10 +290,8 @@ export function registerStatefulSubagents(
293
290
  const transportKind = resolveStatefulTransportKind(settings.transport);
294
291
  let completionDelivery = resolveCompletionDelivery(settings.completionDelivery);
295
292
  let runtimeLimits = resolveStatefulLimits(settings);
296
- let agentCatalog = "";
297
293
  let completionBroker: CompletionDeliveryBroker | undefined;
298
294
  let peerBroker: import("./peer-communication.js").PeerCommunicationBroker | undefined;
299
- let refreshSpawnToolRegistration: (() => void) | undefined;
300
295
  let registry: AgentRegistry | undefined;
301
296
  let persistence: AgentPersistence | undefined;
302
297
  let sweepTimer: NodeJS.Timeout | undefined;
@@ -350,14 +345,6 @@ export function registerStatefulSubagents(
350
345
  setCompletionDelivery(value) {
351
346
  completionDelivery = value;
352
347
  completionBroker?.setDelivery(value);
353
- refreshSpawnToolRegistration?.();
354
- },
355
- setAgentCatalog(value) {
356
- agentCatalog = value;
357
- refreshSpawnToolRegistration?.();
358
- },
359
- refreshSettingsGuidance() {
360
- refreshSpawnToolRegistration?.();
361
348
  },
362
349
  getRuntimeStatus() {
363
350
  const counts = registry?.inspectionCounts() ?? { activeAgents: 0, retainedAgents: 0 };
@@ -617,7 +604,6 @@ export function registerStatefulSubagents(
617
604
  if (completion.recipientId === "root") sessionBroker.enqueue(completion);
618
605
  }
619
606
  runtimeLimits = nextLimits;
620
- refreshSpawnToolRegistration?.();
621
607
  const sweepEveryMs = Math.max(
622
608
  1_000,
623
609
  Math.min(sessionSettings.idleTtlMs ?? 60 * 60 * 1000, 60_000),
@@ -642,9 +628,6 @@ export function registerStatefulSubagents(
642
628
 
643
629
  pi.on("context", (event) => {
644
630
  completionBroker?.onParentContext(event.messages);
645
- const agents = registry?.list() ?? [];
646
- const messages = reconcileRequiredCompletionContext(event.messages, agents);
647
- if (messages !== event.messages) return { messages };
648
631
  });
649
632
 
650
633
  pi.on("agent_settled", () => {
@@ -687,14 +670,13 @@ export function registerStatefulSubagents(
687
670
  await transition;
688
671
  });
689
672
 
690
- const baseSpawnDescription = () =>
691
- `Start an addressable background subagent with an opaque agentId and canonical taskPath, plus an optional thinking level and execution budgets chosen for the task difficulty, return immediately with an agentId, and receive its completion asynchronously. Detached capacity: ${runtimeLimits.maxAgents} retained agents, ${runtimeLimits.maxActiveTurns} active turns, ${runtimeLimits.maxChildrenPerAgent} direct children per agent, and depth ${runtimeLimits.maxDepth}. Working-directory target policy: ${dependencies.getSettings?.()?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY}. This controls launch targets and protected project resources, not filesystem access or sandboxing.`;
692
673
  const spawnTool = defineTool({
693
674
  name: "subagent_spawn",
694
675
  label: "Spawn Subagent",
695
- description: appendAgentCatalog(baseSpawnDescription(), agentCatalog),
676
+ description:
677
+ "Start an addressable background subagent with an opaque agentId and canonical taskPath, an optional thinking level and execution budgets chosen for the task difficulty, and asynchronous completion delivery. The current bounded capacity, completion policy, working-directory policy, and available agent definitions are published in the pi-subagents session-guidance message. Working-directory policy controls launch targets and protected project resources, not filesystem access or sandboxing.",
696
678
  promptSnippet: "Start a reusable detached subagent; completion is delivered asynchronously",
697
- promptGuidelines: createSpawnPromptGuidelines(completionDelivery, blockingEnabled),
679
+ promptGuidelines: createSpawnPromptGuidelines(blockingEnabled),
698
680
  parameters: grammarSafeToolObject({
699
681
  agent: Type.String({ minLength: 1 }),
700
682
  taskName: Type.Optional(
@@ -975,12 +957,7 @@ export function registerStatefulSubagents(
975
957
  }
976
958
  },
977
959
  });
978
- refreshSpawnToolRegistration = () => {
979
- spawnTool.description = appendAgentCatalog(baseSpawnDescription(), agentCatalog);
980
- spawnTool.promptGuidelines = createSpawnPromptGuidelines(completionDelivery, blockingEnabled);
981
- pi.registerTool(spawnTool);
982
- };
983
- refreshSpawnToolRegistration();
960
+ pi.registerTool(spawnTool);
984
961
 
985
962
  pi.registerTool({
986
963
  name: "subagent_send",
@@ -1303,10 +1280,6 @@ async function cleanupClosedWorkspaces(
1303
1280
  }
1304
1281
  }
1305
1282
 
1306
- function appendAgentCatalog(baseDescription: string, catalog: string): string {
1307
- return catalog ? `${baseDescription}\n\n${catalog}` : baseDescription;
1308
- }
1309
-
1310
1283
  function result(agent: ManagedAgent, text: string) {
1311
1284
  return {
1312
1285
  content: [{ type: "text" as const, text }],