@dogpile/sdk 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CHANGELOG.md +201 -0
  2. package/README.md +1 -0
  3. package/dist/browser/index.js +2328 -237
  4. package/dist/browser/index.js.map +1 -1
  5. package/dist/index.d.ts +3 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/providers/openai-compatible.d.ts +11 -0
  10. package/dist/providers/openai-compatible.d.ts.map +1 -1
  11. package/dist/providers/openai-compatible.js +88 -2
  12. package/dist/providers/openai-compatible.js.map +1 -1
  13. package/dist/runtime/audit.d.ts +42 -0
  14. package/dist/runtime/audit.d.ts.map +1 -0
  15. package/dist/runtime/audit.js +73 -0
  16. package/dist/runtime/audit.js.map +1 -0
  17. package/dist/runtime/broadcast.d.ts.map +1 -1
  18. package/dist/runtime/broadcast.js +39 -36
  19. package/dist/runtime/broadcast.js.map +1 -1
  20. package/dist/runtime/cancellation.d.ts +26 -0
  21. package/dist/runtime/cancellation.d.ts.map +1 -1
  22. package/dist/runtime/cancellation.js +38 -1
  23. package/dist/runtime/cancellation.js.map +1 -1
  24. package/dist/runtime/coordinator.d.ts +79 -1
  25. package/dist/runtime/coordinator.d.ts.map +1 -1
  26. package/dist/runtime/coordinator.js +979 -61
  27. package/dist/runtime/coordinator.js.map +1 -1
  28. package/dist/runtime/decisions.d.ts +25 -3
  29. package/dist/runtime/decisions.d.ts.map +1 -1
  30. package/dist/runtime/decisions.js +241 -3
  31. package/dist/runtime/decisions.js.map +1 -1
  32. package/dist/runtime/defaults.d.ts +37 -1
  33. package/dist/runtime/defaults.d.ts.map +1 -1
  34. package/dist/runtime/defaults.js +359 -4
  35. package/dist/runtime/defaults.js.map +1 -1
  36. package/dist/runtime/engine.d.ts +17 -4
  37. package/dist/runtime/engine.d.ts.map +1 -1
  38. package/dist/runtime/engine.js +770 -35
  39. package/dist/runtime/engine.js.map +1 -1
  40. package/dist/runtime/health.d.ts +51 -0
  41. package/dist/runtime/health.d.ts.map +1 -0
  42. package/dist/runtime/health.js +85 -0
  43. package/dist/runtime/health.js.map +1 -0
  44. package/dist/runtime/introspection.d.ts +96 -0
  45. package/dist/runtime/introspection.d.ts.map +1 -0
  46. package/dist/runtime/introspection.js +31 -0
  47. package/dist/runtime/introspection.js.map +1 -0
  48. package/dist/runtime/metrics.d.ts +44 -0
  49. package/dist/runtime/metrics.d.ts.map +1 -0
  50. package/dist/runtime/metrics.js +12 -0
  51. package/dist/runtime/metrics.js.map +1 -0
  52. package/dist/runtime/model.d.ts.map +1 -1
  53. package/dist/runtime/model.js +34 -7
  54. package/dist/runtime/model.js.map +1 -1
  55. package/dist/runtime/provenance.d.ts +25 -0
  56. package/dist/runtime/provenance.d.ts.map +1 -0
  57. package/dist/runtime/provenance.js +13 -0
  58. package/dist/runtime/provenance.js.map +1 -0
  59. package/dist/runtime/sequential.d.ts.map +1 -1
  60. package/dist/runtime/sequential.js +47 -37
  61. package/dist/runtime/sequential.js.map +1 -1
  62. package/dist/runtime/shared.d.ts.map +1 -1
  63. package/dist/runtime/shared.js +39 -36
  64. package/dist/runtime/shared.js.map +1 -1
  65. package/dist/runtime/tracing.d.ts +31 -0
  66. package/dist/runtime/tracing.d.ts.map +1 -0
  67. package/dist/runtime/tracing.js +18 -0
  68. package/dist/runtime/tracing.js.map +1 -0
  69. package/dist/runtime/validation.d.ts +10 -0
  70. package/dist/runtime/validation.d.ts.map +1 -1
  71. package/dist/runtime/validation.js +73 -0
  72. package/dist/runtime/validation.js.map +1 -1
  73. package/dist/types/events.d.ts +339 -12
  74. package/dist/types/events.d.ts.map +1 -1
  75. package/dist/types/replay.d.ts +7 -1
  76. package/dist/types/replay.d.ts.map +1 -1
  77. package/dist/types.d.ts +255 -6
  78. package/dist/types.d.ts.map +1 -1
  79. package/dist/types.js.map +1 -1
  80. package/package.json +39 -1
  81. package/src/index.ts +15 -0
  82. package/src/providers/openai-compatible.ts +83 -3
  83. package/src/runtime/audit.ts +121 -0
  84. package/src/runtime/broadcast.ts +40 -37
  85. package/src/runtime/cancellation.ts +59 -1
  86. package/src/runtime/coordinator.ts +1221 -61
  87. package/src/runtime/decisions.ts +307 -4
  88. package/src/runtime/defaults.ts +389 -4
  89. package/src/runtime/engine.ts +1004 -35
  90. package/src/runtime/health.ts +136 -0
  91. package/src/runtime/introspection.ts +122 -0
  92. package/src/runtime/metrics.ts +45 -0
  93. package/src/runtime/model.ts +38 -6
  94. package/src/runtime/provenance.ts +43 -0
  95. package/src/runtime/sequential.ts +49 -38
  96. package/src/runtime/shared.ts +40 -37
  97. package/src/runtime/tracing.ts +35 -0
  98. package/src/runtime/validation.ts +81 -0
  99. package/src/types/events.ts +369 -12
  100. package/src/types/replay.ts +14 -1
  101. package/src/types.ts +279 -4
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Duck-typed OTEL tracing bridge surface (Phase 9 / OTEL-01..OTEL-03).
3
+ *
4
+ * The SDK does not import `@opentelemetry/*` anywhere in `src/runtime/`,
5
+ * `src/browser/`, or `src/providers/`. Callers wire a real OTEL Tracer to
6
+ * the SDK by providing an object that structurally matches `DogpileTracer`.
7
+ * See `docs/developer-usage.md` for the WeakMap-based bridge pattern.
8
+ *
9
+ * `replay()` and `replayStream()` ignore any tracer on engine options —
10
+ * historical timestamps would confuse OTEL backends.
11
+ */
12
+
13
+ export interface DogpileSpan {
14
+ end(): void;
15
+ setAttribute(key: string, value: string | number | boolean): void;
16
+ setStatus(code: "ok" | "error", message?: string): void;
17
+ }
18
+
19
+ export interface DogpileSpanOptions {
20
+ readonly parent?: DogpileSpan;
21
+ readonly attributes?: Readonly<Record<string, string | number | boolean>>;
22
+ }
23
+
24
+ export interface DogpileTracer {
25
+ startSpan(name: string, options?: DogpileSpanOptions): DogpileSpan;
26
+ }
27
+
28
+ export const DOGPILE_SPAN_NAMES = {
29
+ RUN: "dogpile.run",
30
+ SUB_RUN: "dogpile.sub-run",
31
+ AGENT_TURN: "dogpile.agent-turn",
32
+ MODEL_CALL: "dogpile.model-call"
33
+ } as const;
34
+
35
+ export type DogpileSpanName = (typeof DOGPILE_SPAN_NAMES)[keyof typeof DOGPILE_SPAN_NAMES];
@@ -17,6 +17,7 @@ import type {
17
17
 
18
18
  const protocolNames = ["coordinator", "sequential", "broadcast", "shared"] as const;
19
19
  const budgetTiers = ["fast", "balanced", "quality"] as const;
20
+ const onChildFailureModes = ["continue", "abort"] as const;
20
21
 
21
22
  type ValidationRule =
22
23
  | "required"
@@ -24,6 +25,7 @@ type ValidationRule =
24
25
  | "finite-number"
25
26
  | "non-negative-number"
26
27
  | "positive-integer"
28
+ | "positive-finite-number"
27
29
  | "non-negative-integer"
28
30
  | "range"
29
31
  | "enum"
@@ -69,12 +71,29 @@ export function validateDogpileOptions(options: DogpileOptions): void {
69
71
  validateOptionalFunction(options.evaluate, "evaluate");
70
72
  validateOptionalSeed(options.seed, "seed");
71
73
  validateOptionalAbortSignal(options.signal, "signal");
74
+ validateOptionalNonNegativeInteger(options.maxDepth, "maxDepth");
75
+ validateOptionalPositiveInteger(options.maxConcurrentChildren, "maxConcurrentChildren");
76
+ validateOptionalPositiveFiniteNumber(options.defaultSubRunTimeoutMs, "defaultSubRunTimeoutMs");
77
+ validateOptionalOnChildFailure(options.onChildFailure, "onChildFailure");
72
78
  }
73
79
 
74
80
  export function validateMissionIntent(intent: unknown, path = "intent"): void {
75
81
  validateNonEmptyString(intent, path, "intent is required.");
76
82
  }
77
83
 
84
+ /**
85
+ * Validate per-call run/stream options (`Engine.run(intent, options)` / `Engine.stream(...)`).
86
+ */
87
+ export function validateRunCallOptions(options: unknown, path = "options"): void {
88
+ if (options === undefined) {
89
+ return;
90
+ }
91
+ const record = requireRecord(options, path);
92
+ validateOptionalNonNegativeInteger(record.maxDepth, `${path}.maxDepth`);
93
+ validateOptionalPositiveInteger(record.maxConcurrentChildren, `${path}.maxConcurrentChildren`);
94
+ validateOptionalOnChildFailure(record.onChildFailure, `${path}.onChildFailure`);
95
+ }
96
+
78
97
  /**
79
98
  * Validate low-level engine configuration before normalizing reusable controls.
80
99
  */
@@ -92,6 +111,10 @@ export function validateEngineOptions(options: EngineOptions): void {
92
111
  validateOptionalFunction(options.evaluate, "evaluate");
93
112
  validateOptionalSeed(options.seed, "seed");
94
113
  validateOptionalAbortSignal(options.signal, "signal");
114
+ validateOptionalNonNegativeInteger(options.maxDepth, "maxDepth");
115
+ validateOptionalPositiveInteger(options.maxConcurrentChildren, "maxConcurrentChildren");
116
+ validateOptionalPositiveFiniteNumber(options.defaultSubRunTimeoutMs, "defaultSubRunTimeoutMs");
117
+ validateOptionalOnChildFailure(options.onChildFailure, "onChildFailure");
95
118
  }
96
119
 
97
120
  /**
@@ -195,6 +218,28 @@ function validateBudgetTier(value: BudgetTier, path: string): void {
195
218
  }
196
219
  }
197
220
 
221
+ function validateOptionalOnChildFailure(value: unknown, path: string): void {
222
+ if (value === undefined) {
223
+ return;
224
+ }
225
+ if (value === "continue" || value === "abort") {
226
+ return;
227
+ }
228
+ throw new DogpileError({
229
+ code: "invalid-configuration",
230
+ message: `Invalid onChildFailure: expected "continue" or "abort", got ${JSON.stringify(value)}`,
231
+ retryable: false,
232
+ detail: {
233
+ kind: "configuration-validation",
234
+ path,
235
+ rule: "enum",
236
+ expected: onChildFailureModes.join(" | "),
237
+ received: describeValue(value),
238
+ reason: "invalid-on-child-failure"
239
+ }
240
+ });
241
+ }
242
+
198
243
  /**
199
244
  * Validate configured model provider definitions at registration boundaries.
200
245
  */
@@ -205,6 +250,27 @@ export function validateModelProviderRegistration(value: unknown, path = "model"
205
250
  validateOptionalFunction(record.stream, `${path}.stream`);
206
251
  }
207
252
 
253
+ /**
254
+ * Engine-time defense-in-depth check that a provider's optional
255
+ * `metadata.locality` is one of the two valid values when present (Phase 3 D-03).
256
+ * Catches user-implemented providers that bypass TypeScript checks.
257
+ */
258
+ export function validateProviderLocality(
259
+ provider: ConfiguredModelProvider,
260
+ pathPrefix: string = "model"
261
+ ): void {
262
+ const loc = provider.metadata?.locality;
263
+ if (loc !== undefined && loc !== "local" && loc !== "remote") {
264
+ invalidConfiguration({
265
+ path: `${pathPrefix}.metadata.locality`,
266
+ rule: "enum",
267
+ message: `${pathPrefix}.metadata.locality must be "local" or "remote" when provided.`,
268
+ expected: "\"local\" | \"remote\"",
269
+ actual: loc
270
+ });
271
+ }
272
+ }
273
+
208
274
  function validateVercelAILanguageModel(value: unknown, path: string): void {
209
275
  const record = requireRecord(value, path);
210
276
 
@@ -704,6 +770,21 @@ function validateOptionalNonNegativeInteger(value: unknown, path: string): void
704
770
  }
705
771
  }
706
772
 
773
+ function validateOptionalPositiveFiniteNumber(value: unknown, path: string): void {
774
+ if (value === undefined) {
775
+ return;
776
+ }
777
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
778
+ invalidConfiguration({
779
+ path,
780
+ rule: "positive-finite-number",
781
+ message: "value must be a positive finite number.",
782
+ expected: "finite number > 0",
783
+ actual: value
784
+ });
785
+ }
786
+ }
787
+
707
788
  function validateOptionalNonNegativeNumber(value: unknown, path: string): void {
708
789
  if (value === undefined) {
709
790
  return;
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ BudgetCaps,
2
3
  BudgetStopReason,
3
4
  CostSummary,
4
5
  JsonObject,
@@ -9,11 +10,14 @@ import type {
9
10
  ModelResponse,
10
11
  NormalizedQualityScore,
11
12
  Protocol,
13
+ ProtocolName,
12
14
  RunEvaluation,
15
+ RunResult,
13
16
  RuntimeToolIdentity,
14
17
  RuntimeToolPermission,
15
18
  RuntimeToolResult,
16
- TerminationStopRecord
19
+ TerminationStopRecord,
20
+ Trace
17
21
  } from "../types.js";
18
22
 
19
23
 
@@ -39,6 +43,8 @@ export interface RoleAssignmentEvent {
39
43
  readonly type: "role-assignment";
40
44
  /** Stable run id shared by all events in one workflow. */
41
45
  readonly runId: string;
46
+ /** Root-first ancestry chain when bubbled through a parent stream. */
47
+ readonly parentRunIds?: readonly string[];
42
48
  /** ISO-8601 event timestamp. */
43
49
  readonly at: string;
44
50
  /** Agent receiving the role assignment. */
@@ -63,12 +69,16 @@ export interface ModelRequestEvent {
63
69
  readonly type: "model-request";
64
70
  /** Stable run id shared by all events in one workflow. */
65
71
  readonly runId: string;
66
- /** ISO-8601 event timestamp. */
67
- readonly at: string;
72
+ /** Root-first ancestry chain when bubbled through a parent stream. */
73
+ readonly parentRunIds?: readonly string[];
74
+ /** ISO-8601 timestamp immediately before the provider call began. */
75
+ readonly startedAt: string;
68
76
  /** Stable provider call id within the run. */
69
77
  readonly callId: string;
70
78
  /** Configured model provider id receiving the request. */
71
79
  readonly providerId: string;
80
+ /** Resolved model identifier; falls back to provider id when adapter does not set modelId. */
81
+ readonly modelId: string;
72
82
  /** Agent requesting the model call. */
73
83
  readonly agentId: string;
74
84
  /** Agent role for the active model call. */
@@ -91,12 +101,18 @@ export interface ModelResponseEvent {
91
101
  readonly type: "model-response";
92
102
  /** Stable run id shared by all events in one workflow. */
93
103
  readonly runId: string;
94
- /** ISO-8601 event timestamp. */
95
- readonly at: string;
104
+ /** Root-first ancestry chain when bubbled through a parent stream. */
105
+ readonly parentRunIds?: readonly string[];
106
+ /** ISO-8601 timestamp when the provider call started (same value as the paired ModelRequestEvent). */
107
+ readonly startedAt: string;
108
+ /** ISO-8601 timestamp after the provider call completed. Duration = completedAt minus startedAt. */
109
+ readonly completedAt: string;
96
110
  /** Stable provider call id within the run. */
97
111
  readonly callId: string;
98
112
  /** Configured model provider id that produced the response. */
99
113
  readonly providerId: string;
114
+ /** Resolved model identifier; falls back to provider id when adapter does not set modelId. */
115
+ readonly modelId: string;
100
116
  /** Agent that requested the model call. */
101
117
  readonly agentId: string;
102
118
  /** Agent role for the completed model call. */
@@ -131,6 +147,8 @@ export interface ModelOutputChunkEvent {
131
147
  readonly type: "model-output-chunk";
132
148
  /** Stable run id shared by all events in one workflow. */
133
149
  readonly runId: string;
150
+ /** Root-first ancestry chain when bubbled through a parent stream. */
151
+ readonly parentRunIds?: readonly string[];
134
152
  /** ISO-8601 event timestamp. */
135
153
  readonly at: string;
136
154
  /** Agent currently producing output. */
@@ -160,6 +178,8 @@ export interface ToolCallEvent {
160
178
  readonly type: "tool-call";
161
179
  /** Stable run id shared by all events in one workflow. */
162
180
  readonly runId: string;
181
+ /** Root-first ancestry chain when bubbled through a parent stream. */
182
+ readonly parentRunIds?: readonly string[];
163
183
  /** ISO-8601 event timestamp. */
164
184
  readonly at: string;
165
185
  /** Stable tool call id within the run. */
@@ -187,6 +207,8 @@ export interface ToolResultEvent {
187
207
  readonly type: "tool-result";
188
208
  /** Stable run id shared by all events in one workflow. */
189
209
  readonly runId: string;
210
+ /** Root-first ancestry chain when bubbled through a parent stream. */
211
+ readonly parentRunIds?: readonly string[];
190
212
  /** ISO-8601 event timestamp. */
191
213
  readonly at: string;
192
214
  /** Stable tool call id within the run. */
@@ -211,7 +233,9 @@ export interface ToolResultEvent {
211
233
  * metadata so reproduction harnesses can distinguish contribution from
212
234
  * voluntary abstention without reparsing raw text.
213
235
  */
214
- export interface AgentDecision {
236
+ export interface ParticipateAgentDecision {
237
+ /** Discriminant marking this as a participate-style decision. */
238
+ readonly type: "participate";
215
239
  /** Task-specific role selected by the agent for this turn. */
216
240
  readonly selectedRole: string;
217
241
  /** Whether the agent contributed or voluntarily abstained. */
@@ -222,6 +246,46 @@ export interface AgentDecision {
222
246
  readonly contribution: string;
223
247
  }
224
248
 
249
+ /**
250
+ * Decision emitted by a coordinator agent that delegates a sub-mission to a
251
+ * coordination protocol rather than contributing directly. The runtime
252
+ * dispatches a child run when this decision is returned.
253
+ */
254
+ export interface DelegateAgentDecision {
255
+ /** Discriminant marking this as a delegate-style decision. */
256
+ readonly type: "delegate";
257
+ /** Coordination protocol the child sub-run will execute. */
258
+ readonly protocol: ProtocolName;
259
+ /** Mission text passed to the child sub-run. */
260
+ readonly intent: string;
261
+ /**
262
+ * Optional model provider id assertion. When set, the runtime requires the
263
+ * value to match the parent's `ConfiguredModelProvider.id` (D-11) — child
264
+ * runs always inherit the parent provider instance verbatim.
265
+ */
266
+ readonly model?: string;
267
+ /** Optional per-decision budget caps applied to the child run. */
268
+ readonly budget?: BudgetCaps;
269
+ /**
270
+ * Optional per-decision child concurrency ceiling. This can only lower the
271
+ * engine/run effective max for the current coordinator fan-out turn (D-05).
272
+ */
273
+ readonly maxConcurrentChildren?: number;
274
+ }
275
+
276
+ /**
277
+ * Discriminated union of structured agent decisions parsed from model output.
278
+ *
279
+ * - `participate`: paper-style turn contribution; carries the four labeled
280
+ * fields (`selectedRole`, `participation`, `rationale`, `contribution`).
281
+ * - `delegate`: coordinator-only delegation to a child sub-run. The runtime
282
+ * dispatches a sub-mission when this branch is returned.
283
+ *
284
+ * Consumers MUST narrow on `decision.type === "participate"` before reading
285
+ * paper-style fields.
286
+ */
287
+ export type AgentDecision = ParticipateAgentDecision | DelegateAgentDecision;
288
+
225
289
  /**
226
290
  * Agent participation state for a paper-style turn decision.
227
291
  */
@@ -256,6 +320,8 @@ export interface TurnEvent {
256
320
  readonly type: "agent-turn";
257
321
  /** Stable run id shared by all events in one workflow. */
258
322
  readonly runId: string;
323
+ /** Root-first ancestry chain when bubbled through a parent stream. */
324
+ readonly parentRunIds?: readonly string[];
259
325
  /** ISO-8601 event timestamp. */
260
326
  readonly at: string;
261
327
  /** Agent that produced this turn. */
@@ -267,7 +333,7 @@ export interface TurnEvent {
267
333
  /** Model output produced by the agent. */
268
334
  readonly output: string;
269
335
  /** Optional structured role/participation decision parsed from model output. */
270
- readonly decision?: AgentDecision;
336
+ readonly decision?: AgentDecision | readonly DelegateAgentDecision[];
271
337
  /** Cumulative cost after this turn. */
272
338
  readonly cost: CostSummary;
273
339
  }
@@ -296,7 +362,7 @@ export interface BroadcastContribution {
296
362
  /** Independent model output produced for the shared mission. */
297
363
  readonly output: string;
298
364
  /** Optional structured role/participation decision parsed from model output. */
299
- readonly decision?: AgentDecision;
365
+ readonly decision?: AgentDecision | readonly DelegateAgentDecision[];
300
366
  }
301
367
 
302
368
  /**
@@ -323,6 +389,8 @@ export interface BroadcastEvent {
323
389
  readonly type: "broadcast";
324
390
  /** Stable run id shared by all events in one workflow. */
325
391
  readonly runId: string;
392
+ /** Root-first ancestry chain when bubbled through a parent stream. */
393
+ readonly parentRunIds?: readonly string[];
326
394
  /** ISO-8601 event timestamp. */
327
395
  readonly at: string;
328
396
  /** One-based broadcast round number. */
@@ -346,6 +414,8 @@ export interface BudgetStopEvent {
346
414
  readonly type: "budget-stop";
347
415
  /** Stable run id shared by all events in one workflow. */
348
416
  readonly runId: string;
417
+ /** Root-first ancestry chain when bubbled through a parent stream. */
418
+ readonly parentRunIds?: readonly string[];
349
419
  /** ISO-8601 event timestamp. */
350
420
  readonly at: string;
351
421
  /** Normalized machine-readable budget stop reason. */
@@ -418,6 +488,259 @@ export interface FinalEvent {
418
488
  readonly termination?: TerminationStopRecord;
419
489
  }
420
490
 
491
+ /**
492
+ * Event emitted when the coordinator dispatches a delegated sub-run.
493
+ *
494
+ * @remarks
495
+ * Recorded immediately before the child run starts executing. Carries the
496
+ * child's run id, the parent decision id that triggered the dispatch, and the
497
+ * resolved protocol/intent/depth. The `recursive` flag marks the diagnostic
498
+ * case where a coordinator delegates to another coordinator (D-16).
499
+ *
500
+ * The event's `runId` is the PARENT run id, matching the existing trace
501
+ * convention; `parentRunId` duplicates it for explicit cross-reference.
502
+ */
503
+ export interface SubRunStartedEvent {
504
+ /** Discriminant for event rendering and exhaustive switches. */
505
+ readonly type: "sub-run-started";
506
+ /** Parent run id; matches the surrounding trace runId. */
507
+ readonly runId: string;
508
+ /** Root-first ancestry chain when bubbled through a parent stream. */
509
+ readonly parentRunIds?: readonly string[];
510
+ /** ISO-8601 event timestamp. */
511
+ readonly at: string;
512
+ /** Child run id assigned to the dispatched sub-run. */
513
+ readonly childRunId: string;
514
+ /** Parent run id (duplicates `runId` for explicit cross-reference). */
515
+ readonly parentRunId: string;
516
+ /** Replay decision id of the parent decision that triggered this sub-run. */
517
+ readonly parentDecisionId: string;
518
+ /**
519
+ * 0-indexed position of this delegate within the fan-out array of its
520
+ * originating coordinator plan-turn (Phase 3 D-10). Single-delegate turns
521
+ * use `0` for backward compatibility. Together with `parentDecisionId`, this
522
+ * uniquely identifies the delegate within a fan-out.
523
+ */
524
+ readonly parentDecisionArrayIndex: number;
525
+ /** Coordination protocol the child run will execute. */
526
+ readonly protocol: ProtocolName;
527
+ /** Mission intent passed to the child run. */
528
+ readonly intent: string;
529
+ /** Recursion depth of the child run (1 for first-level sub-run). */
530
+ readonly depth: number;
531
+ /**
532
+ * Diagnostic flag set when a coordinator delegates to another coordinator
533
+ * (parent protocol === "coordinator" and child protocol === "coordinator").
534
+ */
535
+ readonly recursive?: boolean;
536
+ }
537
+
538
+ /**
539
+ * Event emitted when a delegated sub-run completes successfully.
540
+ *
541
+ * @remarks
542
+ * Carries the full {@link RunResult} as `subResult`, including the embedded
543
+ * child {@link Trace}. Replay walks the parent event sequence and recurses on
544
+ * `subResult.trace` to rehydrate sub-run accounting without re-invoking the
545
+ * provider (D-08).
546
+ */
547
+ export interface SubRunCompletedEvent {
548
+ /** Discriminant for event rendering and exhaustive switches. */
549
+ readonly type: "sub-run-completed";
550
+ /** Parent run id; matches the surrounding trace runId. */
551
+ readonly runId: string;
552
+ /** Root-first ancestry chain when bubbled through a parent stream. */
553
+ readonly parentRunIds?: readonly string[];
554
+ /** ISO-8601 event timestamp. */
555
+ readonly at: string;
556
+ /** Child run id that produced this result. */
557
+ readonly childRunId: string;
558
+ /** Parent run id (duplicates `runId` for explicit cross-reference). */
559
+ readonly parentRunId: string;
560
+ /** Replay decision id of the parent decision that triggered the sub-run. */
561
+ readonly parentDecisionId: string;
562
+ /**
563
+ * 0-indexed position of this delegate within the fan-out array of its
564
+ * originating coordinator plan-turn (Phase 3 D-10). Single-delegate turns
565
+ * use `0` for backward compatibility. Together with `parentDecisionId`, this
566
+ * uniquely identifies the delegate within a fan-out.
567
+ */
568
+ readonly parentDecisionArrayIndex: number;
569
+ /** Full child {@link RunResult}, including the embedded child {@link Trace}. */
570
+ readonly subResult: RunResult;
571
+ }
572
+
573
+ /**
574
+ * Event emitted when a delegated sub-run fails before completion.
575
+ *
576
+ * @remarks
577
+ * Captures a structured `error` plus the partial {@link Trace} accumulated
578
+ * before failure. The same `Trace` shape used by completed runs is preserved
579
+ * — consumers can replay or inspect the partial child events without bespoke
580
+ * deserialization logic.
581
+ */
582
+ export interface SubRunFailedEvent {
583
+ /** Discriminant for event rendering and exhaustive switches. */
584
+ readonly type: "sub-run-failed";
585
+ /** Parent run id; matches the surrounding trace runId. */
586
+ readonly runId: string;
587
+ /** Root-first ancestry chain when bubbled through a parent stream. */
588
+ readonly parentRunIds?: readonly string[];
589
+ /** ISO-8601 event timestamp. */
590
+ readonly at: string;
591
+ /** Child run id that failed. */
592
+ readonly childRunId: string;
593
+ /** Parent run id (duplicates `runId` for explicit cross-reference). */
594
+ readonly parentRunId: string;
595
+ /** Replay decision id of the parent decision that triggered the sub-run. */
596
+ readonly parentDecisionId: string;
597
+ /**
598
+ * 0-indexed position of this delegate within the fan-out array of its
599
+ * originating coordinator plan-turn (Phase 3 D-10). Single-delegate turns
600
+ * use `0` for backward compatibility. Together with `parentDecisionId`, this
601
+ * uniquely identifies the delegate within a fan-out.
602
+ */
603
+ readonly parentDecisionArrayIndex: number;
604
+ /** Structured failure description. */
605
+ readonly error: {
606
+ /** Stable error code (matches DogpileError code shape). */
607
+ readonly code: string;
608
+ /** Human-readable failure description. */
609
+ readonly message: string;
610
+ /** Provider id when the failure originated in a model call. */
611
+ readonly providerId?: string;
612
+ /** Optional structured detail (e.g., the failed delegate decision payload). */
613
+ readonly detail?: JsonObject;
614
+ };
615
+ /** Partial child {@link Trace} accumulated up to the failure point. */
616
+ readonly partialTrace: Trace;
617
+ /**
618
+ * Cost from provider calls completed before the child threw (BUDGET-03 / D-02).
619
+ *
620
+ * Equals `lastCostBearingEventCost(partialTrace.events) ?? emptyCost()`. The
621
+ * parent rolls this into its own `accounting.cost` so failed children
622
+ * contribute their real wallet spend.
623
+ */
624
+ readonly partialCost: CostSummary;
625
+ }
626
+
627
+ /**
628
+ * Event emitted when the parent's `signal` aborts AFTER a sub-run has already
629
+ * completed successfully but BEFORE the parent advances to its next coordinator
630
+ * turn (BUDGET-01 / D-10).
631
+ *
632
+ * @remarks
633
+ * Provides replay/streaming provenance for "parent gave up after a successful
634
+ * child finished." The marker is observable on `Dogpile.stream()` subscribers
635
+ * before the run errors with `code: "aborted"`. Non-streaming `run()` rejects
636
+ * with the abort error and does NOT expose the marker — `engine.ts` does not
637
+ * attach the parent events array to the rejected error (verified at
638
+ * `engine.ts:230-239`). Streaming-subscriber observability is the contract.
639
+ */
640
+ export interface SubRunParentAbortedEvent {
641
+ /** Discriminant for event rendering and exhaustive switches. */
642
+ readonly type: "sub-run-parent-aborted";
643
+ /** Parent run id; matches the surrounding trace runId. */
644
+ readonly runId: string;
645
+ /** Root-first ancestry chain when bubbled through a parent stream. */
646
+ readonly parentRunIds?: readonly string[];
647
+ /** ISO-8601 event timestamp. */
648
+ readonly at: string;
649
+ /** Most-recent completed child run id whose completion preceded the abort. */
650
+ readonly childRunId: string;
651
+ /** Parent run id (duplicates `runId` for explicit cross-reference). */
652
+ readonly parentRunId: string;
653
+ /** Discriminator (currently always "parent-aborted"; reserved for future variants). */
654
+ readonly reason: "parent-aborted";
655
+ }
656
+
657
+ /**
658
+ * Event emitted when a delegated sub-run's requested `budget.timeoutMs`
659
+ * exceeds the parent's remaining deadline and is therefore clamped to the
660
+ * parent's remaining time (BUDGET-02 / D-12).
661
+ *
662
+ * @remarks
663
+ * Emitted on the parent trace BEFORE `sub-run-started`. When the requested
664
+ * decision-level timeout fits within the parent's remaining deadline, the
665
+ * event is NOT emitted (zero-overhead happy path). The parent's deadline is
666
+ * a hard ceiling for the whole tree, so any decision-level override that
667
+ * exceeds it is silently clamped rather than throwing — recording the
668
+ * requested-vs-clamped pair on the trace preserves provenance for replay.
669
+ */
670
+ export interface SubRunBudgetClampedEvent {
671
+ /** Discriminant for event rendering and exhaustive switches. */
672
+ readonly type: "sub-run-budget-clamped";
673
+ /** Parent run id; matches the surrounding trace runId. */
674
+ readonly runId: string;
675
+ /** Root-first ancestry chain when bubbled through a parent stream. */
676
+ readonly parentRunIds?: readonly string[];
677
+ /** ISO-8601 event timestamp. */
678
+ readonly at: string;
679
+ /** Child run id whose budget was clamped. */
680
+ readonly childRunId: string;
681
+ /** Parent run id (duplicates `runId` for explicit cross-reference). */
682
+ readonly parentRunId: string;
683
+ /** Replay decision id of the parent decision that triggered the sub-run. */
684
+ readonly parentDecisionId: string;
685
+ /** The decision's originally requested `budget.timeoutMs` value (milliseconds). */
686
+ readonly requestedTimeoutMs: number;
687
+ /** The clamped child timeout actually applied (parent's remaining deadline, in milliseconds). */
688
+ readonly clampedTimeoutMs: number;
689
+ /** Discriminator for the clamp cause (currently always "exceeded-parent-remaining"). */
690
+ readonly reason: "exceeded-parent-remaining";
691
+ }
692
+
693
+ /**
694
+ * Event emitted when a delegated sub-run is enqueued because the
695
+ * coordinator's effective `maxConcurrentChildren` budget is fully in flight
696
+ * (Phase 3 D-07). Emitted ONLY when the slot is not immediately free —
697
+ * no-pressure runs do NOT emit this event.
698
+ *
699
+ * Three-event timeline under pressure: sub-run-queued → sub-run-started →
700
+ * sub-run-completed (or sub-run-failed). Without pressure: sub-run-started
701
+ * → completion (no queued event).
702
+ */
703
+ export interface SubRunQueuedEvent {
704
+ readonly type: "sub-run-queued";
705
+ readonly runId: string;
706
+ readonly parentRunIds?: readonly string[];
707
+ readonly at: string;
708
+ readonly childRunId: string;
709
+ readonly parentRunId: string;
710
+ readonly parentDecisionId: string;
711
+ readonly parentDecisionArrayIndex: number;
712
+ readonly protocol: ProtocolName;
713
+ readonly intent: string;
714
+ readonly depth: number;
715
+ readonly queuePosition: number;
716
+ }
717
+
718
+ /**
719
+ * Event emitted ONCE per run when the runtime detects a `"local"` provider
720
+ * in the coordinator's active tree and clamps `maxConcurrentChildren` to 1
721
+ * (Phase 3 CONCURRENCY-02 / D-12). Emitted at the FIRST delegate dispatch
722
+ * where the local-provider check trips. Subsequent dispatches in the same
723
+ * run do NOT re-emit. Runs with no delegates, or runs with delegates but
724
+ * no local provider, never emit this event.
725
+ *
726
+ * The clamp is silent (no throw, no console output) per D-13 — this event
727
+ * IS the warning surface; subscribers can react via the engine's `emit`
728
+ * callback.
729
+ */
730
+ export interface SubRunConcurrencyClampedEvent {
731
+ readonly type: "sub-run-concurrency-clamped";
732
+ readonly runId: string;
733
+ readonly parentRunIds?: readonly string[];
734
+ readonly at: string;
735
+ /** The pre-clamp effective max that would have applied (engine/run/decision min). */
736
+ readonly requestedMax: number;
737
+ /** Always 1 — locality-clamp is a fixed cap. */
738
+ readonly effectiveMax: 1;
739
+ readonly reason: "local-provider-detected";
740
+ /** Stable id of the FIRST local provider found during the active-tree walk. */
741
+ readonly providerId: string;
742
+ }
743
+
421
744
  /**
422
745
  * Successful coordination event emitted by Dogpile and persisted in traces.
423
746
  *
@@ -434,6 +757,11 @@ export interface FinalEvent {
434
757
  * - `tool-result`: one runtime tool invocation completed.
435
758
  * - `agent-turn`: one agent completed a prompt/response turn.
436
759
  * - `broadcast`: a broadcast round gathered independent contributions.
760
+ * - `sub-run-started`: a delegated sub-run was dispatched.
761
+ * - `sub-run-completed`: a delegated sub-run completed and embedded its full result.
762
+ * - `sub-run-failed`: a delegated sub-run failed before completion.
763
+ * - `sub-run-queued`: a delegated sub-run waited for a concurrency slot.
764
+ * - `sub-run-concurrency-clamped`: a local provider forced child concurrency to 1.
437
765
  * - `budget-stop`: a configured budget cap halted further model turns.
438
766
  * - `final`: the run completed and produced the final output.
439
767
  *
@@ -464,6 +792,13 @@ export type RunEvent =
464
792
  | ToolResultEvent
465
793
  | TurnEvent
466
794
  | BroadcastEvent
795
+ | SubRunStartedEvent
796
+ | SubRunCompletedEvent
797
+ | SubRunFailedEvent
798
+ | SubRunParentAbortedEvent
799
+ | SubRunBudgetClampedEvent
800
+ | SubRunQueuedEvent
801
+ | SubRunConcurrencyClampedEvent
467
802
  | BudgetStopEvent
468
803
  | FinalEvent;
469
804
 
@@ -483,10 +818,33 @@ export type ToolActivityEvent = ToolCallEvent | ToolResultEvent;
483
818
  * Lifecycle event yielded by `stream()`.
484
819
  *
485
820
  * These events describe workflow coordination state rather than model text.
486
- * Role assignment establishes the participant roster, while `budget-stop`
487
- * records a lifecycle halt before the terminal completion event.
821
+ * Role assignment establishes the participant roster, `budget-stop` records a
822
+ * lifecycle halt before the terminal completion event, and the `sub-run-*`
823
+ * variants surface delegated child-run dispatch boundaries.
488
824
  */
489
- export type StreamLifecycleEvent = RoleAssignmentEvent | BudgetStopEvent;
825
+ export type StreamLifecycleEvent =
826
+ | RoleAssignmentEvent
827
+ | BudgetStopEvent
828
+ | SubRunStartedEvent
829
+ | SubRunCompletedEvent
830
+ | SubRunFailedEvent
831
+ | SubRunParentAbortedEvent
832
+ | SubRunBudgetClampedEvent
833
+ | SubRunQueuedEvent
834
+ | SubRunConcurrencyClampedEvent
835
+ | AbortedEvent;
836
+
837
+ /**
838
+ * Lifecycle event yielded by `stream()` when a run is aborted.
839
+ */
840
+ export interface AbortedEvent {
841
+ readonly type: "aborted";
842
+ readonly runId: string;
843
+ readonly at: string;
844
+ readonly reason: "parent-aborted" | "timeout";
845
+ readonly detail?: JsonObject;
846
+ readonly parentRunIds?: readonly string[];
847
+ }
490
848
 
491
849
  /**
492
850
  * Output event yielded by `stream()`.
@@ -541,4 +899,3 @@ export type StreamCompletionEvent = FinalEvent;
541
899
  * {@link RunResult} trace to return.
542
900
  */
543
901
  export type StreamEvent = StreamLifecycleEvent | StreamOutputEvent | StreamErrorEvent | StreamCompletionEvent;
544
-