@ian-pascoe/pi-minimal-subagents 0.1.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.
@@ -0,0 +1,29 @@
1
+ interface MinimalSubagentsShutdownCoordinator {
2
+ waitForSettledOperations(): Promise<void>;
3
+ shutdownAfterSettling(): Promise<void>;
4
+ shutdown(): Promise<void>;
5
+ }
6
+
7
+ interface MinimalSubagentsRootIdleGate {
8
+ isRootIdle(): boolean;
9
+ waitForRootIdle(): Promise<void>;
10
+ }
11
+
12
+ /** Drain active child and root work before reload while preserving canceling shutdown elsewhere. */
13
+ export async function shutdownMinimalSubagentsSession(
14
+ reason: string,
15
+ coordinator: MinimalSubagentsShutdownCoordinator,
16
+ rootIdleGate: MinimalSubagentsRootIdleGate,
17
+ ): Promise<void> {
18
+ if (reason !== "reload") {
19
+ await coordinator.shutdown();
20
+ return;
21
+ }
22
+
23
+ while (true) {
24
+ await coordinator.waitForSettledOperations();
25
+ if (rootIdleGate.isRootIdle()) break;
26
+ await rootIdleGate.waitForRootIdle();
27
+ }
28
+ await coordinator.shutdownAfterSettling();
29
+ }
@@ -0,0 +1,66 @@
1
+ import { StringEnum } from "@earendil-works/pi-ai";
2
+ import { Type, type TSchema } from "typebox";
3
+ import { THINKING_LEVELS } from "./minimal-subagents-capabilities.js";
4
+
5
+ const SessionContextSchema = StringEnum(["inherit", "compact", "omit"] as const);
6
+ const ProjectContextSchema = StringEnum(["inherit", "omit"] as const);
7
+ const DelegationSchema = StringEnum(["none", "fanout"] as const);
8
+ const ThinkingLevelSchema = StringEnum(THINKING_LEVELS);
9
+ const ToolSelectionSchema = Type.Union([
10
+ StringEnum(["none", "read", "modify"] as const),
11
+ Type.Array(Type.String({ minLength: 1 }), { uniqueItems: true }),
12
+ ]);
13
+ const FRIENDLY_AGENT_ID_PATTERN = "^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$";
14
+ const CANONICAL_AGENT_ID_PATTERN =
15
+ "^(?:root\\.)?[A-Za-z0-9][A-Za-z0-9_-]{0,63}(?:\\.[A-Za-z0-9][A-Za-z0-9_-]{0,63})*$";
16
+
17
+ function canonicalAgentIdSchema(description?: string) {
18
+ return Type.String({ minLength: 1, pattern: CANONICAL_AGENT_ID_PATTERN, description });
19
+ }
20
+
21
+ /** Build all six strict TypeBox schemas, including the refreshed runtime model enum. */
22
+ export function createCoordinatorToolSchemas(modelIds: readonly string[]) {
23
+ const explicitModelSchema: TSchema =
24
+ modelIds.length > 0 ? StringEnum(modelIds as [string, ...string[]]) : Type.Never();
25
+ return {
26
+ subagent: Type.Object({
27
+ task: Type.String({ minLength: 1, description: "Task for the persistent child agent" }),
28
+ agent_id: Type.Optional(
29
+ Type.String({
30
+ pattern: FRIENDLY_AGENT_ID_PATTERN,
31
+ description:
32
+ "Friendly peer-unique ID segment; a root child uses this segment as its canonical ID",
33
+ }),
34
+ ),
35
+ session_context: Type.Optional(SessionContextSchema),
36
+ project_context: Type.Optional(ProjectContextSchema),
37
+ model: Type.Optional(explicitModelSchema),
38
+ thinking_level: Type.Optional(ThinkingLevelSchema),
39
+ tools: Type.Optional(ToolSelectionSchema),
40
+ delegation: Type.Optional(DelegationSchema),
41
+ }),
42
+ agent_message: Type.Object({
43
+ agent_id: Type.Optional(
44
+ canonicalAgentIdSchema(
45
+ "Direct parent, sibling, or child canonical agent ID, or parent alias",
46
+ ),
47
+ ),
48
+ message: Type.String({ minLength: 1 }),
49
+ }),
50
+ subagent_wait: Type.Object({
51
+ agent_id: canonicalAgentIdSchema("Direct child canonical agent ID"),
52
+ timeout_ms: Type.Optional(Type.Integer({ minimum: 0 })),
53
+ }),
54
+ subagent_status: Type.Object({
55
+ agent_id: Type.Optional(canonicalAgentIdSchema("Direct child canonical agent ID")),
56
+ }),
57
+ subagent_cancel: Type.Object({
58
+ agent_id: canonicalAgentIdSchema("Direct child canonical agent ID"),
59
+ recursive: Type.Optional(Type.Boolean()),
60
+ }),
61
+ subagent_delete: Type.Object({
62
+ agent_id: canonicalAgentIdSchema("Direct child canonical agent ID"),
63
+ recursive: Type.Optional(Type.Boolean()),
64
+ }),
65
+ };
66
+ }
@@ -0,0 +1,285 @@
1
+ import {
2
+ DEFAULT_MAX_BYTES,
3
+ DEFAULT_MAX_LINES,
4
+ defineTool,
5
+ truncateHead,
6
+ type AgentToolResult,
7
+ type ExtensionContext,
8
+ type Theme,
9
+ type ToolDefinition,
10
+ type ToolRenderResultOptions,
11
+ } from "@earendil-works/pi-coding-agent";
12
+ import type { MinimalSubagentsCoordinator } from "./minimal-subagents-coordinator.js";
13
+ import type { MinimalSubagentsModelRole } from "./minimal-subagents-config.js";
14
+ import {
15
+ renderCoordinatorToolCall,
16
+ renderCoordinatorToolResult,
17
+ type CoordinatorToolName,
18
+ } from "./minimal-subagents-rendering.js";
19
+ import type { createCoordinatorToolSchemas } from "./minimal-subagents-tool-schemas.js";
20
+ import type { CallerSnapshot, SpawnParameters } from "./minimal-subagents-types.js";
21
+
22
+ const ORDINARY_CHILD_COORDINATOR_TOOL_NAMES = new Set([
23
+ "agent_message",
24
+ "subagent_wait",
25
+ "subagent_status",
26
+ ]);
27
+
28
+ interface CoordinatorToolDefinitionOptions {
29
+ coordinator: MinimalSubagentsCoordinator;
30
+ callerId: string;
31
+ allowFanoutTools?: boolean;
32
+ modelRoles?: readonly MinimalSubagentsModelRole[];
33
+ schemas: ReturnType<typeof createCoordinatorToolSchemas>;
34
+ captureCaller: (context: ExtensionContext) => CallerSnapshot;
35
+ onActivity?: () => void;
36
+ onAttention?: (message: string) => void;
37
+ }
38
+
39
+ function buildModelRolePromptGuidelines(
40
+ modelRoles: readonly MinimalSubagentsModelRole[],
41
+ ): string[] | undefined {
42
+ if (modelRoles.length === 0) return undefined;
43
+ const roleLines = modelRoles.map(
44
+ (role) => ` - ${role.name} → ${role.model}${role.hint ? ` — ${role.hint}` : ""}`,
45
+ );
46
+ return [
47
+ ["Configured model roles are guidance, not constraints:", ...roleLines].join("\n"),
48
+ "Choose a model based on the task. Choose thinking_level independently.",
49
+ ];
50
+ }
51
+
52
+ async function runCoordinatorToolActivity<T>(
53
+ options: CoordinatorToolDefinitionOptions,
54
+ operation: () => Promise<T> | T,
55
+ ): Promise<T> {
56
+ try {
57
+ return await operation();
58
+ } finally {
59
+ options.onActivity?.();
60
+ }
61
+ }
62
+
63
+ function createCoordinatorToolRendering(toolName: CoordinatorToolName) {
64
+ return {
65
+ renderCall: (args: Record<string, unknown>, theme: Theme) =>
66
+ renderCoordinatorToolCall(toolName, args, theme),
67
+ renderResult: (
68
+ result: AgentToolResult<unknown>,
69
+ renderOptions: ToolRenderResultOptions,
70
+ theme: Theme,
71
+ context: { args: Record<string, unknown>; isError: boolean },
72
+ ) =>
73
+ renderCoordinatorToolResult(
74
+ toolName,
75
+ result,
76
+ renderOptions,
77
+ theme,
78
+ context.args,
79
+ context.isError,
80
+ ),
81
+ };
82
+ }
83
+
84
+ function structuredToolResult(result: unknown) {
85
+ const json = JSON.stringify(result, null, 2);
86
+ const truncated = truncateHead(json, {
87
+ maxBytes: DEFAULT_MAX_BYTES,
88
+ maxLines: DEFAULT_MAX_LINES,
89
+ });
90
+ return {
91
+ content: [{ type: "text" as const, text: truncated.content }],
92
+ details: result,
93
+ };
94
+ }
95
+
96
+ function failedStructuredOperation(prefix: string, result: unknown): never {
97
+ const json = JSON.stringify(result);
98
+ const truncated = truncateHead(json, {
99
+ maxBytes: DEFAULT_MAX_BYTES,
100
+ maxLines: DEFAULT_MAX_LINES,
101
+ });
102
+ throw new Error(`${prefix}: ${truncated.content}`);
103
+ }
104
+
105
+ function callerSourceTurnId(
106
+ coordinator: MinimalSubagentsCoordinator,
107
+ callerId: string,
108
+ toolCallId: string,
109
+ ): string {
110
+ if (callerId === "root") return `root:${toolCallId}`;
111
+ const status = coordinator.inspectStatus(callerId);
112
+ return "agent" in status && status.agent.active_turn_id
113
+ ? status.agent.active_turn_id
114
+ : `${callerId}:${toolCallId}`;
115
+ }
116
+
117
+ /** Create caller-bound definitions for the six coordinator tools shared by root and children. */
118
+ export function createCoordinatorToolDefinitions(
119
+ options: CoordinatorToolDefinitionOptions,
120
+ ): ToolDefinition[] {
121
+ const modelRolePromptGuidelines = buildModelRolePromptGuidelines(options.modelRoles ?? []);
122
+ const spawnTool = defineTool({
123
+ name: "subagent",
124
+ label: "Subagent",
125
+ description:
126
+ "Create a persistent nested agent asynchronously. Returns its canonical agent ID and active turn ID immediately. Root-child IDs omit the root prefix; nested IDs retain the parent path.",
127
+ promptSnippet: "Spawn a persistent child with a prefix-free root-child ID",
128
+ promptGuidelines: modelRolePromptGuidelines,
129
+ parameters: options.schemas.subagent,
130
+ async execute(_toolCallId, parameters, _signal, _onUpdate, context) {
131
+ return runCoordinatorToolActivity(options, async () => {
132
+ const result = await options.coordinator.spawn(
133
+ options.callerId,
134
+ parameters as SpawnParameters,
135
+ options.captureCaller(context),
136
+ );
137
+ const status = options.coordinator.inspectStatus(result.agent_id);
138
+ return {
139
+ ...structuredToolResult(result),
140
+ details: {
141
+ ...result,
142
+ ...(status && "agent" in status ? { agent: status.agent } : {}),
143
+ },
144
+ };
145
+ });
146
+ },
147
+ ...createCoordinatorToolRendering("subagent"),
148
+ });
149
+
150
+ const messageTool = defineTool({
151
+ name: "agent_message",
152
+ label: "Agent Message",
153
+ description:
154
+ "Send one mid-turn coordination message to a direct parent, direct sibling, or direct child when the recipient must act before the caller's turn finishes.",
155
+ promptSnippet: "Coordinate required mid-turn action with one adjacent agent",
156
+ parameters: options.schemas.agent_message,
157
+ async execute(toolCallId, parameters) {
158
+ return runCoordinatorToolActivity(options, async () => {
159
+ const result = await options.coordinator.sendAgentMessage(
160
+ options.callerId,
161
+ {
162
+ agent_id: parameters.agent_id,
163
+ message: parameters.message,
164
+ },
165
+ callerSourceTurnId(options.coordinator, options.callerId, toolCallId),
166
+ );
167
+ return structuredToolResult(result);
168
+ });
169
+ },
170
+ ...createCoordinatorToolRendering("agent_message"),
171
+ });
172
+
173
+ const waitTool = defineTool({
174
+ name: "subagent_wait",
175
+ label: "Subagent Wait",
176
+ description:
177
+ "Wait for one direct child's exact active turn, or return its latest settled turn immediately. Timeout never cancels the child.",
178
+ promptSnippet: "Wait for one direct child's exact turn",
179
+ parameters: options.schemas.subagent_wait,
180
+ async execute(_toolCallId, parameters, signal, onUpdate) {
181
+ const startedAt = Date.now();
182
+ const updateWaitingResult = () =>
183
+ onUpdate?.({
184
+ content: [{ type: "text", text: `Waiting for ${parameters.agent_id}` }],
185
+ details: {
186
+ agent_id: parameters.agent_id,
187
+ status: "waiting",
188
+ elapsed_ms: Date.now() - startedAt,
189
+ },
190
+ });
191
+ updateWaitingResult();
192
+ const waitingInterval = setInterval(updateWaitingResult, 1_000);
193
+ waitingInterval.unref?.();
194
+ try {
195
+ return await runCoordinatorToolActivity(options, async () => {
196
+ const result = await options.coordinator.wait(
197
+ options.callerId,
198
+ parameters.agent_id,
199
+ parameters.timeout_ms,
200
+ signal,
201
+ );
202
+ return {
203
+ ...structuredToolResult(result),
204
+ details: {
205
+ ...result,
206
+ source_agent_id: result.agent_id,
207
+ source_turn_id: result.turn_id,
208
+ },
209
+ };
210
+ });
211
+ } finally {
212
+ clearInterval(waitingInterval);
213
+ }
214
+ },
215
+ ...createCoordinatorToolRendering("subagent_wait"),
216
+ });
217
+
218
+ const statusTool = defineTool({
219
+ name: "subagent_status",
220
+ label: "Subagent Status",
221
+ description:
222
+ "List direct children when agent_id is omitted, or inspect one direct child's launch contract, result, usage, and dependencies.",
223
+ promptSnippet: "Inspect direct child state",
224
+ parameters: options.schemas.subagent_status,
225
+ async execute(_toolCallId, parameters) {
226
+ return runCoordinatorToolActivity(options, () =>
227
+ structuredToolResult(options.coordinator.status(options.callerId, parameters.agent_id)),
228
+ );
229
+ },
230
+ ...createCoordinatorToolRendering("subagent_status"),
231
+ });
232
+
233
+ const cancelTool = defineTool({
234
+ name: "subagent_cancel",
235
+ label: "Subagent Cancel",
236
+ description:
237
+ "Abort active work for one direct child while preserving sessions for later continuation. Recursive cancellation includes its subtree and defaults to true.",
238
+ promptSnippet: "Cancel active subagent turns without deleting sessions",
239
+ parameters: options.schemas.subagent_cancel,
240
+ async execute(_toolCallId, parameters) {
241
+ return runCoordinatorToolActivity(options, async () =>
242
+ structuredToolResult(
243
+ await options.coordinator.cancel(
244
+ options.callerId,
245
+ parameters.agent_id,
246
+ parameters.recursive ?? true,
247
+ ),
248
+ ),
249
+ );
250
+ },
251
+ ...createCoordinatorToolRendering("subagent_cancel"),
252
+ });
253
+
254
+ const deleteTool = defineTool({
255
+ name: "subagent_delete",
256
+ label: "Subagent Delete",
257
+ description:
258
+ "Delete one direct child's persistent session and retain durable ID tombstones. Recursive deletion includes its subtree and defaults to true.",
259
+ promptSnippet: "Delete subagent sessions and tombstone their IDs",
260
+ parameters: options.schemas.subagent_delete,
261
+ async execute(_toolCallId, parameters) {
262
+ return runCoordinatorToolActivity(options, async () => {
263
+ const result = await options.coordinator.delete(
264
+ options.callerId,
265
+ parameters.agent_id,
266
+ parameters.recursive ?? true,
267
+ );
268
+ if (result.failures.length > 0) {
269
+ options.onAttention?.(
270
+ `Minimal subagents deletion partially failed for ${parameters.agent_id}`,
271
+ );
272
+ failedStructuredOperation("Minimal subagents deletion partially failed", result);
273
+ }
274
+ return structuredToolResult(result);
275
+ });
276
+ },
277
+ ...createCoordinatorToolRendering("subagent_delete"),
278
+ });
279
+
280
+ const coordinatorTools = [spawnTool, messageTool, waitTool, statusTool, cancelTool, deleteTool];
281
+ const allowFanoutTools = options.allowFanoutTools ?? options.callerId === "root";
282
+ return allowFanoutTools
283
+ ? coordinatorTools
284
+ : coordinatorTools.filter((tool) => ORDINARY_CHILD_COORDINATOR_TOOL_NAMES.has(tool.name));
285
+ }
@@ -0,0 +1,305 @@
1
+ import type { AgentMessage, ThinkingLevel } from "@earendil-works/pi-agent-core";
2
+ import type { Usage } from "@earendil-works/pi-ai";
3
+
4
+ /** Canonical path-like identity for one persistent subagent. */
5
+ export type AgentId = string & { readonly __agentId: unique symbol };
6
+
7
+ /** Stable identity for one prompt and its complete assistant/tool loop. */
8
+ export type TurnId = string & { readonly __turnId: unique symbol };
9
+
10
+ /** Controls how much committed caller conversation enters a new child session. */
11
+ export type SessionContextMode = "inherit" | "compact" | "omit";
12
+ /** Controls whether child resource discovery includes project instructions, skills, and prompts. */
13
+ export type ProjectContextMode = "inherit" | "omit";
14
+ /** Selects inherited, bundled, absent, or explicitly named ordinary child tools. */
15
+ export type ToolSelection = "none" | "read" | "modify" | string[];
16
+ /** Controls whether a child must work directly or may explicitly fan out one bounded level. */
17
+ export type DelegationMode = "none" | "fanout";
18
+ /** Reports whether a persistent agent currently owns an active turn. */
19
+ export type AgentState = "running" | "idle";
20
+ /** Reports whether saved launch dependencies can recreate an agent runtime. */
21
+ export type AgentAvailability = "available" | "unavailable";
22
+ /** Classifies active and terminal persistent subagent turn outcomes. */
23
+ export type TurnStatus = "running" | "completed" | "failed" | "cancelled" | "interrupted";
24
+
25
+ /** Defines the validated launch contract accepted by the subagent tool. */
26
+ export interface SpawnParameters {
27
+ task: string;
28
+ agent_id?: string;
29
+ session_context?: SessionContextMode;
30
+ project_context?: ProjectContextMode;
31
+ model?: string;
32
+ thinking_level?: ThinkingLevel;
33
+ tools?: ToolSelection;
34
+ delegation?: DelegationMode;
35
+ }
36
+
37
+ /** Returns persistent agent and turn identities immediately after launch scheduling. */
38
+ export interface SpawnResult {
39
+ agent_id: string;
40
+ turn_id: string;
41
+ status: "running";
42
+ }
43
+
44
+ /** Retains one keyed terminal turn result for waits and durable delivery recovery. */
45
+ export interface TurnResult {
46
+ agent_id: string;
47
+ turn_id: string;
48
+ status: Exclude<TurnStatus, "running">;
49
+ output: string;
50
+ error?: string;
51
+ usage?: Usage;
52
+ elapsed_ms?: number;
53
+ }
54
+
55
+ /** Reports delivery of one direct message to an authorized adjacent agent. */
56
+ export interface AgentMessageResult {
57
+ agent_id: string;
58
+ delivered: boolean;
59
+ error?: string;
60
+ }
61
+
62
+ /** Provides bounded hierarchy and usage data for one persistent agent. */
63
+ export interface AgentSummary {
64
+ agent_id: string;
65
+ parent_id: string;
66
+ state: AgentState;
67
+ availability: AgentAvailability;
68
+ active_turn_id?: string;
69
+ latest_turn?: Pick<TurnResult, "turn_id" | "status">;
70
+ model: string;
71
+ thinking_level: ThinkingLevel;
72
+ tools: string[];
73
+ elapsed_ms?: number;
74
+ latest_activity?: string;
75
+ latest_activity_at?: string;
76
+ task?: string;
77
+ child_count: number;
78
+ children: AgentSummary[];
79
+ }
80
+
81
+ /** Provides bounded recent child conversation text for detailed status. */
82
+ export interface RecentAgentMessage {
83
+ source_agent_id: string;
84
+ turn_id: string;
85
+ content: string;
86
+ }
87
+
88
+ /** Extends summary status with launch, dependency, and recent-message diagnostics. */
89
+ export interface AgentDetail extends AgentSummary {
90
+ session_file?: string;
91
+ launch_contract: Record<string, unknown>;
92
+ capability_ceiling: string[];
93
+ spawn_entry_id: string;
94
+ recent_messages: RecentAgentMessage[];
95
+ latest_result?: TurnResult;
96
+ missing_dependencies: string[];
97
+ unavailable_reason?: string;
98
+ usage?: Usage;
99
+ }
100
+
101
+ /** Returns either caller-owned direct children or one authorized direct-child detail. */
102
+ export type StatusResult = { parent_id: string; agents: AgentSummary[] } | { agent: AgentDetail };
103
+
104
+ /** Supplies the complete root hierarchy to trusted internal UI and activity projections. */
105
+ export type HierarchyStatusResult =
106
+ | { root_id: "root"; agents: AgentSummary[] }
107
+ | { agent: AgentDetail };
108
+
109
+ /** Reports active turns cancelled without deleting persistent sessions. */
110
+ export interface CancelResult {
111
+ agent_id: string;
112
+ recursive: boolean;
113
+ affected_agent_ids: string[];
114
+ cancelled_turn_ids: string[];
115
+ }
116
+
117
+ /** Reports post-order deletion successes, tombstones, trash paths, and partial failures. */
118
+ export interface DeleteResult {
119
+ agent_id: string;
120
+ recursive: boolean;
121
+ deleted_agent_ids: string[];
122
+ tombstoned_agent_ids: string[];
123
+ trashed_session_files: string[];
124
+ failures: Array<{ agent_id: string; error: string }>;
125
+ }
126
+
127
+ /** Persists immutable context, model, thinking, and ordinary-tool launch choices. */
128
+ export interface LaunchContract {
129
+ session_context: SessionContextMode;
130
+ project_context: ProjectContextMode;
131
+ model: string;
132
+ thinking_level: ThinkingLevel;
133
+ tools: ToolSelection | undefined;
134
+ ordinary_tools: string[];
135
+ delegation?: DelegationMode;
136
+ }
137
+
138
+ /** Captures committed caller context and its capability ceiling at spawn time. */
139
+ export interface CallerSnapshot {
140
+ messages: AgentMessage[];
141
+ model: string;
142
+ thinkingLevel: ThinkingLevel;
143
+ ordinaryTools: string[];
144
+ capabilityCeiling: string[];
145
+ availableTools: string[];
146
+ spawnEntryId: string;
147
+ }
148
+
149
+ /** Normalizes Pi child runtime completion before persistent turn settlement. */
150
+ export interface RuntimeTurnOutcome {
151
+ status: "completed" | "failed" | "cancelled";
152
+ output: string;
153
+ error?: string;
154
+ usage?: Usage;
155
+ }
156
+
157
+ /** Carries keyed conversation-plane content between persistent agent sessions. */
158
+ export interface CoordinatorMessage {
159
+ customType: "minimal-subagents.message" | "minimal-subagents.result";
160
+ content: string;
161
+ details: {
162
+ source_agent_id: string;
163
+ destination_agent_id?: string;
164
+ source_turn_id: string;
165
+ status?: TurnStatus;
166
+ elapsed_ms?: number;
167
+ usage?: Usage;
168
+ };
169
+ }
170
+
171
+ /** Process-local adapter around one SDK-created Pi child session. */
172
+ export interface ChildAgentRuntime {
173
+ readonly sessionFile: string;
174
+ readonly sessionId: string;
175
+ readonly isRunning: boolean;
176
+ runPrompt(
177
+ task: string,
178
+ compact: boolean,
179
+ callerModel: string,
180
+ callerThinkingLevel: ThinkingLevel,
181
+ ): Promise<RuntimeTurnOutcome>;
182
+ runMessage(message: CoordinatorMessage): Promise<RuntimeTurnOutcome>;
183
+ /** Steer one typed coordinator message into the child session. */
184
+ steerCoordinatorMessage(message: CoordinatorMessage): Promise<void>;
185
+ abort(): Promise<void>;
186
+ dispose(): void;
187
+ snapshotCommittedMessages(): AgentMessage[];
188
+ hasDeliveryEvidence(sourceAgentId: string, sourceTurnId: string): boolean;
189
+ getUsage(): Usage | undefined;
190
+ cloneSession(): Promise<{ sessionFile: string; sessionId: string }>;
191
+ }
192
+
193
+ /** Identifies one writable child JSONL session owned by a coordinator root. */
194
+ export interface PersistedSessionIdentity {
195
+ sessionFile: string;
196
+ sessionId: string;
197
+ }
198
+
199
+ /** Combines a persisted agent record with first-launch imported context. */
200
+ export interface RuntimeCreationRequest {
201
+ agent: PersistedAgent;
202
+ importedMessages: AgentMessage[];
203
+ }
204
+
205
+ /** Pi-specific session operations injected into the pure coordinator. */
206
+ export interface AgentSessionFactory {
207
+ createIdentity(agent: PersistedAgent, importedMessages: AgentMessage[]): PersistedSessionIdentity;
208
+ createRuntime(request: RuntimeCreationRequest): Promise<ChildAgentRuntime>;
209
+ restoreRuntime(agent: PersistedAgent): Promise<ChildAgentRuntime>;
210
+ resolveLaunchMissingDependencies(agent: PersistedAgent): Promise<string[]>;
211
+ resolveRestorationMissingDependencies(agent: PersistedAgent): Promise<string[]>;
212
+ resolveThinkingLevel(modelId: string, requested: ThinkingLevel): ThinkingLevel;
213
+ modelSupportsImages(modelId: string): boolean;
214
+ cloneSession(agent: PersistedAgent): Promise<PersistedSessionIdentity>;
215
+ trashSessionFile(sessionFile: string): Promise<void>;
216
+ }
217
+
218
+ /** Abstracts root message delivery and durable delivery-evidence lookup. */
219
+ export interface RootConversationEndpoint {
220
+ /** Steer one typed coordinator message into the root conversation. */
221
+ steerCoordinatorMessage(message: CoordinatorMessage): Promise<void>;
222
+ hasDeliveryEvidence(sourceAgentId: string, sourceTurnId: string): boolean;
223
+ }
224
+
225
+ /** Stores root-owned agent identity, launch contract, availability, and latest activity. */
226
+ export interface PersistedAgent {
227
+ agent_id: string;
228
+ friendly_id: string;
229
+ parent_id: string;
230
+ created_at: string;
231
+ task?: string;
232
+ latest_activity_at?: string;
233
+ spawn_entry_id: string;
234
+ session_file?: string;
235
+ session_id?: string;
236
+ clone_error?: string;
237
+ launch_contract: LaunchContract;
238
+ capability_ceiling: string[];
239
+ active_turn_id?: string;
240
+ active_turn_started_at?: string;
241
+ latest_result?: TurnResult;
242
+ availability: AgentAvailability;
243
+ missing_dependencies: string[];
244
+ unavailable_reason?: string;
245
+ recent_messages: RecentAgentMessage[];
246
+ deleted?: boolean;
247
+ }
248
+
249
+ /** Records whether successful output was observed through wait or automatic messaging. */
250
+ export type DeliveryPath = "wait" | "message";
251
+
252
+ /** Stores a keyed successful result until destination evidence settles delivery. */
253
+ export interface PersistedDelivery {
254
+ source_agent_id: string;
255
+ source_turn_id: string;
256
+ destination_agent_id: string;
257
+ path: DeliveryPath;
258
+ settled: boolean;
259
+ result?: TurnResult;
260
+ error?: string;
261
+ }
262
+
263
+ /** Checkpoints all live agents, deletion tombstones, and delivery records for one root. */
264
+ export interface RegistrySnapshot {
265
+ agents: PersistedAgent[];
266
+ tombstones: string[];
267
+ deliveries: PersistedDelivery[];
268
+ }
269
+
270
+ /** Clones a registry snapshot while recording the source root session file. */
271
+ export interface ForkSnapshot extends RegistrySnapshot {
272
+ source_root_session_file: string;
273
+ }
274
+
275
+ /** Appends root-owned registry events to the active root conversation branch. */
276
+ export interface RegistryWriter {
277
+ readonly rootSessionId: string;
278
+ append(event: import("./minimal-subagents-registry.js").RegistryEventV1): void;
279
+ }
280
+
281
+ /** Describes concise lifecycle notices surfaced through Pi UI notifications. */
282
+ export interface CoordinatorNotification {
283
+ type:
284
+ | "spawn"
285
+ | "completion"
286
+ | "failure"
287
+ | "cancellation"
288
+ | "interruption"
289
+ | "restoration"
290
+ | "unavailable"
291
+ | "fork-clone-failure";
292
+ agentId: string;
293
+ message: string;
294
+ }
295
+
296
+ /** Injects sessions, root delivery, registry, delegation depth, delivery grace, and notifications. */
297
+ export interface CoordinatorDependencies {
298
+ registry: RegistryWriter;
299
+ sessions: AgentSessionFactory;
300
+ root: RootConversationEndpoint;
301
+ maxSubagentDepth?: number;
302
+ now?: () => Date;
303
+ automaticDeliveryGraceMs?: number;
304
+ notify?: (notification: CoordinatorNotification) => void;
305
+ }