@oai404iao/pi-subagent 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,7 @@ Compatibility: Pi 0.84.2 or newer; tested against 0.84.2.
18
18
  - `fork`: one-shot child seeded through the parent's latest completed turn
19
19
  - **Two lifecycles**
20
20
  - foreground one-shot runs return the child's final answer
21
- - background continuable runs return a child session id immediately
21
+ - background continuable runs return a durable agent id immediately
22
22
  - **Foreground-only policy** that removes background scheduling and lifecycle controls
23
23
  - **Independent context and session** for every child
24
24
  - **Bundled presets without filesystem writes by default**, with opt-in
@@ -242,6 +242,7 @@ See [`config.example.json`](config.example.json) and [`config.schema.json`](conf
242
242
  "defaultBackground": true,
243
243
  "reportDelivery": "wakeup",
244
244
  "inheritExtensions": false,
245
+ "openAIIdentity": false,
245
246
  "maxOutputBytes": 51200
246
247
  }
247
248
  ```
@@ -255,10 +256,21 @@ See [`config.example.json`](config.example.json) and [`config.schema.json`](conf
255
256
  | `defaultBackground` | `true` | Default scheduling for fresh `subagent` calls when background execution is enabled. |
256
257
  | `reportDelivery` | `wakeup` | `wakeup` starts/queues a parent turn; `quiet` waits for the parent's next turn. |
257
258
  | `inheritExtensions` | `false` | Load other Pi extensions in child runtimes. This package filters itself out; explicit agent tool ceilings still apply. |
259
+ | `openAIIdentity` | `false` | For OpenAI Responses child models, inject only the named `pi-codex-minimal-tools` identity lifecycle inline. Codex Session/Thread/Turn/Window ids remain owned and serialized by that package. |
258
260
  | `maxOutputBytes` | `51200` | Cap for parent-visible foreground output, reports, and settlement notices. Full output remains in the child session. |
259
261
 
260
262
  Invalid configuration and unknown child tool names fail loud before the child's first model request.
261
263
 
264
+ `openAIIdentity` and `inheritExtensions` are independent. The former adds only
265
+ the lightweight Codex identity lifecycle even when normal extension inheritance
266
+ is disabled. Enable `inheritExtensions` as well when the child should receive
267
+ the complete separately installed Codex extension tool surface such as
268
+ `web_search` and `apply_patch`.
269
+
270
+ The Codex adapter is an optional package dependency. If an installation omits
271
+ optional dependencies, `openAIIdentity: true` fails before the child starts
272
+ with an actionable missing-adapter error.
273
+
262
274
  ### Foreground-only mode
263
275
 
264
276
  ```json
@@ -301,7 +313,11 @@ The caller waits for one isolated child run. Only the child's last non-empty ass
301
313
 
302
314
  ### Continuable
303
315
 
304
- The start tool resolves at prompt preflight acceptance and returns the stable child session id. When an activation settles:
316
+ The start tool resolves at prompt preflight acceptance and returns the child's durable
317
+ agent id (UUIDv7). Agent ids are independent of Pi session (file) ids: they are
318
+ generated once per subagent, recorded in the child's session as `pi-subagent/agent`,
319
+ and chained through `parentAgentId` in the descriptor, so children stay addressable
320
+ even when a parent session is forked or re-created. When an activation settles:
305
321
 
306
322
  1. the runtime sends the parent a settlement notice with the stop reason and closing message;
307
323
  2. the child runtime is disposed once its owned descendants are done;
@@ -7,5 +7,6 @@
7
7
  "defaultBackground": true,
8
8
  "reportDelivery": "wakeup",
9
9
  "inheritExtensions": false,
10
+ "openAIIdentity": false,
10
11
  "maxOutputBytes": 51200
11
12
  }
@@ -50,6 +50,11 @@
50
50
  "description": "Load the parent's other Pi extensions in child runtimes. pi-subagent itself is filtered out and explicit agent tool ceilings still apply.",
51
51
  "default": false
52
52
  },
53
+ "openAIIdentity": {
54
+ "type": "boolean",
55
+ "description": "Inject the pi-codex-minimal-tools identity lifecycle as a named inline extension for OpenAI Responses child models.",
56
+ "default": false
57
+ },
53
58
  "maxOutputBytes": {
54
59
  "type": "integer",
55
60
  "minimum": 1024,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oai404iao/pi-subagent",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Durable, continuable subagents for Pi with spawn/fork providers and lifecycle controls.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,6 +37,9 @@
37
37
  "@earendil-works/pi-tui": ">=0.84.2",
38
38
  "typebox": "*"
39
39
  },
40
+ "optionalDependencies": {
41
+ "@oai404iao/pi-codex-minimal-tools": "1.4.0"
42
+ },
40
43
  "devDependencies": {
41
44
  "@earendil-works/pi-agent-core": "^0.84.2",
42
45
  "@earendil-works/pi-ai": "^0.84.2",
@@ -84,5 +87,5 @@
84
87
  "optional": true
85
88
  }
86
89
  },
87
- "gitHead": "ef42984c0e40ef1f26ead4b4c7d149b21280e66b"
90
+ "gitHead": "a93559be8f8b02739310c492b36270b13607fbf9"
88
91
  }
package/src/catalog.ts CHANGED
@@ -4,7 +4,7 @@ import type { SessionView } from "./providers.ts";
4
4
  import type { CatalogDiagnostic, SubagentDescriptor } from "./types.ts";
5
5
 
6
6
  export interface PersistedDescriptor {
7
- id: string;
7
+ agentId: string;
8
8
  sessionFile: string;
9
9
  descriptor: SubagentDescriptor;
10
10
  }
@@ -44,7 +44,7 @@ export async function readPersistedCatalog(session: SessionView): Promise<Persis
44
44
  if (headerParent !== folded.descriptor.parentSessionFile) {
45
45
  diagnostics.push({
46
46
  kind: "diagnostic",
47
- id: manager.getSessionId(),
47
+ piSessionId: manager.getSessionId(),
48
48
  reason: "corrupt",
49
49
  sessionFile: info.path,
50
50
  ...(headerParent ? { parentSessionFile: headerParent } : {}),
@@ -53,7 +53,7 @@ export async function readPersistedCatalog(session: SessionView): Promise<Persis
53
53
  return;
54
54
  }
55
55
  descriptors.push({
56
- id: manager.getSessionId(),
56
+ agentId: folded.descriptor.agentId,
57
57
  sessionFile: info.path,
58
58
  descriptor: folded.descriptor,
59
59
  });
@@ -61,7 +61,7 @@ export async function readPersistedCatalog(session: SessionView): Promise<Persis
61
61
  const headerParent = manager.getHeader()?.parentSession;
62
62
  diagnostics.push({
63
63
  kind: "diagnostic",
64
- id: manager.getSessionId(),
64
+ piSessionId: manager.getSessionId(),
65
65
  reason: "corrupt",
66
66
  sessionFile: info.path,
67
67
  ...(headerParent ? { parentSessionFile: headerParent } : {}),
@@ -71,7 +71,7 @@ export async function readPersistedCatalog(session: SessionView): Promise<Persis
71
71
  } catch (error) {
72
72
  diagnostics.push({
73
73
  kind: "diagnostic",
74
- id: info.id,
74
+ piSessionId: info.id,
75
75
  reason: "unavailable",
76
76
  sessionFile: info.path,
77
77
  ...(info.parentSessionPath ? { parentSessionFile: info.parentSessionPath } : {}),
@@ -82,8 +82,11 @@ export async function readPersistedCatalog(session: SessionView): Promise<Persis
82
82
 
83
83
  descriptors.sort(
84
84
  (left, right) =>
85
- left.descriptor.createdAt.localeCompare(right.descriptor.createdAt) || left.id.localeCompare(right.id),
85
+ left.descriptor.createdAt.localeCompare(right.descriptor.createdAt) ||
86
+ left.agentId.localeCompare(right.agentId),
87
+ );
88
+ diagnostics.sort((left, right) =>
89
+ left.piSessionId.localeCompare(right.piSessionId),
86
90
  );
87
- diagnostics.sort((left, right) => left.id.localeCompare(right.id));
88
91
  return { descriptors, diagnostics };
89
92
  }
package/src/config.ts CHANGED
@@ -13,6 +13,7 @@ export const DEFAULT_SETTINGS: Readonly<SubagentSettings> = {
13
13
  defaultBackground: true,
14
14
  reportDelivery: "wakeup",
15
15
  inheritExtensions: false,
16
+ openAIIdentity: false,
16
17
  maxOutputBytes: 50 * 1024,
17
18
  };
18
19
 
@@ -25,6 +26,7 @@ const CONFIG_KEYS = new Set([
25
26
  "defaultBackground",
26
27
  "reportDelivery",
27
28
  "inheritExtensions",
29
+ "openAIIdentity",
28
30
  "maxOutputBytes",
29
31
  ]);
30
32
 
@@ -147,6 +149,10 @@ function applyConfig(
147
149
  config.inheritExtensions === undefined
148
150
  ? settings.inheritExtensions
149
151
  : parseBoolean(config.inheritExtensions, "inheritExtensions", source),
152
+ openAIIdentity:
153
+ config.openAIIdentity === undefined
154
+ ? settings.openAIIdentity
155
+ : parseBoolean(config.openAIIdentity, "openAIIdentity", source),
150
156
  maxOutputBytes:
151
157
  config.maxOutputBytes === undefined
152
158
  ? settings.maxOutputBytes
@@ -1,13 +1,13 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import { join, relative, resolve } from "node:path";
3
2
  import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
4
- import type { Model } from "@earendil-works/pi-ai";
3
+ import { type Model, uuidv7 } from "@earendil-works/pi-ai";
5
4
  import {
6
5
  type AgentSessionEvent,
7
6
  type AgentToolResult,
8
7
  type AgentToolUpdateCallback,
9
8
  type ExtensionAPI,
10
9
  type ExtensionContext,
10
+ type InlineExtension,
11
11
  type ModelRegistry,
12
12
  type ToolDefinition,
13
13
  type CreateAgentSessionRuntimeFactory,
@@ -75,6 +75,10 @@ import {
75
75
 
76
76
  const REPORT_CUSTOM_TYPE = "pi-subagent/report";
77
77
  const SETTLED_CUSTOM_TYPE = "pi-subagent/settled";
78
+ const AGENT_CUSTOM_TYPE = "pi-subagent/agent";
79
+ const LINEAGE_CUSTOM_TYPE = "pi-subagent/lineage";
80
+ const AGENT_ID_PATTERN =
81
+ /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
78
82
  const MAX_TRACE_ITEMS = 100;
79
83
  const MAX_TRACE_TEXT = 4000;
80
84
  const BACKGROUND_CONTROL_TOOLS = new Set([
@@ -107,7 +111,7 @@ export type DelegationOutcome =
107
111
  | { kind: "foreground"; details: DelegationDetails; result: SubagentRunResult };
108
112
 
109
113
  interface ParentRef {
110
- id: string;
114
+ agentId: string;
111
115
  depth: number;
112
116
  cwd: string;
113
117
  sessionManager: SessionView;
@@ -125,8 +129,8 @@ interface ParentRef {
125
129
  }
126
130
 
127
131
  interface Activation {
128
- id: string;
129
- epochId: string;
132
+ agentId: string;
133
+ runId: string;
130
134
  descriptor: SubagentDescriptor;
131
135
  parent: ParentRef;
132
136
  runtime: AgentSessionRuntime;
@@ -158,7 +162,7 @@ interface CreateActivationOptions {
158
162
  }
159
163
 
160
164
  interface CatalogRecord {
161
- id: string;
165
+ agentId: string;
162
166
  descriptor: SubagentDescriptor;
163
167
  sessionFile?: string;
164
168
  active?: Activation;
@@ -191,6 +195,41 @@ function errorText(error: unknown): string {
191
195
  return error instanceof Error ? error.message : String(error);
192
196
  }
193
197
 
198
+ /**
199
+ * Read the durable pi-subagent control id recorded for a session, if any.
200
+ *
201
+ * The latest entry wins because a copied/forked Pi session may contain older
202
+ * identity checkpoints.
203
+ */
204
+ function readAgentId(session: Pick<SessionView, "getEntries">): string | undefined {
205
+ const entries = session.getEntries();
206
+ for (let index = entries.length - 1; index >= 0; index--) {
207
+ const entry = entries[index];
208
+ if (!entry) continue;
209
+ if (entry.type !== "custom" || entry.customType !== AGENT_CUSTOM_TYPE) continue;
210
+ const data = entry.data as { agentId?: unknown } | undefined;
211
+ if (
212
+ typeof data?.agentId === "string"
213
+ && AGENT_ID_PATTERN.test(data.agentId)
214
+ ) {
215
+ return data.agentId;
216
+ }
217
+ }
218
+ return undefined;
219
+ }
220
+
221
+ /**
222
+ * Resolve the provider-neutral control id for a session, creating it on first
223
+ * use. In-memory SessionManager instances retain custom entries too.
224
+ */
225
+ function ensureAgentId(session: SessionView): string {
226
+ const existing = readAgentId(session);
227
+ if (existing) return existing;
228
+ const agentId = uuidv7();
229
+ session.appendCustomEntry(AGENT_CUSTOM_TYPE, { agentId });
230
+ return agentId;
231
+ }
232
+
194
233
  function stopReasonHeadline(reason: SubagentStopReason): string {
195
234
  switch (reason) {
196
235
  case "completed":
@@ -213,6 +252,7 @@ function makeRuntimeSettings(descriptor: SubagentDescriptor): SubagentSettings {
213
252
  defaultBackground: descriptor.runtime.defaultBackground,
214
253
  reportDelivery: descriptor.runtime.reportDelivery,
215
254
  inheritExtensions: descriptor.runtime.inheritExtensions,
255
+ openAIIdentity: descriptor.runtime.openAIIdentity,
216
256
  maxOutputBytes: descriptor.runtime.maxOutputBytes,
217
257
  };
218
258
  }
@@ -222,6 +262,29 @@ function isPathInside(parent: string, child: string): boolean {
222
262
  return rel === "" || (!rel.startsWith("..") && !rel.startsWith("/"));
223
263
  }
224
264
 
265
+ function isOpenAIResponsesModel(model: Model<any>): boolean {
266
+ return model.api === "openai-responses"
267
+ || model.api === "openai-codex-responses";
268
+ }
269
+
270
+ async function loadCodexIdentityInlineExtension(
271
+ parentSessionManager: SessionView,
272
+ ): Promise<InlineExtension> {
273
+ try {
274
+ const integration = await import(
275
+ "@oai404iao/pi-codex-minimal-tools/subagent-inline"
276
+ );
277
+ return integration.createCodexSubagentInlineExtension({
278
+ parentSessionManager,
279
+ });
280
+ } catch (error) {
281
+ throw new Error(
282
+ "openAIIdentity requires @oai404iao/pi-codex-minimal-tools with its subagent-inline export",
283
+ { cause: error },
284
+ );
285
+ }
286
+ }
287
+
225
288
  export class SubagentCoordinator {
226
289
  private readonly providers = new ProviderRegistry();
227
290
  private readonly active = new Map<string, Activation>();
@@ -278,10 +341,15 @@ export class SubagentCoordinator {
278
341
  const descriptor = folded.kind === "valid" ? folded.descriptor : undefined;
279
342
  const modelRuntime = runtimeFromRegistry(ctx.modelRegistry);
280
343
  return {
281
- id: ctx.sessionManager.getSessionId(),
344
+ agentId: readAgentId(ctx.sessionManager) ?? ensureAgentId(
345
+ ctx.sessionManager as unknown as SessionView,
346
+ ),
282
347
  depth: descriptor?.depth ?? 0,
283
348
  cwd: ctx.cwd,
284
- sessionManager: ctx.sessionManager,
349
+ // ExtensionContext narrows the live SessionManager to a read-only
350
+ // view; appending the agent entry through the same instance keeps
351
+ // the in-memory tree and the session file consistent.
352
+ sessionManager: ctx.sessionManager as unknown as SessionView,
285
353
  modelRuntime,
286
354
  model: ctx.model,
287
355
  thinkingLevel: ctx.thinkingLevel ?? "off",
@@ -362,12 +430,17 @@ export class SubagentCoordinator {
362
430
  const model = this.resolveModel(parent, agent);
363
431
  const thinkingLevel = agent.thinking ?? parent.thinkingLevel;
364
432
  const prepared = await provider.prepare(parent, mode);
433
+ parent.agentId = ensureAgentId(parent.sessionManager);
434
+ const openAIIdentityEnabled =
435
+ settings.openAIIdentity && isOpenAIResponsesModel(model);
365
436
  const descriptor: SubagentDescriptor = {
366
- version: 1,
437
+ version: 2,
367
438
  mode,
368
439
  provider: providerName,
369
440
  label: input.description.trim(),
370
- parentSessionId: parent.id,
441
+ agentId: uuidv7(),
442
+ parentAgentId: parent.agentId,
443
+ parentPiSessionId: parent.sessionManager.getSessionId(),
371
444
  ...(parent.sessionManager.getSessionFile()
372
445
  ? { parentSessionFile: parent.sessionManager.getSessionFile() }
373
446
  : {}),
@@ -385,9 +458,25 @@ export class SubagentCoordinator {
385
458
  defaultBackground: settings.defaultBackground,
386
459
  reportDelivery: settings.reportDelivery,
387
460
  inheritExtensions: settings.inheritExtensions,
461
+ openAIIdentity: openAIIdentityEnabled,
388
462
  maxOutputBytes: settings.maxOutputBytes,
389
463
  },
390
464
  };
465
+ prepared.sessionManager.appendCustomEntry(AGENT_CUSTOM_TYPE, {
466
+ agentId: descriptor.agentId,
467
+ });
468
+ prepared.sessionManager.appendCustomEntry(LINEAGE_CUSTOM_TYPE, {
469
+ version: 1,
470
+ agentId: descriptor.agentId,
471
+ parentAgentId: descriptor.parentAgentId,
472
+ parentPiSessionId: descriptor.parentPiSessionId,
473
+ relation: descriptor.provider,
474
+ agentName: descriptor.agent.name,
475
+ openAIIdentity: openAIIdentityEnabled,
476
+ ...(descriptor.parentSessionFile
477
+ ? { parentSessionFile: descriptor.parentSessionFile }
478
+ : {}),
479
+ });
391
480
 
392
481
  let activation: Activation | undefined;
393
482
  try {
@@ -399,7 +488,7 @@ export class SubagentCoordinator {
399
488
  onUpdate,
400
489
  });
401
490
  if (mode === "continuable" && parent.activation) {
402
- parent.activation.ownedChildren.add(activation.id);
491
+ parent.activation.ownedChildren.add(activation.agentId);
403
492
  }
404
493
  const started = this.startPrompt(activation, input.prompt, signal, mode === "continuable");
405
494
  await started.accepted;
@@ -456,7 +545,7 @@ export class SubagentCoordinator {
456
545
  prepared: coldPrepared,
457
546
  isNew: false,
458
547
  });
459
- if (parent.activation) parent.activation.ownedChildren.add(activation.id);
548
+ if (parent.activation) parent.activation.ownedChildren.add(activation.agentId);
460
549
  } else {
461
550
  this.assertDirectParent(parent, activation.descriptor);
462
551
  }
@@ -484,7 +573,7 @@ export class SubagentCoordinator {
484
573
  const target = this.active.get(targetId);
485
574
  if (!target) return;
486
575
  if (!(await this.isDescendantOf(parent, target.descriptor))) {
487
- throw new Error(`subagent ${targetId} is not a live descendant of ${parent.id}`);
576
+ throw new Error(`subagent ${targetId} is not a live descendant of ${parent.agentId}`);
488
577
  }
489
578
  target.status = "failed";
490
579
  void target.runtime.session.abort().catch((error) => {
@@ -495,16 +584,16 @@ export class SubagentCoordinator {
495
584
  async list(parent: ParentRef, scope: "children" | "descendants"): Promise<CatalogEntry[]> {
496
585
  const catalog = await this.catalogRecords(parent);
497
586
  const records = catalog.records;
498
- const byId = new Map(records.map((record) => [record.id, record]));
587
+ const byId = new Map(records.map((record) => [record.agentId, record]));
499
588
  const children: CatalogChild[] = [];
500
589
  for (const record of records) {
501
590
  if (record.descriptor.mode !== "continuable") continue;
502
- const distance = this.distanceFrom(parent.id, record.descriptor, byId);
591
+ const distance = this.distanceFrom(parent.agentId, record.descriptor, byId);
503
592
  if (distance === undefined || (scope === "children" && distance !== 1)) continue;
504
593
  children.push({
505
594
  kind: "child",
506
- id: record.id,
507
- parentId: record.descriptor.parentSessionId,
595
+ agentId: record.agentId,
596
+ parentAgentId: record.descriptor.parentAgentId,
508
597
  depth: distance,
509
598
  descriptor: record.descriptor,
510
599
  ...(record.sessionFile ? { sessionFile: record.sessionFile } : {}),
@@ -517,7 +606,8 @@ export class SubagentCoordinator {
517
606
  }
518
607
  children.sort(
519
608
  (left, right) =>
520
- left.descriptor.createdAt.localeCompare(right.descriptor.createdAt) || left.id.localeCompare(right.id),
609
+ left.descriptor.createdAt.localeCompare(right.descriptor.createdAt) ||
610
+ left.agentId.localeCompare(right.agentId),
521
611
  );
522
612
  const parentFile = parent.sessionManager.getSessionFile();
523
613
  const diagnostics = parentFile
@@ -533,7 +623,7 @@ export class SubagentCoordinator {
533
623
  throw new Error("report is available only to continuable subagents");
534
624
  }
535
625
  const truncated = truncateUtf8(output, child.descriptor.runtime.maxOutputBytes);
536
- const content = `Background subagent ${child.id} reported:\n\n${truncated.text}${
626
+ const content = `Background subagent ${child.agentId} reported:\n\n${truncated.text}${
537
627
  truncated.truncated ? `\n\n[Report truncated; ${truncated.omittedBytes} bytes omitted.]` : ""
538
628
  }`;
539
629
  await child.parent.deliver(
@@ -541,7 +631,7 @@ export class SubagentCoordinator {
541
631
  content,
542
632
  {
543
633
  kind: "report",
544
- childId: child.id,
634
+ childAgentId: child.agentId,
545
635
  label: child.descriptor.label,
546
636
  ...(truncated.truncated ? { truncated: true } : {}),
547
637
  },
@@ -653,7 +743,7 @@ export class SubagentCoordinator {
653
743
  text: `message queued as the next turn for subagent ${params.subagent_id}`,
654
744
  },
655
745
  ],
656
- details: { kind: "control", action: "send", id: params.subagent_id } satisfies ControlDetails,
746
+ details: { kind: "control", action: "send", agentId: params.subagent_id } satisfies ControlDetails,
657
747
  };
658
748
  },
659
749
  });
@@ -670,7 +760,7 @@ export class SubagentCoordinator {
670
760
  await this.interrupt(this.parentForActivation(activation), params.agent_id);
671
761
  return {
672
762
  content: [{ type: "text", text: `interrupt requested for agent ${params.agent_id}` }],
673
- details: { kind: "control", action: "interrupt", id: params.agent_id } satisfies ControlDetails,
763
+ details: { kind: "control", action: "interrupt", agentId: params.agent_id } satisfies ControlDetails,
674
764
  };
675
765
  },
676
766
  });
@@ -706,7 +796,7 @@ export class SubagentCoordinator {
706
796
  await this.report(activation, params.output);
707
797
  return {
708
798
  content: [{ type: "text", text: `report accepted by the agent that started you` }],
709
- details: { kind: "control", action: "report", id: activation.id } satisfies ControlDetails,
799
+ details: { kind: "control", action: "report", agentId: activation.agentId } satisfies ControlDetails,
710
800
  };
711
801
  },
712
802
  });
@@ -717,7 +807,7 @@ export class SubagentCoordinator {
717
807
  outcomeToolResult(outcome: DelegationOutcome): AgentToolResult<DelegationDetails> {
718
808
  if (outcome.kind === "continuable") {
719
809
  return {
720
- content: [{ type: "text", text: `started subagent ${outcome.details.id}` }],
810
+ content: [{ type: "text", text: `started subagent ${outcome.details.agentId}` }],
721
811
  details: outcome.details,
722
812
  };
723
813
  }
@@ -740,10 +830,12 @@ export class SubagentCoordinator {
740
830
  if (entries.length === 0) return "(no subagents)";
741
831
  return entries
742
832
  .map((entry) => {
743
- if (entry.kind === "diagnostic") return `${entry.id} [diagnostic: ${entry.reason}]`;
833
+ if (entry.kind === "diagnostic") {
834
+ return `${entry.piSessionId} [diagnostic: ${entry.reason}]`;
835
+ }
744
836
  const location =
745
- scope === "descendants" ? ` parent=${entry.parentId} depth=${entry.depth}` : "";
746
- return `${entry.id} [${entry.status}]${location} — ${entry.descriptor.label} (${entry.descriptor.agent.name})`;
837
+ scope === "descendants" ? ` parent=${entry.parentAgentId} depth=${entry.depth}` : "";
838
+ return `${entry.agentId} [${entry.status}]${location} — ${entry.descriptor.label} (${entry.descriptor.agent.name})`;
747
839
  })
748
840
  .join("\n");
749
841
  }
@@ -801,6 +893,14 @@ export class SubagentCoordinator {
801
893
  `cannot materialize subagent: model ${descriptor.model.provider}/${descriptor.model.id} is unavailable`,
802
894
  );
803
895
  }
896
+ const extensionFactories: InlineExtension[] =
897
+ descriptor.runtime.openAIIdentity && isOpenAIResponsesModel(model)
898
+ ? [
899
+ await loadCodexIdentityInlineExtension(
900
+ options.parent.sessionManager,
901
+ ),
902
+ ]
903
+ : [];
804
904
 
805
905
  const appendSystemPrompt = [
806
906
  descriptor.agent.systemPrompt,
@@ -836,10 +936,13 @@ export class SubagentCoordinator {
836
936
  noExtensions: !descriptor.runtime.inheritExtensions,
837
937
  noThemes: true,
838
938
  appendSystemPrompt,
939
+ extensionFactories,
839
940
  extensionsOverride: (base) => ({
840
941
  ...base,
841
942
  extensions: base.extensions.filter(
842
- (extension) => !isPathInside(this.packageRoot, extension.resolvedPath),
943
+ (extension) =>
944
+ extension.resolvedPath.startsWith("<inline:")
945
+ || !isPathInside(this.packageRoot, extension.resolvedPath),
843
946
  ),
844
947
  }),
845
948
  },
@@ -935,8 +1038,8 @@ export class SubagentCoordinator {
935
1038
  }
936
1039
 
937
1040
  activation = {
938
- id: runtime.session.sessionId,
939
- epochId: randomUUID(),
1041
+ agentId: descriptor.agentId,
1042
+ runId: uuidv7(),
940
1043
  descriptor,
941
1044
  parent: options.parent,
942
1045
  runtime,
@@ -954,7 +1057,7 @@ export class SubagentCoordinator {
954
1057
  disposed: false,
955
1058
  };
956
1059
  activation.unsubscribe = runtime.session.subscribe((event) => this.observe(activation!, event));
957
- this.active.set(activation.id, activation);
1060
+ this.active.set(activation.agentId, activation);
958
1061
  return activation;
959
1062
  } catch (error) {
960
1063
  await runtime.dispose().catch(() => {});
@@ -968,7 +1071,7 @@ export class SubagentCoordinator {
968
1071
  signal: AbortSignal | undefined,
969
1072
  detachAtAcceptance: boolean,
970
1073
  ): { accepted: Promise<void>; result: Promise<SubagentRunResult> } {
971
- if (activation.currentRun) throw new Error(`subagent ${activation.id} is already running`);
1074
+ if (activation.currentRun) throw new Error(`subagent ${activation.agentId} is already running`);
972
1075
  if (signal?.aborted) throw signal.reason ?? new Error("subagent start aborted");
973
1076
  activation.pendingSettlement = undefined;
974
1077
  activation.status = "running";
@@ -1111,7 +1214,7 @@ export class SubagentCoordinator {
1111
1214
  const closing = truncated.text.trim()
1112
1215
  ? `Its closing message:\n\n${truncated.text}`
1113
1216
  : "It left no closing message.";
1114
- const content = `Background subagent ${activation.id} ${stopReasonHeadline(result.stopReason)} and will do no further work unless you send it more.\n\n${closing}${
1217
+ const content = `Background subagent ${activation.agentId} ${stopReasonHeadline(result.stopReason)} and will do no further work unless you send it more.\n\n${closing}${
1115
1218
  truncated.truncated ? `\n\n[Closing message truncated; ${truncated.omittedBytes} bytes omitted.]` : ""
1116
1219
  }`;
1117
1220
  await activation.parent.deliver(
@@ -1119,7 +1222,7 @@ export class SubagentCoordinator {
1119
1222
  content,
1120
1223
  {
1121
1224
  kind: "settled",
1122
- childId: activation.id,
1225
+ childAgentId: activation.agentId,
1123
1226
  label: activation.descriptor.label,
1124
1227
  stopReason: result.stopReason,
1125
1228
  ...(truncated.truncated ? { truncated: true } : {}),
@@ -1130,7 +1233,7 @@ export class SubagentCoordinator {
1130
1233
 
1131
1234
  private parentForActivation(activation: Activation): ParentRef {
1132
1235
  return {
1133
- id: activation.id,
1236
+ agentId: activation.agentId,
1134
1237
  depth: activation.descriptor.depth,
1135
1238
  cwd: activation.descriptor.cwd,
1136
1239
  sessionManager: activation.runtime.session.sessionManager,
@@ -1140,7 +1243,7 @@ export class SubagentCoordinator {
1140
1243
  projectTrusted: activation.parent.projectTrusted,
1141
1244
  activation,
1142
1245
  deliver: async (customType, content, details, delivery) => {
1143
- if (activation.disposed) throw new Error(`parent subagent ${activation.id} is no longer resident`);
1246
+ if (activation.disposed) throw new Error(`parent subagent ${activation.agentId} is no longer resident`);
1144
1247
  const session = activation.runtime.session;
1145
1248
  if (delivery === "quiet") {
1146
1249
  await session.sendCustomMessage(
@@ -1170,7 +1273,8 @@ export class SubagentCoordinator {
1170
1273
  const truncated = truncateUtf8(output, activation.descriptor.runtime.maxOutputBytes);
1171
1274
  const sessionFile = activation.runtime.session.sessionFile;
1172
1275
  return {
1173
- id: activation.id,
1276
+ agentId: activation.agentId,
1277
+ piSessionId: activation.runtime.session.sessionId,
1174
1278
  ...(sessionFile ? { sessionFile } : {}),
1175
1279
  output: truncated.truncated
1176
1280
  ? `${truncated.text}\n\n[Output truncated; ${truncated.omittedBytes} bytes omitted.${
@@ -1224,22 +1328,24 @@ export class SubagentCoordinator {
1224
1328
  if (activation.published) return;
1225
1329
  activation.published = true;
1226
1330
  this.pi.events.emit("pi-subagent:start", {
1227
- runId: activation.epochId,
1228
- id: activation.id,
1331
+ runId: activation.runId,
1332
+ agentId: activation.agentId,
1333
+ piSessionId: activation.runtime.session.sessionId,
1334
+ parentAgentId: activation.descriptor.parentAgentId,
1229
1335
  provider: activation.descriptor.provider,
1230
1336
  mode: activation.descriptor.mode,
1231
- parentId: activation.descriptor.parentSessionId,
1232
1337
  });
1233
1338
  }
1234
1339
 
1235
1340
  private emitEnd(activation: Activation, result: SubagentRunResult): void {
1236
1341
  if (!activation.published) return;
1237
1342
  this.pi.events.emit("pi-subagent:end", {
1238
- runId: activation.epochId,
1239
- id: activation.id,
1343
+ runId: activation.runId,
1344
+ agentId: activation.agentId,
1345
+ piSessionId: activation.runtime.session.sessionId,
1346
+ parentAgentId: activation.descriptor.parentAgentId,
1240
1347
  provider: activation.descriptor.provider,
1241
1348
  mode: activation.descriptor.mode,
1242
- parentId: activation.descriptor.parentSessionId,
1243
1349
  stopReason: result.stopReason,
1244
1350
  output: result.output,
1245
1351
  });
@@ -1248,7 +1354,8 @@ export class SubagentCoordinator {
1248
1354
  private detailsOf(activation: Activation, result?: SubagentRunResult): DelegationDetails {
1249
1355
  return {
1250
1356
  kind: "delegation",
1251
- id: activation.id,
1357
+ agentId: activation.agentId,
1358
+ piSessionId: activation.runtime.session.sessionId,
1252
1359
  provider: activation.descriptor.provider,
1253
1360
  mode: activation.descriptor.mode,
1254
1361
  agent: activation.descriptor.agent.name,
@@ -1302,14 +1409,14 @@ export class SubagentCoordinator {
1302
1409
  try {
1303
1410
  await activation.runtime.dispose();
1304
1411
  } finally {
1305
- if (this.active.get(activation.id) === activation) this.active.delete(activation.id);
1412
+ if (this.active.get(activation.agentId) === activation) this.active.delete(activation.agentId);
1306
1413
  }
1307
1414
  }
1308
1415
 
1309
1416
  private async releaseParentOwnership(activation: Activation): Promise<void> {
1310
1417
  const owner = activation.parent.activation;
1311
1418
  if (!owner) return;
1312
- owner.ownedChildren.delete(activation.id);
1419
+ owner.ownedChildren.delete(activation.agentId);
1313
1420
  if (
1314
1421
  !owner.currentRun &&
1315
1422
  owner.ownedChildren.size === 0 &&
@@ -1321,9 +1428,9 @@ export class SubagentCoordinator {
1321
1428
  }
1322
1429
 
1323
1430
  private assertDirectParent(parent: ParentRef, descriptor: SubagentDescriptor): void {
1324
- if (descriptor.parentSessionId !== parent.id) {
1431
+ if (descriptor.parentAgentId !== parent.agentId) {
1325
1432
  throw new Error(
1326
- `subagent ${descriptor.label} is not a direct child of ${parent.id}; message was not delivered`,
1433
+ `subagent ${descriptor.label} is not a direct child of ${parent.agentId}; message was not delivered`,
1327
1434
  );
1328
1435
  }
1329
1436
  }
@@ -1333,7 +1440,7 @@ export class SubagentCoordinator {
1333
1440
  childId: string,
1334
1441
  ): Promise<{ descriptor: SubagentDescriptor; sessionFile: string } | undefined> {
1335
1442
  const catalog = await readPersistedCatalog(parent.sessionManager);
1336
- const entry = catalog.descriptors.find((candidate) => candidate.id === childId);
1443
+ const entry = catalog.descriptors.find((candidate) => candidate.agentId === childId);
1337
1444
  return entry ? { descriptor: entry.descriptor, sessionFile: entry.sessionFile } : undefined;
1338
1445
  }
1339
1446
 
@@ -1341,16 +1448,19 @@ export class SubagentCoordinator {
1341
1448
  const persisted = await readPersistedCatalog(parent.sessionManager);
1342
1449
  const records = new Map<string, CatalogRecord>();
1343
1450
  for (const item of persisted.descriptors) {
1344
- records.set(item.id, {
1345
- id: item.id,
1451
+ records.set(item.agentId, {
1452
+ agentId: item.agentId,
1346
1453
  descriptor: item.descriptor,
1347
1454
  sessionFile: item.sessionFile,
1348
1455
  });
1349
1456
  }
1457
+ const activeSessionIds = new Set(
1458
+ [...this.active.values()].map((activation) => activation.runtime.session.sessionId),
1459
+ );
1350
1460
  for (const activation of this.active.values()) {
1351
1461
  if (activation.descriptor.cwd !== parent.cwd) continue;
1352
- records.set(activation.id, {
1353
- id: activation.id,
1462
+ records.set(activation.agentId, {
1463
+ agentId: activation.agentId,
1354
1464
  descriptor: activation.descriptor,
1355
1465
  ...(activation.runtime.session.sessionFile
1356
1466
  ? { sessionFile: activation.runtime.session.sessionFile }
@@ -1360,7 +1470,9 @@ export class SubagentCoordinator {
1360
1470
  }
1361
1471
  return {
1362
1472
  records: [...records.values()],
1363
- diagnostics: persisted.diagnostics.filter((diagnostic) => !this.active.has(diagnostic.id)),
1473
+ diagnostics: persisted.diagnostics.filter(
1474
+ (diagnostic) => !activeSessionIds.has(diagnostic.piSessionId),
1475
+ ),
1364
1476
  };
1365
1477
  }
1366
1478
 
@@ -1369,7 +1481,7 @@ export class SubagentCoordinator {
1369
1481
  descriptor: SubagentDescriptor,
1370
1482
  byId: Map<string, CatalogRecord>,
1371
1483
  ): number | undefined {
1372
- let parentId = descriptor.parentSessionId;
1484
+ let parentId = descriptor.parentAgentId;
1373
1485
  let distance = 1;
1374
1486
  const visited = new Set<string>();
1375
1487
  while (true) {
@@ -1378,16 +1490,21 @@ export class SubagentCoordinator {
1378
1490
  visited.add(parentId);
1379
1491
  const parent = byId.get(parentId);
1380
1492
  if (!parent) return undefined;
1381
- parentId = parent.descriptor.parentSessionId;
1493
+ parentId = parent.descriptor.parentAgentId;
1382
1494
  distance++;
1383
1495
  }
1384
1496
  }
1385
1497
 
1386
1498
  private async isDescendantOf(parent: ParentRef, descriptor: SubagentDescriptor): Promise<boolean> {
1387
1499
  const { records } = await this.catalogRecords(parent);
1388
- const byId = new Map(records.map((record) => [record.id, record]));
1389
- return this.distanceFrom(parent.id, descriptor, byId) !== undefined;
1500
+ const byId = new Map(records.map((record) => [record.agentId, record]));
1501
+ return this.distanceFrom(parent.agentId, descriptor, byId) !== undefined;
1390
1502
  }
1391
1503
  }
1392
1504
 
1393
- export { REPORT_CUSTOM_TYPE, SETTLED_CUSTOM_TYPE };
1505
+ export {
1506
+ REPORT_CUSTOM_TYPE,
1507
+ SETTLED_CUSTOM_TYPE,
1508
+ AGENT_CUSTOM_TYPE,
1509
+ LINEAGE_CUSTOM_TYPE,
1510
+ };
package/src/descriptor.ts CHANGED
@@ -11,7 +11,7 @@ import type {
11
11
  } from "./types.ts";
12
12
 
13
13
  export const DESCRIPTOR_CUSTOM_TYPE = "pi-subagent/descriptor";
14
- export const DESCRIPTOR_VERSION = 1;
14
+ export const DESCRIPTOR_VERSION = 2;
15
15
 
16
16
  const THINKING_LEVELS = new Set<ThinkingLevel>(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
17
17
  const AGENT_SOURCES = new Set<AgentSource>(["bundled", "user", "project"]);
@@ -20,6 +20,8 @@ const PROVIDERS = new Set<SubagentProviderName>(["spawn", "fork"]);
20
20
  const REPORT_DELIVERIES = new Set<ReportDelivery>(["wakeup", "quiet"]);
21
21
  const AGENT_SCOPES = new Set<AgentScope>(["user", "project", "both"]);
22
22
  const AGENT_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
23
+ /** UUIDv7 with the standard RFC 9562 variant bits (version 7, variant 10xx). */
24
+ const AGENT_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
23
25
 
24
26
  type UnknownRecord = Record<string, unknown>;
25
27
 
@@ -46,6 +48,14 @@ function optionalString(value: unknown, field: string): string | undefined {
46
48
  return value === undefined ? undefined : string(value, field);
47
49
  }
48
50
 
51
+ function agentId(value: unknown, field: string): string {
52
+ const parsed = string(value, field);
53
+ if (!AGENT_ID_PATTERN.test(parsed)) {
54
+ throw new Error(`${field} must be a UUIDv7 agent id`);
55
+ }
56
+ return parsed;
57
+ }
58
+
49
59
  function limitedString(value: unknown, field: string, maxLength: number): string {
50
60
  const parsed = string(value, field);
51
61
  if (parsed.length > maxLength) throw new Error(`${field} exceeds ${maxLength} characters`);
@@ -135,7 +145,9 @@ export function parseDescriptor(value: unknown): SubagentDescriptor {
135
145
  mode,
136
146
  provider,
137
147
  label: limitedString(input.label, "label", 200),
138
- parentSessionId: string(input.parentSessionId, "parentSessionId"),
148
+ agentId: agentId(input.agentId, "agentId"),
149
+ parentAgentId: agentId(input.parentAgentId, "parentAgentId"),
150
+ parentPiSessionId: string(input.parentPiSessionId, "parentPiSessionId"),
139
151
  ...(optionalString(input.parentSessionFile, "parentSessionFile")
140
152
  ? { parentSessionFile: input.parentSessionFile as string }
141
153
  : {}),
@@ -162,6 +174,7 @@ export function parseDescriptor(value: unknown): SubagentDescriptor {
162
174
  defaultBackground: boolean(runtime.defaultBackground, "runtime.defaultBackground"),
163
175
  reportDelivery,
164
176
  inheritExtensions: boolean(runtime.inheritExtensions, "runtime.inheritExtensions"),
177
+ openAIIdentity: boolean(runtime.openAIIdentity, "runtime.openAIIdentity"),
165
178
  maxOutputBytes: boundedInteger(
166
179
  runtime.maxOutputBytes,
167
180
  "runtime.maxOutputBytes",
package/src/index.ts CHANGED
@@ -78,11 +78,11 @@ function registerDelegationTool(
78
78
  "This foreground-only tool waits for the child and returns its final answer. " +
79
79
  "Independent sibling calls may still execute in parallel."
80
80
  : defaultBackground
81
- ? "Delegate a complete standalone task to a fresh child with its own Pi session and context. " +
82
- "Background mode is continuable and returns a durable child id; use send_message for later FIFO turns. " +
83
- "Start independent children together in one assistant message."
84
- : "Delegate a complete standalone task to a fresh child with its own Pi session and context. " +
85
- "This tool waits for the result by default; set run_in_background to true to return a durable child id.";
81
+ ? "Delegate a complete standalone task to a fresh child with its own Pi session and context. " +
82
+ "Background mode is continuable and returns a durable agent id; use send_message for later FIFO turns. " +
83
+ "Start independent children together in one assistant message."
84
+ : "Delegate a complete standalone task to a fresh child with its own Pi session and context. " +
85
+ "This tool waits for the result by default; set run_in_background to true to return a durable agent id.";
86
86
  const promptGuidelines = !enableRunInBackground
87
87
  ? [
88
88
  "Use subagent for focused independent work and give it a complete standalone prompt.",
@@ -214,7 +214,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
214
214
  name: "send_message",
215
215
  label: "Send Message",
216
216
  description:
217
- "Queue a message as a direct continuable child's next FIFO turn. If it is inactive, its persisted session is cold-resumed. " +
217
+ "Queue a message as a direct continuable child's next FIFO turn. If it is inactive, its persisted session is cold-resumed by agent id. " +
218
218
  "This call returns acceptance only, never the child's answer.",
219
219
  promptSnippet: "Send a later FIFO turn to a direct continuable subagent",
220
220
  executionMode: "parallel",
@@ -230,7 +230,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
230
230
  text: `message queued as the next turn for subagent ${params.subagent_id}`,
231
231
  },
232
232
  ],
233
- details: { kind: "control", action: "send", id: params.subagent_id } satisfies ControlDetails,
233
+ details: { kind: "control", action: "send", agentId: params.subagent_id } satisfies ControlDetails,
234
234
  };
235
235
  },
236
236
  });
@@ -250,7 +250,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
250
250
  await coordinator.interrupt(parent, params.agent_id);
251
251
  return {
252
252
  content: [{ type: "text", text: `interrupt requested for agent ${params.agent_id}` }],
253
- details: { kind: "control", action: "interrupt", id: params.agent_id } satisfies ControlDetails,
253
+ details: { kind: "control", action: "interrupt", agentId: params.agent_id } satisfies ControlDetails,
254
254
  };
255
255
  },
256
256
  });
@@ -307,6 +307,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
307
307
  : "foreground-first";
308
308
  const sections = [
309
309
  `Mode: ${schedulingMode}`,
310
+ `OpenAI identity inline: ${sessionSettings.openAIIdentity ? "enabled" : "disabled"}`,
310
311
  sessionSettings.syncBundledAgents
311
312
  ? `Bundled presets: synchronized to ${agentSync?.userAgentsDir ?? coordinator.getUserAgentsDir()}`
312
313
  : "Bundled presets: package defaults (no filesystem sync)",
package/src/providers.ts CHANGED
@@ -9,6 +9,8 @@ export interface SessionView {
9
9
  getSessionDir(): string;
10
10
  getSessionFile(): string | undefined;
11
11
  getSessionId(): string;
12
+ getEntries(): SessionEntry[];
13
+ appendCustomEntry(customType: string, data?: unknown): string;
12
14
  }
13
15
 
14
16
  export interface ProviderParent {
package/src/render.ts CHANGED
@@ -46,7 +46,7 @@ export function renderDelegationResult(
46
46
  const header = `${icon} ${theme.fg("toolTitle", theme.bold(details.agent))} ${theme.fg(
47
47
  "muted",
48
48
  `[${details.provider}/${details.mode}]`,
49
- )} ${theme.fg("dim", details.id)}`;
49
+ )} ${theme.fg("dim", details.agentId)}`;
50
50
 
51
51
  if (!options.expanded) {
52
52
  const lines = [header, theme.fg("muted", details.label)];
@@ -102,7 +102,7 @@ export function renderParentMessage(
102
102
  theme.fg("accent", icon) +
103
103
  " " +
104
104
  theme.fg("toolTitle", theme.bold(`subagent ${kind}`)) +
105
- theme.fg("muted", ` ${details?.childId ?? "unknown"}${label}`),
105
+ theme.fg("muted", ` ${details?.childAgentId ?? "unknown"}${label}`),
106
106
  outputPad,
107
107
  0,
108
108
  );
package/src/schemas.ts CHANGED
@@ -87,7 +87,10 @@ export function forkDelegationParameters(agentNames?: readonly string[]) {
87
87
 
88
88
  export const SendMessageParameters = Type.Object(
89
89
  {
90
- subagent_id: Type.String({ description: "Durable id of a direct continuable child", minLength: 1 }),
90
+ subagent_id: Type.String({
91
+ description: "Durable agent id of a direct continuable child",
92
+ minLength: 1,
93
+ }),
91
94
  message: Type.String({
92
95
  description: "Message to enqueue as the child's next FIFO turn",
93
96
  minLength: 1,
@@ -99,7 +102,7 @@ export const SendMessageParameters = Type.Object(
99
102
  export const InterruptParameters = Type.Object(
100
103
  {
101
104
  agent_id: Type.String({
102
- description: "Id of a live child or deeper descendant whose current turn should stop",
105
+ description: "Agent id of a live child or deeper descendant whose current turn should stop",
103
106
  minLength: 1,
104
107
  }),
105
108
  },
package/src/types.ts CHANGED
@@ -16,6 +16,7 @@ export interface SubagentSettings {
16
16
  defaultBackground: boolean;
17
17
  reportDelivery: ReportDelivery;
18
18
  inheritExtensions: boolean;
19
+ openAIIdentity: boolean;
19
20
  maxOutputBytes: number;
20
21
  }
21
22
 
@@ -53,15 +54,22 @@ export interface SubagentRuntimeSnapshot {
53
54
  defaultBackground: boolean;
54
55
  reportDelivery: ReportDelivery;
55
56
  inheritExtensions: boolean;
57
+ openAIIdentity: boolean;
56
58
  maxOutputBytes: number;
57
59
  }
58
60
 
59
61
  export interface SubagentDescriptor {
60
- version: 1;
62
+ version: 2;
61
63
  mode: SubagentMode;
62
64
  provider: SubagentProviderName;
63
65
  label: string;
64
- parentSessionId: string;
66
+ /** Durable pi-subagent control identity, independent of provider wire ids. */
67
+ agentId: string;
68
+ /** Durable control identity of the delegating pi-subagent. */
69
+ parentAgentId: string;
70
+ /** Pi session lookup key of the delegating agent; never used as a wire id. */
71
+ parentPiSessionId: string;
72
+ /** Path of the parent's session file at delegation time (attribute, not identity). */
65
73
  parentSessionFile?: string;
66
74
  depth: number;
67
75
  cwd: string;
@@ -77,7 +85,8 @@ export interface SubagentUsage extends Usage {
77
85
  }
78
86
 
79
87
  export interface SubagentRunResult {
80
- id: string;
88
+ agentId: string;
89
+ piSessionId?: string;
81
90
  sessionFile?: string;
82
91
  output: string;
83
92
  stopReason: SubagentStopReason;
@@ -92,7 +101,8 @@ export interface TraceItem {
92
101
 
93
102
  export interface DelegationDetails {
94
103
  kind: "delegation";
95
- id: string;
104
+ agentId: string;
105
+ piSessionId?: string;
96
106
  provider: SubagentProviderName;
97
107
  mode: SubagentMode;
98
108
  agent: string;
@@ -109,13 +119,13 @@ export interface DelegationDetails {
109
119
  export interface ControlDetails {
110
120
  kind: "control";
111
121
  action: "send" | "interrupt" | "list" | "report";
112
- id?: string;
122
+ agentId?: string;
113
123
  }
114
124
 
115
125
  export interface CatalogChild {
116
126
  kind: "child";
117
- id: string;
118
- parentId: string;
127
+ agentId: string;
128
+ parentAgentId: string;
119
129
  depth: number;
120
130
  descriptor: SubagentDescriptor;
121
131
  sessionFile?: string;
@@ -124,7 +134,7 @@ export interface CatalogChild {
124
134
 
125
135
  export interface CatalogDiagnostic {
126
136
  kind: "diagnostic";
127
- id: string;
137
+ piSessionId: string;
128
138
  reason: "corrupt" | "unavailable";
129
139
  sessionFile?: string;
130
140
  parentSessionFile?: string;
@@ -135,7 +145,7 @@ export type CatalogEntry = CatalogChild | CatalogDiagnostic;
135
145
 
136
146
  export interface ParentMessageDetails {
137
147
  kind: "report" | "settled";
138
- childId: string;
148
+ childAgentId: string;
139
149
  label: string;
140
150
  stopReason?: SubagentStopReason;
141
151
  truncated?: boolean;