@oai404iao/pi-subagent 0.4.0-alpha.0 → 0.4.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.
@@ -123,12 +123,12 @@ import {
123
123
  type SubagentProviderName,
124
124
  type SubagentRunResult,
125
125
  type SubagentSettings,
126
+ type RuntimeMode,
126
127
  type SubagentStopReason,
127
128
  type TraceItem,
128
129
  } from "./types.ts";
129
130
 
130
131
  const REPORT_CUSTOM_TYPE = "pi-subagent/report";
131
- const SETTLED_CUSTOM_TYPE = "pi-subagent/settled";
132
132
  const AGENT_CUSTOM_TYPE = "pi-subagent/agent";
133
133
  const LINEAGE_CUSTOM_TYPE = "pi-subagent/lineage";
134
134
  const AGENT_ID_PATTERN =
@@ -153,7 +153,8 @@ const DELEGATION_SCOPE_PROMPT = [
153
153
  const REPORT_PROMPT = [
154
154
  "You have a `report` tool that sends a selected update to the agent that started you.",
155
155
  "Call it with a self-contained answer before finishing, and earlier when a finding changes what the parent should do.",
156
- "Reporting does not end this turn and does not prevent later follow-up messages.",
156
+ "A report is recorded for the parent without waking it; it does not end this turn and does not prevent later follow-up messages.",
157
+ "Your final answer is delivered separately as a completion update.",
157
158
  ].join(" ");
158
159
 
159
160
  export interface DelegationInput {
@@ -161,7 +162,6 @@ export interface DelegationInput {
161
162
  task_name?: string;
162
163
  description: string;
163
164
  prompt: string;
164
- run_in_background?: boolean;
165
165
  context?: {
166
166
  mode: ContextInheritance["mode"];
167
167
  completed_turns?: number;
@@ -172,15 +172,13 @@ export type DelegationOutcome =
172
172
  | { kind: "continuable"; details: DelegationDetails }
173
173
  | { kind: "foreground"; details: DelegationDetails; result: SubagentRunResult };
174
174
 
175
- export type SendMessageOutcome =
176
- | { kind: "legacy"; agentId: string; taskPath: string }
177
- | {
178
- kind: "mailbox-v2";
179
- agentId: string;
180
- taskPath: string;
181
- messageId: string;
182
- pendingMessages: number;
183
- };
175
+ export interface SendMessageOutcome {
176
+ kind: "mailbox";
177
+ agentId: string;
178
+ taskPath: string;
179
+ messageId: string;
180
+ pendingMessages: number;
181
+ }
184
182
 
185
183
  export interface FollowupTaskOutcome {
186
184
  agentId: string;
@@ -218,7 +216,6 @@ interface ParentRef {
218
216
  customType: string,
219
217
  content: string,
220
218
  details: ParentMessageDetails,
221
- delivery: "wakeup" | "quiet",
222
219
  ): Promise<void>;
223
220
  }
224
221
 
@@ -240,7 +237,6 @@ interface Activation {
240
237
  userMessageGates: Map<string, UserMessageGate>;
241
238
  ownedChildren: Set<string>;
242
239
  currentRun?: Promise<SubagentRunResult>;
243
- currentMessageGate?: Promise<void>;
244
240
  pendingSettlement?: SubagentRunResult;
245
241
  unsubscribe?: () => void;
246
242
  onUpdate?: (details: DelegationDetails) => void;
@@ -301,14 +297,7 @@ interface PendingPromptStart {
301
297
  coldPrepared?: PreparedChildSession;
302
298
  }
303
299
 
304
- type SerializedSendMessageOutcome =
305
- | {
306
- kind: "legacy";
307
- agentId: string;
308
- taskPath: string;
309
- pending?: PendingPromptStart;
310
- }
311
- | Extract<SendMessageOutcome, { kind: "mailbox-v2" }>;
300
+ type SerializedSendMessageOutcome = SendMessageOutcome;
312
301
 
313
302
  interface PendingFollowupStart extends PendingPromptStart {
314
303
  outcome: FollowupTaskOutcome;
@@ -490,12 +479,9 @@ function makeRuntimeSettings(descriptor: SubagentDescriptor): SubagentSettings {
490
479
  return {
491
480
  agentScope: descriptor.runtime.agentScope,
492
481
  maxDepth: descriptor.runtime.maxDepth,
493
- enableRunInBackground: descriptor.runtime.enableRunInBackground,
494
- defaultBackground: descriptor.runtime.defaultBackground,
482
+ runtimeMode: descriptor.runtime.runtimeMode,
495
483
  maxConcurrentBackgroundRuns: descriptor.runtime.maxConcurrentBackgroundRuns,
496
484
  maxIdleRuntimes: descriptor.runtime.maxIdleRuntimes,
497
- backgroundProtocol: descriptor.runtime.backgroundProtocol,
498
- reportDelivery: descriptor.runtime.reportDelivery,
499
485
  inheritExtensions: descriptor.runtime.inheritExtensions,
500
486
  openAIIdentity: descriptor.runtime.openAIIdentity,
501
487
  maxOutputBytes: descriptor.runtime.maxOutputBytes,
@@ -698,13 +684,13 @@ export class SubagentCoordinator {
698
684
  model: ctx.model,
699
685
  thinkingLevel: ctx.thinkingLevel ?? "off",
700
686
  projectTrusted: ctx.isProjectTrusted(),
701
- deliver: async (customType, content, details, delivery) => {
702
- const options =
703
- delivery === "wakeup"
704
- ? { triggerTurn: true, deliverAs: "followUp" as const }
705
- : ctx.isIdle()
706
- ? { triggerTurn: false }
707
- : { triggerTurn: false, deliverAs: "nextTurn" as const };
687
+ // Reports never start a parent turn. The entry becomes part of the
688
+ // parent session (and therefore its next model context) while the
689
+ // parent keeps working; durable completions are read with wait_agent.
690
+ deliver: async (customType, content, details) => {
691
+ const options = ctx.isIdle()
692
+ ? { triggerTurn: false }
693
+ : { triggerTurn: false, deliverAs: "nextTurn" as const };
708
694
  this.pi.sendMessage({ customType, content, display: true, details }, options);
709
695
  },
710
696
  };
@@ -746,32 +732,19 @@ export class SubagentCoordinator {
746
732
  const resolvedProviderName: SubagentProviderName =
747
733
  context.mode === "fresh" ? "spawn" : "fork";
748
734
  const provider = this.providers.get(resolvedProviderName);
749
- if (
750
- !settings.enableRunInBackground &&
751
- input.run_in_background === true
752
- ) {
753
- throw new Error(
754
- "run_in_background is disabled by pi-subagent foreground-only mode (enableRunInBackground: false)",
755
- );
756
- }
757
- const runInBackground =
758
- settings.enableRunInBackground
759
- ? providerName === "fork"
760
- ? (input.run_in_background ?? false)
761
- : (input.run_in_background ?? settings.defaultBackground)
762
- : false;
763
- const mode: SubagentMode = runInBackground ? "continuable" : "one-shot";
735
+ const mode: SubagentMode =
736
+ settings.runtimeMode === "background" ? "continuable" : "one-shot";
764
737
  if (mode === "continuable" && !provider.supportsContinuable) {
765
738
  throw new Error(`subagent provider "${provider.name}" does not support continuable children`);
766
739
  }
767
740
  if (mode === "continuable" && !parent.sessionManager.getSessionFile()) {
768
741
  throw new Error(
769
- "continuable subagents require a persisted parent session; set run_in_background to false",
742
+ 'continuable subagents require a persisted parent session; set runtimeMode to "foreground" to delegate from an ephemeral session',
770
743
  );
771
744
  }
772
745
  if (mode === "continuable" && parent.activation?.descriptor.mode === "one-shot") {
773
746
  throw new Error(
774
- "a one-shot child cannot leave a continuable descendant behind; set run_in_background to false",
747
+ "a foreground child cannot leave a continuable descendant behind; its runtime mode is fixed at creation",
775
748
  );
776
749
  }
777
750
 
@@ -840,12 +813,9 @@ export class SubagentCoordinator {
840
813
  runtime: {
841
814
  agentScope: settings.agentScope,
842
815
  maxDepth: settings.maxDepth,
843
- enableRunInBackground: settings.enableRunInBackground,
844
- defaultBackground: settings.defaultBackground,
816
+ runtimeMode: settings.runtimeMode,
845
817
  maxConcurrentBackgroundRuns: settings.maxConcurrentBackgroundRuns,
846
818
  maxIdleRuntimes: settings.maxIdleRuntimes,
847
- backgroundProtocol: settings.backgroundProtocol ?? "legacy",
848
- reportDelivery: settings.reportDelivery,
849
819
  inheritExtensions: settings.inheritExtensions,
850
820
  openAIIdentity: openAIIdentityEnabled,
851
821
  maxOutputBytes: settings.maxOutputBytes,
@@ -937,32 +907,9 @@ export class SubagentCoordinator {
937
907
  ): Promise<SendMessageOutcome> {
938
908
  if (this.draining) throw new Error("pi-subagent is shutting down; message was not delivered");
939
909
  const target = await this.resolveTarget(parent, childId);
940
- const delivery = await this.agentOperations.run(target.agentId, () =>
910
+ return this.agentOperations.run(target.agentId, () =>
941
911
  this.sendMessageSerialized(parent, target.agentId, message, signal),
942
912
  );
943
- if (delivery.kind === "mailbox-v2") return delivery;
944
- const pending = delivery.pending;
945
- if (!pending) return delivery;
946
- try {
947
- await pending.accepted;
948
- } catch (error) {
949
- if (pending.coldPrepared && !pending.activation.published) {
950
- await this.agentOperations.run(target.agentId, async () => {
951
- if (this.active.get(target.agentId) !== pending.activation) return;
952
- pending.activation.suppressSettlement = true;
953
- await this.rollbackActivation(
954
- pending.activation,
955
- pending.coldPrepared!,
956
- );
957
- });
958
- }
959
- throw error;
960
- }
961
- return {
962
- kind: "legacy",
963
- agentId: delivery.agentId,
964
- taskPath: delivery.taskPath,
965
- };
966
913
  }
967
914
 
968
915
  private async sendMessageSerialized(
@@ -975,139 +922,54 @@ export class SubagentCoordinator {
975
922
  throw new Error("pi-subagent is shutting down; message was not delivered");
976
923
  }
977
924
  let activation = this.active.get(childId);
978
- let coldPrepared: PreparedChildSession | undefined;
979
925
  if (activation?.disposed) activation = undefined;
980
926
  if (activation) {
981
927
  this.touchActivation(activation);
982
928
  this.assertContinuableDirectChild(parent, activation.descriptor);
983
- if (activation.descriptor.runtime.backgroundProtocol === "mailbox-v2") {
984
- if (signal?.aborted) throw abortReason(signal);
985
- await waitForPromise(activation.persistenceGate.promise, signal);
986
- if (this.draining || activation.disposed) {
987
- throw new Error(
988
- `subagent ${childId} became unavailable before its mailbox could be persisted`,
989
- );
990
- }
991
- const enqueued = enqueueMailboxMessage(
992
- activation.runtime.session.sessionManager,
993
- {
994
- senderAgentId: parent.agentId,
995
- recipientAgentId: childId,
996
- content: message,
997
- },
929
+ if (signal?.aborted) throw abortReason(signal);
930
+ await waitForPromise(activation.persistenceGate.promise, signal);
931
+ if (this.draining || activation.disposed) {
932
+ throw new Error(
933
+ `subagent ${childId} became unavailable before its mailbox could be persisted`,
998
934
  );
999
- return {
1000
- kind: "mailbox-v2",
1001
- agentId: childId,
1002
- taskPath: descriptorTaskPath(activation.descriptor),
1003
- messageId: enqueued.message.messageId,
1004
- pendingMessages: enqueued.pendingMessages,
1005
- };
1006
- }
1007
- }
1008
- if (activation?.pendingSettlement && !activation.currentRun) {
1009
- await this.finalizeContinuableLocked(activation);
1010
- if (activation.disposed || this.active.get(childId) !== activation) {
1011
- activation = undefined;
1012
935
  }
1013
- }
1014
- if (!activation) {
1015
- const located = await this.findPersistedChild(parent, childId);
1016
- if (!located) throw new Error(`unknown subagent: ${childId}; message was not delivered`);
1017
- this.assertContinuableDirectChild(parent, located.descriptor);
1018
- const manager = SessionManager.open(
1019
- located.sessionFile,
1020
- parent.sessionManager.getSessionDir(),
1021
- parent.cwd,
1022
- );
1023
- if (located.descriptor.runtime.backgroundProtocol === "mailbox-v2") {
1024
- if (signal?.aborted) throw abortReason(signal);
1025
- const enqueued = enqueueMailboxMessage(manager, {
936
+ const enqueued = enqueueMailboxMessage(
937
+ activation.runtime.session.sessionManager,
938
+ {
1026
939
  senderAgentId: parent.agentId,
1027
940
  recipientAgentId: childId,
1028
941
  content: message,
1029
- });
1030
- return {
1031
- kind: "mailbox-v2",
1032
- agentId: childId,
1033
- taskPath: descriptorTaskPath(located.descriptor),
1034
- messageId: enqueued.message.messageId,
1035
- pendingMessages: enqueued.pendingMessages,
1036
- };
1037
- }
1038
- coldPrepared = {
1039
- sessionManager: manager,
1040
- seedMessageCount: manager.buildSessionContext().messages.length,
1041
- rollback: () => Promise.resolve(),
1042
- };
1043
- activation = await this.createActivation({
1044
- parent,
1045
- descriptor: located.descriptor,
1046
- prepared: coldPrepared,
1047
- isNew: false,
1048
- });
1049
- this.acquireParentOwnership(activation, parent);
1050
- } else {
1051
- this.assertContinuableDirectChild(parent, activation.descriptor);
1052
- }
1053
- const resolvedPath = descriptorTaskPath(activation.descriptor);
1054
-
1055
- const session = activation.runtime.session;
1056
- if (activation.currentRun || session.isStreaming) {
1057
- if (signal?.aborted) throw signal.reason ?? new Error("message delivery aborted");
1058
- if (activation.controlState.turn.state === "queued") {
1059
- if (!activation.currentMessageGate) {
1060
- throw new Error(
1061
- `subagent ${activation.agentId} is queued but has no message gate`,
1062
- );
1063
- }
1064
- return {
1065
- kind: "legacy",
1066
- agentId: childId,
1067
- taskPath: resolvedPath,
1068
- pending: {
1069
- activation,
1070
- accepted: waitForPromise(
1071
- activation.currentMessageGate,
1072
- signal,
1073
- ).then(() => {
1074
- if (signal?.aborted) throw abortReason(signal);
1075
- return session.followUp(message);
1076
- }),
1077
- },
1078
- };
1079
- }
1080
- await session.followUp(message);
1081
- return {
1082
- kind: "legacy",
1083
- agentId: childId,
1084
- taskPath: resolvedPath,
1085
- };
1086
- }
1087
-
1088
- try {
1089
- this.acquireParentOwnership(activation, parent);
1090
- const started = this.startPrompt(activation, message, signal, {
1091
- detachAtAcceptance: true,
1092
- waitForCapacity: !parent.activation?.holdsBackgroundSlot,
1093
- });
942
+ },
943
+ );
1094
944
  return {
1095
- kind: "legacy",
945
+ kind: "mailbox",
1096
946
  agentId: childId,
1097
- taskPath: resolvedPath,
1098
- pending: {
1099
- activation,
1100
- accepted: started.accepted,
1101
- ...(coldPrepared ? { coldPrepared } : {}),
1102
- },
947
+ taskPath: descriptorTaskPath(activation.descriptor),
948
+ messageId: enqueued.message.messageId,
949
+ pendingMessages: enqueued.pendingMessages,
1103
950
  };
1104
- } catch (error) {
1105
- if (coldPrepared && !activation.published) {
1106
- activation.suppressSettlement = true;
1107
- await this.rollbackActivation(activation, coldPrepared);
1108
- }
1109
- throw error;
1110
951
  }
952
+ const located = await this.findPersistedChild(parent, childId);
953
+ if (!located) throw new Error(`unknown subagent: ${childId}; message was not delivered`);
954
+ this.assertContinuableDirectChild(parent, located.descriptor);
955
+ const manager = SessionManager.open(
956
+ located.sessionFile,
957
+ parent.sessionManager.getSessionDir(),
958
+ parent.cwd,
959
+ );
960
+ if (signal?.aborted) throw abortReason(signal);
961
+ const enqueued = enqueueMailboxMessage(manager, {
962
+ senderAgentId: parent.agentId,
963
+ recipientAgentId: childId,
964
+ content: message,
965
+ });
966
+ return {
967
+ kind: "mailbox",
968
+ agentId: childId,
969
+ taskPath: descriptorTaskPath(located.descriptor),
970
+ messageId: enqueued.message.messageId,
971
+ pendingMessages: enqueued.pendingMessages,
972
+ };
1111
973
  }
1112
974
 
1113
975
  async followupTask(
@@ -1188,12 +1050,6 @@ export class SubagentCoordinator {
1188
1050
  parent.cwd,
1189
1051
  );
1190
1052
  }
1191
- if (descriptor.runtime.backgroundProtocol !== "mailbox-v2") {
1192
- throw new Error(
1193
- `subagent ${childId} uses the legacy background protocol; use send_message to start its next turn`,
1194
- );
1195
- }
1196
-
1197
1053
  const owner = mailboxOwner(descriptor);
1198
1054
  const batch = readMailbox(manager.getEntries(), owner).pending;
1199
1055
  if (batch.length === 0) {
@@ -1716,18 +1572,13 @@ export class SubagentCoordinator {
1716
1572
  const content = `Background subagent ${readablePath} (${child.agentId}) reported:\n\n${truncated.text}${
1717
1573
  truncated.truncated ? `\n\n[Report truncated; ${truncated.omittedBytes} bytes omitted.]` : ""
1718
1574
  }`;
1719
- await child.parent.deliver(
1720
- REPORT_CUSTOM_TYPE,
1721
- content,
1722
- {
1723
- kind: "report",
1724
- childAgentId: child.agentId,
1725
- taskPath: readablePath,
1726
- label: child.descriptor.label,
1727
- ...(truncated.truncated ? { truncated: true } : {}),
1728
- },
1729
- child.descriptor.runtime.reportDelivery,
1730
- );
1575
+ await child.parent.deliver(REPORT_CUSTOM_TYPE, content, {
1576
+ kind: "report",
1577
+ childAgentId: child.agentId,
1578
+ taskPath: readablePath,
1579
+ label: child.descriptor.label,
1580
+ ...(truncated.truncated ? { truncated: true } : {}),
1581
+ });
1731
1582
  }
1732
1583
 
1733
1584
  private runAdmittedOperation<T>(operation: () => Promise<T>): Promise<T> {
@@ -1812,14 +1663,12 @@ export class SubagentCoordinator {
1812
1663
 
1813
1664
  createChildToolDefinitions(
1814
1665
  getActivation: () => Activation,
1815
- enableRunInBackground = true,
1816
- defaultBackground = true,
1666
+ runtimeMode: RuntimeMode = "background",
1817
1667
  agentDiscovery?: AgentDiscoveryResult,
1818
- backgroundProtocol: NonNullable<SubagentSettings["backgroundProtocol"]> = "legacy",
1819
1668
  ): ToolDefinition[] {
1820
1669
  const agentNames = agentDiscovery?.agents.map((agent) => agent.name);
1821
1670
  const assertBackgroundControlEnabled = (toolName: string): void => {
1822
- if (!enableRunInBackground) {
1671
+ if (runtimeMode === "foreground") {
1823
1672
  throw new Error(`tool "${toolName}" is unavailable in foreground-only mode`);
1824
1673
  }
1825
1674
  };
@@ -1836,12 +1685,10 @@ export class SubagentCoordinator {
1836
1685
  label: "Subagent",
1837
1686
  description:
1838
1687
  "Delegate a standalone task to a named child path, with optional completed-turn context inheritance. " +
1839
- (!enableRunInBackground
1688
+ (runtimeMode === "foreground"
1840
1689
  ? "This foreground-only instance always waits for the result."
1841
- : defaultBackground
1842
- ? "It runs in the background by default and returns a readable path plus durable id."
1843
- : "It waits for the result by default; background mode returns a readable path plus durable id."),
1844
- parameters: delegationParameters(enableRunInBackground, agentNames),
1690
+ : "It returns a readable path plus durable id; use send_message, followup_task, and wait_agent to continue it."),
1691
+ parameters: delegationParameters(agentNames),
1845
1692
  execute: async (_id, params, signal, onUpdate) => {
1846
1693
  const activation = getActivation();
1847
1694
  const outcome = await this.delegate(
@@ -1861,11 +1708,11 @@ export class SubagentCoordinator {
1861
1708
  name: "subagent_fork",
1862
1709
  label: "Subagent Fork",
1863
1710
  description:
1864
- "Delegate a task to a child seeded with all completed turns from this conversation. It is foreground by default and can be explicitly continuable.",
1865
- parameters: forkDelegationParameters(
1866
- agentNames,
1867
- enableRunInBackground,
1868
- ),
1711
+ "Delegate a task to a child seeded with all completed turns from this conversation. " +
1712
+ (runtimeMode === "foreground"
1713
+ ? "This foreground-only instance always waits for the result."
1714
+ : "It returns a readable path plus durable id and can be continued through its mailbox."),
1715
+ parameters: forkDelegationParameters(agentNames),
1869
1716
  execute: async (_id, params, signal, onUpdate) => {
1870
1717
  const activation = getActivation();
1871
1718
  const outcome = await this.delegate(
@@ -1885,9 +1732,7 @@ export class SubagentCoordinator {
1885
1732
  name: "send_message",
1886
1733
  label: "Send Message",
1887
1734
  description:
1888
- backgroundProtocol === "mailbox-v2"
1889
- ? "Durably append a message to a direct mailbox-v2 child's FIFO mailbox without starting or resuming it."
1890
- : "Queue a message as a direct continuable child's next FIFO turn. This returns acceptance, not the child's answer.",
1735
+ "Durably append a message to a direct continuable child's FIFO mailbox without starting or resuming it. Use followup_task to start the queued batch. This returns acceptance, not the child's answer.",
1891
1736
  parameters: SendMessageParameters,
1892
1737
  execute: async (_id, params, signal) => {
1893
1738
  assertBackgroundControlEnabled("send_message");
@@ -1903,7 +1748,7 @@ export class SubagentCoordinator {
1903
1748
  {
1904
1749
  type: "text",
1905
1750
  text:
1906
- delivery.kind === "mailbox-v2"
1751
+ delivery.kind === "mailbox"
1907
1752
  ? `message ${delivery.messageId} durably enqueued for ${delivery.taskPath}; ${delivery.pendingMessages} pending`
1908
1753
  : `message queued as the next turn for ${delivery.taskPath}`,
1909
1754
  },
@@ -1913,7 +1758,7 @@ export class SubagentCoordinator {
1913
1758
  action: "send",
1914
1759
  agentId: delivery.agentId,
1915
1760
  taskPath: delivery.taskPath,
1916
- ...(delivery.kind === "mailbox-v2"
1761
+ ...(delivery.kind === "mailbox"
1917
1762
  ? {
1918
1763
  messageId: delivery.messageId,
1919
1764
  pendingMessages: delivery.pendingMessages,
@@ -1928,7 +1773,7 @@ export class SubagentCoordinator {
1928
1773
  name: "followup_task",
1929
1774
  label: "Follow-up Task",
1930
1775
  description:
1931
- "Start exactly one scheduled turn for a direct mailbox-v2 child, atomically claiming its current pending FIFO mailbox batch.",
1776
+ "Start exactly one scheduled turn for a direct child, atomically claiming its current pending FIFO mailbox batch.",
1932
1777
  parameters: FollowupTaskParameters,
1933
1778
  execute: async (_id, params, signal) => {
1934
1779
  assertBackgroundControlEnabled("followup_task");
@@ -1961,7 +1806,7 @@ export class SubagentCoordinator {
1961
1806
  name: "wait_agent",
1962
1807
  label: "Wait Agent",
1963
1808
  description:
1964
- "Wait event-driven for unread completion updates from direct mailbox-v2 children. This does not start a child or occupy a background scheduler slot.",
1809
+ "Wait event-driven for unread completion updates from direct children. This does not start a child or occupy a background scheduler slot.",
1965
1810
  parameters: WaitAgentParameters,
1966
1811
  execute: async (id, params, signal) => {
1967
1812
  assertBackgroundControlEnabled("wait_agent");
@@ -2099,10 +1944,7 @@ export class SubagentCoordinator {
2099
1944
  scope === "descendants"
2100
1945
  ? ` parent=${entry.parentTaskPath} depth=${entry.depth}`
2101
1946
  : "";
2102
- const mailbox =
2103
- entry.descriptor.runtime.backgroundProtocol === "mailbox-v2"
2104
- ? ` pending=${entry.pendingMessages} updates=${entry.unreadUpdates}`
2105
- : "";
1947
+ const mailbox = ` pending=${entry.pendingMessages} updates=${entry.unreadUpdates}`;
2106
1948
  return `${entry.taskPath} [${entry.status}]${mailbox}${location} — ${entry.descriptor.label} (${entry.descriptor.agent.name}) id=${entry.agentId}`;
2107
1949
  })
2108
1950
  .join("\n");
@@ -2180,10 +2022,8 @@ export class SubagentCoordinator {
2180
2022
  if (!activation) throw new Error("subagent activation is not published yet");
2181
2023
  return activation;
2182
2024
  },
2183
- descriptor.runtime.enableRunInBackground,
2184
- descriptor.runtime.defaultBackground,
2025
+ descriptor.runtime.runtimeMode,
2185
2026
  agentDiscovery,
2186
- descriptor.runtime.backgroundProtocol,
2187
2027
  );
2188
2028
  const model =
2189
2029
  options.parent.modelRuntime.getModel(descriptor.model.provider, descriptor.model.id) ??
@@ -2299,17 +2139,8 @@ export class SubagentCoordinator {
2299
2139
  return false;
2300
2140
  }
2301
2141
  if (
2302
- !descriptor.runtime.enableRunInBackground &&
2303
- BACKGROUND_CONTROL_TOOLS.has(tool)
2304
- ) {
2305
- return false;
2306
- }
2307
- if (
2308
- descriptor.runtime.backgroundProtocol !== "mailbox-v2"
2309
- && (
2310
- tool === "followup_task"
2311
- || tool === "wait_agent"
2312
- )
2142
+ descriptor.runtime.runtimeMode === "foreground"
2143
+ && BACKGROUND_CONTROL_TOOLS.has(tool)
2313
2144
  ) {
2314
2145
  return false;
2315
2146
  }
@@ -2428,13 +2259,6 @@ export class SubagentCoordinator {
2428
2259
  resolveAccepted = resolvePromise;
2429
2260
  rejectAccepted = rejectPromise;
2430
2261
  });
2431
- activation.currentMessageGate = accepted;
2432
- const clearCurrentMessageGate = () => {
2433
- if (activation.currentMessageGate === accepted) {
2434
- activation.currentMessageGate = undefined;
2435
- }
2436
- };
2437
- void accepted.then(clearCurrentMessageGate, clearCurrentMessageGate);
2438
2262
  const turnAbortController = new AbortController();
2439
2263
  activation.turnAbortController = turnAbortController;
2440
2264
  const abort = () => {
@@ -2570,86 +2394,6 @@ export class SubagentCoordinator {
2570
2394
  return { turnId, accepted, result: lifecycle };
2571
2395
  }
2572
2396
 
2573
- private startInternalMessage(
2574
- activation: Activation,
2575
- customType: string,
2576
- content: string,
2577
- details: ParentMessageDetails,
2578
- ): void {
2579
- if (activation.currentRun || activation.disposed) return;
2580
- activation.pendingSettlement = undefined;
2581
- this.resetTurnCapture(activation);
2582
- const turnId = uuidv7();
2583
- queueAgentTurn(activation.controlState, turnId);
2584
- this.emitUpdate(activation);
2585
- const turnAbortController = new AbortController();
2586
- activation.turnAbortController = turnAbortController;
2587
- let resolveMessageGate!: () => void;
2588
- let rejectMessageGate!: (error: Error) => void;
2589
- let messageGateSettled = false;
2590
- const messageGate = new Promise<void>((resolvePromise, rejectPromise) => {
2591
- resolveMessageGate = resolvePromise;
2592
- rejectMessageGate = rejectPromise;
2593
- });
2594
- activation.currentMessageGate = messageGate;
2595
- void messageGate.catch(() => {});
2596
- const core = (async (): Promise<SubagentRunResult> => {
2597
- let permit: BackgroundRunPermit | undefined;
2598
- try {
2599
- permit = await this.acquireBackgroundRun(
2600
- activation,
2601
- turnAbortController.signal,
2602
- true,
2603
- );
2604
- startAgentTurn(activation.controlState, turnId);
2605
- activation.startedTurnIds.add(turnId);
2606
- this.emitTurnStart(activation, turnId);
2607
- this.emitUpdate(activation);
2608
- messageGateSettled = true;
2609
- resolveMessageGate();
2610
- await activation.runtime.session.sendCustomMessage(
2611
- { customType, content, display: true, details },
2612
- { triggerTurn: true, deliverAs: "followUp" },
2613
- );
2614
- return this.collectResult(activation, turnId, "completed");
2615
- } catch (error) {
2616
- activation.lastError = errorText(error);
2617
- if (!messageGateSettled) {
2618
- messageGateSettled = true;
2619
- rejectMessageGate(
2620
- error instanceof Error ? error : new Error(String(error)),
2621
- );
2622
- }
2623
- const result = this.collectResult(
2624
- activation,
2625
- turnId,
2626
- turnAbortController.signal.aborted ? "aborted" : "error",
2627
- );
2628
- if (!result.output) result.output = activation.lastError;
2629
- return result;
2630
- } finally {
2631
- if (activation.turnAbortController === turnAbortController) {
2632
- activation.turnAbortController = undefined;
2633
- }
2634
- if (activation.currentMessageGate === messageGate) {
2635
- activation.currentMessageGate = undefined;
2636
- }
2637
- if (permit) {
2638
- activation.holdsBackgroundSlot = false;
2639
- permit.release();
2640
- }
2641
- }
2642
- })();
2643
- let lifecycle!: Promise<SubagentRunResult>;
2644
- lifecycle = core.then(async (result) => {
2645
- if (activation.currentRun === lifecycle) activation.currentRun = undefined;
2646
- await this.runFinished(activation, result);
2647
- return result;
2648
- });
2649
- activation.currentRun = lifecycle;
2650
- void lifecycle.catch(() => {});
2651
- }
2652
-
2653
2397
  private async runFinished(activation: Activation, result: SubagentRunResult): Promise<void> {
2654
2398
  activation.pendingMailboxClaims.delete(result.turnId);
2655
2399
  activation.persistenceGate.reject(
@@ -2671,7 +2415,6 @@ export class SubagentCoordinator {
2671
2415
  && !activation.silentSettlementTurnIds.has(result.turnId)
2672
2416
  && activation.published
2673
2417
  && activation.descriptor.mode === "continuable"
2674
- && activation.descriptor.runtime.backgroundProtocol === "mailbox-v2"
2675
2418
  ) {
2676
2419
  try {
2677
2420
  appendCompletionUpdate(activation.parent.sessionManager, {
@@ -2743,18 +2486,6 @@ export class SubagentCoordinator {
2743
2486
  const finalizePromise = (async (): Promise<boolean> => {
2744
2487
  let retained = false;
2745
2488
  try {
2746
- const silentSettlement =
2747
- activation.silentSettlementTurnIds.has(result.turnId);
2748
- if (
2749
- activation.descriptor.runtime.backgroundProtocol !== "mailbox-v2"
2750
- && !silentSettlement
2751
- && !activation.suppressSettlement
2752
- && !this.draining
2753
- ) {
2754
- await this.deliverSettlement(activation, result).catch((error) => {
2755
- activation.lastError = errorText(error);
2756
- });
2757
- }
2758
2489
  this.emitEnd(activation, result);
2759
2490
  activation.silentSettlementTurnIds.delete(result.turnId);
2760
2491
  activation.pendingSettlement = undefined;
@@ -2786,30 +2517,6 @@ export class SubagentCoordinator {
2786
2517
  }
2787
2518
  }
2788
2519
 
2789
- private async deliverSettlement(activation: Activation, result: SubagentRunResult): Promise<void> {
2790
- const readablePath = descriptorTaskPath(activation.descriptor);
2791
- const truncated = truncateUtf8(result.output, activation.descriptor.runtime.maxOutputBytes);
2792
- const closing = truncated.text.trim()
2793
- ? `Its closing message:\n\n${truncated.text}`
2794
- : "It left no closing message.";
2795
- const content = `Background subagent ${readablePath} (${activation.agentId}) ${stopReasonHeadline(result.stopReason)} and will do no further work unless you send it more.\n\n${closing}${
2796
- truncated.truncated ? `\n\n[Closing message truncated; ${truncated.omittedBytes} bytes omitted.]` : ""
2797
- }`;
2798
- await activation.parent.deliver(
2799
- SETTLED_CUSTOM_TYPE,
2800
- content,
2801
- {
2802
- kind: "settled",
2803
- childAgentId: activation.agentId,
2804
- taskPath: readablePath,
2805
- label: activation.descriptor.label,
2806
- stopReason: result.stopReason,
2807
- ...(truncated.truncated ? { truncated: true } : {}),
2808
- },
2809
- "wakeup",
2810
- );
2811
- }
2812
-
2813
2520
  private parentForActivation(activation: Activation): ParentRef {
2814
2521
  return {
2815
2522
  agentId: activation.agentId,
@@ -2822,26 +2529,15 @@ export class SubagentCoordinator {
2822
2529
  thinkingLevel: activation.runtime.session.thinkingLevel,
2823
2530
  projectTrusted: activation.parent.projectTrusted,
2824
2531
  activation,
2825
- deliver: async (customType, content, details, delivery) => {
2532
+ deliver: async (customType, content, details) => {
2826
2533
  if (activation.disposed) throw new Error(`parent subagent ${activation.agentId} is no longer resident`);
2827
2534
  const session = activation.runtime.session;
2828
- if (delivery === "quiet") {
2829
- await session.sendCustomMessage(
2830
- { customType, content, display: true, details },
2831
- session.isStreaming
2832
- ? { triggerTurn: false, deliverAs: "nextTurn" }
2833
- : { triggerTurn: false },
2834
- );
2835
- return;
2836
- }
2837
- if (activation.currentRun || session.isStreaming) {
2838
- await session.sendCustomMessage(
2839
- { customType, content, display: true, details },
2840
- { triggerTurn: true, deliverAs: "followUp" },
2841
- );
2842
- return;
2843
- }
2844
- this.startInternalMessage(activation, customType, content, details);
2535
+ await session.sendCustomMessage(
2536
+ { customType, content, display: true, details },
2537
+ session.isStreaming
2538
+ ? { triggerTurn: false, deliverAs: "nextTurn" }
2539
+ : { triggerTurn: false },
2540
+ );
2845
2541
  },
2846
2542
  };
2847
2543
  }
@@ -3379,7 +3075,7 @@ export class SubagentCoordinator {
3379
3075
  if (parentPath === undefined || parentDepth === undefined) {
3380
3076
  continue;
3381
3077
  }
3382
- if (record.descriptor.version === 3) {
3078
+ {
3383
3079
  let expectedPath: string;
3384
3080
  try {
3385
3081
  expectedPath = taskPath(
@@ -3418,7 +3114,6 @@ export class SubagentCoordinator {
3418
3114
  );
3419
3115
  const problems = new Map<string, string>();
3420
3116
  for (const record of records) {
3421
- if (record.descriptor.version !== 3) continue;
3422
3117
  const parent = byId.get(record.descriptor.parentAgentId);
3423
3118
  if (parent) {
3424
3119
  let expectedPath: string | undefined;
@@ -3559,28 +3254,26 @@ export class SubagentCoordinator {
3559
3254
  for (const activation of this.active.values()) {
3560
3255
  if (activation.descriptor.cwd !== parent.cwd) continue;
3561
3256
  let pendingMessages = 0;
3562
- if (activation.descriptor.runtime.backgroundProtocol === "mailbox-v2") {
3563
- const mailbox = foldOwnedMailbox(
3564
- activation.runtime.session.sessionManager.getEntries(),
3565
- mailboxOwner(activation.descriptor),
3566
- );
3567
- if (mailbox.kind === "valid") {
3568
- pendingMessages = mailbox.snapshot.pending.length;
3569
- } else {
3570
- activeDiagnostics.push({
3571
- kind: "diagnostic",
3572
- piSessionId: activation.runtime.session.sessionId,
3573
- reason: "corrupt",
3574
- ...(activation.runtime.session.sessionFile
3575
- ? { sessionFile: activation.runtime.session.sessionFile }
3576
- : {}),
3577
- ...(activation.descriptor.parentSessionFile
3578
- ? { parentSessionFile: activation.descriptor.parentSessionFile }
3579
- : {}),
3580
- message: `corrupt subagent mailbox: ${mailbox.message}`,
3581
- });
3582
- continue;
3583
- }
3257
+ const mailbox = foldOwnedMailbox(
3258
+ activation.runtime.session.sessionManager.getEntries(),
3259
+ mailboxOwner(activation.descriptor),
3260
+ );
3261
+ if (mailbox.kind === "valid") {
3262
+ pendingMessages = mailbox.snapshot.pending.length;
3263
+ } else {
3264
+ activeDiagnostics.push({
3265
+ kind: "diagnostic",
3266
+ piSessionId: activation.runtime.session.sessionId,
3267
+ reason: "corrupt",
3268
+ ...(activation.runtime.session.sessionFile
3269
+ ? { sessionFile: activation.runtime.session.sessionFile }
3270
+ : {}),
3271
+ ...(activation.descriptor.parentSessionFile
3272
+ ? { parentSessionFile: activation.descriptor.parentSessionFile }
3273
+ : {}),
3274
+ message: `corrupt subagent mailbox: ${mailbox.message}`,
3275
+ });
3276
+ continue;
3584
3277
  }
3585
3278
  const completions = foldCompletionMailbox(
3586
3279
  activation.runtime.session.sessionManager.getEntries(),
@@ -3658,7 +3351,6 @@ export class SubagentCoordinator {
3658
3351
 
3659
3352
  export {
3660
3353
  REPORT_CUSTOM_TYPE,
3661
- SETTLED_CUSTOM_TYPE,
3662
3354
  AGENT_CUSTOM_TYPE,
3663
3355
  LINEAGE_CUSTOM_TYPE,
3664
3356
  };