deepline 0.1.201 → 0.1.202

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 (32) hide show
  1. package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +30 -9
  2. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +25 -0
  3. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +139 -124
  4. package/dist/bundling-sources/sdk/src/release.ts +2 -2
  5. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +17 -2
  6. package/dist/bundling-sources/shared_libs/play-runtime/bounded-dispatch.ts +52 -0
  7. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +152 -23
  8. package/dist/bundling-sources/shared_libs/play-runtime/coordinator-headers.ts +10 -0
  9. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +11 -0
  10. package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +121 -0
  11. package/dist/bundling-sources/shared_libs/play-runtime/governor/adaptive-admission.ts +6 -1
  12. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-budget-state-backend.ts +22 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +86 -19
  14. package/dist/bundling-sources/shared_libs/play-runtime/governor/block-reserving-budget-state-backend.ts +141 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/governor/budget-state-backend.ts +43 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +129 -41
  17. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +40 -2
  18. package/dist/bundling-sources/shared_libs/play-runtime/receipt-completion-sink.ts +92 -33
  19. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +13 -5
  20. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +122 -120
  21. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +10 -8
  22. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +140 -32
  23. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +225 -29
  24. package/dist/bundling-sources/shared_libs/play-runtime/runtime-scheduler-topology.ts +26 -0
  25. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +21 -0
  26. package/dist/bundling-sources/shared_libs/plays/dataset.ts +32 -10
  27. package/dist/cli/index.js +198 -76
  28. package/dist/cli/index.mjs +204 -82
  29. package/dist/index.js +3 -2
  30. package/dist/index.mjs +3 -2
  31. package/package.json +1 -1
  32. package/dist/bundling-sources/shared_libs/play-runtime/adaptive-tool-dispatcher.ts +0 -355
@@ -53,6 +53,7 @@ import {
53
53
  CoordinatorRateStateBackend,
54
54
  type CoordinatorRatePort,
55
55
  } from '../../../shared_libs/play-runtime/governor/coordinator-rate-state-backend';
56
+ import { BlockReservingBudgetStateBackend } from '../../../shared_libs/play-runtime/governor/block-reserving-budget-state-backend';
56
57
  import type { PacingRule } from '../../../shared_libs/play-runtime/governor/rate-state-backend';
57
58
  import {
58
59
  pacingPolicyForTool,
@@ -250,6 +251,7 @@ import { runtimeSheetSessionScope } from '../../../shared_libs/play-runtime/runt
250
251
  import {
251
252
  chooseWorkerMapDispatchPlan,
252
253
  estimateBatchedToolChunkSubrequests,
254
+ workflowSafeMapRowsPerChunk,
253
255
  } from './runtime/map-chunk-plan';
254
256
  import {
255
257
  applyCsvRenameProjection,
@@ -337,6 +339,8 @@ type RunRequest = {
337
339
  forceToolRefresh?: boolean;
338
340
  /** Monotonic coordinator redispatch epoch for receipt and sheet fencing. */
339
341
  runAttempt?: number;
342
+ /** Tenant/worktree scheduler schema for shared durable governor state. */
343
+ runtimeSchedulerSchema?: string | null;
340
344
  /** Optional inline CSV rows (for plays where ctx.csv was passed inline data). */
341
345
  inlineCsv?: { name: string; rows: Record<string, unknown>[] } | null;
342
346
  /** Staged input files keyed by logical filename (used by ctx.csv). */
@@ -1823,7 +1827,7 @@ async function executeTool(
1823
1827
  },
1824
1828
  workflowStep?: WorkflowStep,
1825
1829
  onProviderBackpressure?: (retryAfterMs: number) => void,
1826
- onRetryAttempt?: () => void,
1830
+ onRetryAttempt?: () => void | Promise<void>,
1827
1831
  transientHttpRetrySafe = false,
1828
1832
  abortSignal?: AbortSignal,
1829
1833
  runtimeDeadlineMs?: number,
@@ -1869,7 +1873,7 @@ async function executeToolWithLifecycle(
1869
1873
  workflowStep: WorkflowStep | undefined,
1870
1874
  callbacks: WorkerCtxCallbacks | undefined,
1871
1875
  onProviderBackpressure?: (retryAfterMs: number) => void,
1872
- onRetryAttempt?: () => void,
1876
+ onRetryAttempt?: () => void | Promise<void>,
1873
1877
  transientHttpRetrySafe = false,
1874
1878
  abortSignal?: AbortSignal,
1875
1879
  runtimeDeadlineMs?: number,
@@ -2169,7 +2173,7 @@ async function callToolDirect(
2169
2173
  ) {
2170
2174
  throw lastError;
2171
2175
  }
2172
- onRetryAttempt?.();
2176
+ await onRetryAttempt?.();
2173
2177
  const delayMs = TOOL_EXECUTE_TRANSPORT_RETRY_DELAY_MS * transportAttempt;
2174
2178
  console.warn(
2175
2179
  `[deepline-run:${req.runId}] tool transport retry tool=${toolId} path=${path} attempt=${transportAttempt}/${TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS} retryAfterMs=${delayMs} error=${redactSecretsFromLogString(message)}`,
@@ -2224,7 +2228,7 @@ async function callToolDirect(
2224
2228
  // Charge the retry budget per attempt, matching the cjs runner's
2225
2229
  // chargeBudget('retry') on every 429 / retryable-5xx retry.
2226
2230
  if (failure.chargeRetryBudget) {
2227
- onRetryAttempt?.();
2231
+ await onRetryAttempt?.();
2228
2232
  }
2229
2233
  await sleepWorkerMs(failure.retryDelayMs);
2230
2234
  }
@@ -2954,7 +2958,7 @@ async function postRuntimeEgressFetch(
2954
2958
  req: RunRequest,
2955
2959
  payload: RuntimeEgressFetchPayload,
2956
2960
  onProviderBackpressure?: (retryAfterMs: number) => void,
2957
- onRetryAttempt?: () => void,
2961
+ onRetryAttempt?: () => void | Promise<void>,
2958
2962
  ): Promise<WorkerFetchResponse> {
2959
2963
  let lastError: Error | null = null;
2960
2964
  for (
@@ -3008,7 +3012,7 @@ async function postRuntimeEgressFetch(
3008
3012
  if (attempt >= TOOL_EXECUTE_RATE_LIMIT_MAX_ATTEMPTS) {
3009
3013
  throw lastError;
3010
3014
  }
3011
- onRetryAttempt?.();
3015
+ await onRetryAttempt?.();
3012
3016
  await sleepWorkerMs(
3013
3017
  Math.min(5_000, Math.max(retryAfterMs, attempt * 1000)),
3014
3018
  );
@@ -4309,6 +4313,16 @@ function createGovernorForRun(req: RunRequest): {
4309
4313
  rootRunId: req.playCallGovernance?.rootRunId ?? req.runId,
4310
4314
  },
4311
4315
  rateState: new CoordinatorRateStateBackend(createCoordinatorRatePort(req)),
4316
+ budgetState: new BlockReservingBudgetStateBackend(
4317
+ async (input) =>
4318
+ await postRuntimeApi(req.baseUrl, req.executorToken, {
4319
+ action: 'governor_budget_charge',
4320
+ rootRunId: input.rootRunId,
4321
+ reservationId: input.reservationId,
4322
+ charges: input.charges,
4323
+ schedulerSchema: req.runtimeSchedulerSchema ?? null,
4324
+ }),
4325
+ ),
4312
4326
  resolvePacing,
4313
4327
  resume: resumeGovernanceFromRequest(req),
4314
4328
  });
@@ -4415,7 +4429,7 @@ function createMinimalWorkerCtx(
4415
4429
  workflowStep?: unknown;
4416
4430
  callbacks?: WorkerCtxCallbacks;
4417
4431
  onProviderBackpressure?: (retryAfterMs: number) => void;
4418
- onRetryAttempt?: () => void;
4432
+ onRetryAttempt?: () => void | Promise<void>;
4419
4433
  transientHttpRetrySafe?: boolean;
4420
4434
  durableCallReceiptKey?: string | null;
4421
4435
  executionAuthScopeDigest?: string | null;
@@ -4604,7 +4618,13 @@ function createMinimalWorkerCtx(
4604
4618
  mapDispatchPlan.workEstimate.providerToolCallsPerRow > 0 ||
4605
4619
  mapDispatchPlan.workEstimate.childPlaySubmitsPerRow > 0 ||
4606
4620
  mapDispatchPlan.workEstimate.eventWaitsPerRow > 0;
4607
- const rowsPerChunk = mapDispatchPlan.rowsPerChunk;
4621
+ const plannedRowsPerChunk = mapDispatchPlan.rowsPerChunk;
4622
+ const rowsPerChunk = workflowSafeMapRowsPerChunk({
4623
+ plannedRowsPerChunk,
4624
+ rowAdmissionWindow: governor.policy.concurrency.rowDefault,
4625
+ hasWorkflowStep: Boolean(workflowStep),
4626
+ hasExternalSideEffects: mapHasExternalSideEffects,
4627
+ });
4608
4628
  recordRunnerPerfTrace({
4609
4629
  req,
4610
4630
  phase: 'runner.map_dispatch_plan',
@@ -4612,6 +4632,7 @@ function createMinimalWorkerCtx(
4612
4632
  extra: {
4613
4633
  mapName: name,
4614
4634
  rowsPerChunk,
4635
+ plannedRowsPerChunk,
4615
4636
  rowCountHint,
4616
4637
  estimatedExternalStepsPerRow:
4617
4638
  mapDispatchPlan.estimatedExternalStepsPerRow,
@@ -6949,7 +6970,7 @@ function createMinimalWorkerCtx(
6949
6970
  // across isolates. Charged inside the receipt boundary so a replay
6950
6971
  // (cache hit) never double-charges.
6951
6972
  const childRunId = `${req.runId}:child:${normalizedKey}`;
6952
- const childGovernance = governor.forkChild({
6973
+ const childGovernance = await governor.forkChild({
6953
6974
  childPlayName: resolvedName,
6954
6975
  childRunId,
6955
6976
  });
@@ -146,6 +146,31 @@ export type WorkerMapWorkEstimate = {
146
146
  eventWaitsPerRow: number;
147
147
  };
148
148
 
149
+ /**
150
+ * Keep one externally-effectful Workflow chunk inside the same bounded row
151
+ * window used by runtime admission. The static planner may choose a larger
152
+ * chunk when provider calls coalesce, but a chunk is still the smallest point
153
+ * where the dispatcher can cross a durable sleep boundary. Capping only the
154
+ * native Workflow execution chunk prevents a slow 5k provider chunk from
155
+ * outliving one invocation and being retried before the dispatcher can yield.
156
+ */
157
+ export function workflowSafeMapRowsPerChunk(input: {
158
+ plannedRowsPerChunk: number;
159
+ rowAdmissionWindow: number;
160
+ hasWorkflowStep: boolean;
161
+ hasExternalSideEffects: boolean;
162
+ }): number {
163
+ const plannedRowsPerChunk = Math.max(
164
+ 1,
165
+ Math.floor(input.plannedRowsPerChunk),
166
+ );
167
+ if (!input.hasWorkflowStep || !input.hasExternalSideEffects) {
168
+ return plannedRowsPerChunk;
169
+ }
170
+ const rowAdmissionWindow = Math.max(1, Math.floor(input.rowAdmissionWindow));
171
+ return Math.min(plannedRowsPerChunk, rowAdmissionWindow);
172
+ }
173
+
149
174
  function fieldRoot(field: string | null | undefined): string {
150
175
  return String(field ?? '').split('.', 1)[0] ?? '';
151
176
  }
@@ -3,6 +3,7 @@ import {
3
3
  executeChunkedRequests,
4
4
  type ChunkExecutionResult,
5
5
  } from '../../../../shared_libs/play-runtime/batch-runtime';
6
+ import { dispatchBounded } from '../../../../shared_libs/play-runtime/bounded-dispatch';
6
7
  import type { AnyBatchOperationStrategy } from '../../../../shared_libs/play-runtime/batching-types';
7
8
  import {
8
9
  buildDurableToolCallAuthScopeDigest,
@@ -110,7 +111,7 @@ export type WorkerToolDispatchExecuteTool = (input: {
110
111
  workflowStep?: WorkflowStepLike;
111
112
  callbacks?: WorkerToolDispatchCallbacks;
112
113
  onProviderBackpressure?: (retryAfterMs: number) => void;
113
- onRetryAttempt?: () => void;
114
+ onRetryAttempt?: () => void | Promise<void>;
114
115
  transientHttpRetrySafe?: boolean;
115
116
  durableCallReceiptKey?: string | null;
116
117
  executionAuthScopeDigest?: string | null;
@@ -2026,32 +2027,137 @@ export class WorkerToolBatchScheduler {
2026
2027
  claimedRequests.length < 2
2027
2028
  ) {
2028
2029
  const groupStartedAt = this.options.nowMs();
2029
- await Promise.all(
2030
- claimedRequests.map(async (claimed) => {
2031
- const { request } = claimed;
2032
- const toolContract = await this.options
2033
- .resolvePacing(toolId)
2034
- .catch(() => null);
2035
- const circuitOpenError = this.runtimePersistenceCircuitOpenError(1);
2036
- if (circuitOpenError) {
2037
- this.rejectRequests(
2038
- claimed,
2039
- await this.failureForRejectedToolRequest(
2040
- claimed,
2041
- circuitOpenError,
2030
+ // Seed the dispatch width from the governor's provider-shaped parallelism
2031
+ // instead of the flat policy.concurrency.toolCalls ceiling. A flat width
2032
+ // launched every claimed row at once into the pacer, so unhinted providers
2033
+ // 429'd on the first burst before AIMD could halve the rate. The shaped
2034
+ // estimate is derived from the provider's RPS/maxConcurrency pacing rules
2035
+ // (see suggestedParallelism in governor.ts), floored at 1 and capped by the
2036
+ // global tool-call concurrency ceiling. Per-call pacing still gates each
2037
+ // in-flight request, so this only trims the launch burst.
2038
+ const toolCallConcurrencyCeiling =
2039
+ this.options.governor.policy.concurrency.toolCalls;
2040
+ const shapedToolParallelism =
2041
+ await this.options.governor.suggestedParallelism(
2042
+ toolId,
2043
+ toolCallConcurrencyCeiling,
2044
+ );
2045
+ const dispatchWidth = Math.min(
2046
+ toolCallConcurrencyCeiling,
2047
+ Math.max(1, shapedToolParallelism),
2048
+ );
2049
+ await dispatchBounded(claimedRequests, dispatchWidth, async (claimed) => {
2050
+ const { request } = claimed;
2051
+ const toolContract = await this.options
2052
+ .resolvePacing(toolId)
2053
+ .catch(() => null);
2054
+ const circuitOpenError = this.runtimePersistenceCircuitOpenError(1);
2055
+ if (circuitOpenError) {
2056
+ this.rejectRequests(
2057
+ claimed,
2058
+ await this.failureForRejectedToolRequest(claimed, circuitOpenError),
2059
+ );
2060
+ return;
2061
+ }
2062
+ // Each unbatched provider call takes its own tool slot: the Governor
2063
+ // charges tool budget, holds a global tool-concurrency slot, and
2064
+ // applies per-(org,provider) pacing before the call runs.
2065
+ let slot: { release: () => void };
2066
+ try {
2067
+ slot = await this.options.governor.acquireToolSlot(toolId, {
2068
+ signal: this.options.abortSignal,
2069
+ });
2070
+ } catch (error) {
2071
+ this.rejectRequests(
2072
+ claimed,
2073
+ await this.failureForRejectedToolRequest(claimed, error),
2074
+ );
2075
+ return;
2076
+ }
2077
+ try {
2078
+ let result: unknown;
2079
+ try {
2080
+ await this.markAdmittedToolRequestsRunning([claimed]);
2081
+ this.options.budgetMeter?.count('provider');
2082
+ this.options.budgetMeter?.count(
2083
+ 'egress',
2084
+ Math.max(
2085
+ 0,
2086
+ WORKER_PLATFORM_SUBREQUESTS_PER_UNBATCHED_TOOL_CALL - 1,
2042
2087
  ),
2043
2088
  );
2044
- return;
2045
- }
2046
- // Each unbatched provider call takes its own tool slot: the Governor
2047
- // charges tool budget, holds a global tool-concurrency slot, and
2048
- // applies per-(org,provider) pacing before the call runs.
2049
- let slot: { release: () => void };
2050
- try {
2051
- slot = await this.options.governor.acquireToolSlot(toolId, {
2052
- signal: this.options.abortSignal,
2089
+ const durableReceiptKey = claimed.receiptKey
2090
+ ? durableCallReceiptKeyForClaimed({
2091
+ req: this.options.req,
2092
+ claimed,
2093
+ })
2094
+ : null;
2095
+ result = await executeWithWorkerReceiptHeartbeats({
2096
+ playName: this.options.req.playName,
2097
+ runId: this.options.req.runId,
2098
+ runAttempt: this.options.req.runAttempt ?? 0,
2099
+ ...(this.options.receiptStore
2100
+ ? { receiptStore: this.options.receiptStore }
2101
+ : {}),
2102
+ budgetMeter: this.options.budgetMeter,
2103
+ heartbeatIntervalMs:
2104
+ this.options.runtimeReceiptHeartbeatIntervalMs,
2105
+ targets: claimed.receiptKey
2106
+ ? [
2107
+ {
2108
+ key: claimed.receiptKey,
2109
+ leaseId: claimed.receiptLeaseId,
2110
+ },
2111
+ ]
2112
+ : [],
2113
+ execute: () =>
2114
+ this.options.executeTool({
2115
+ id: request.id,
2116
+ toolId,
2117
+ input: request.input,
2118
+ workflowStep: request.workflowStep,
2119
+ callbacks: this.options.callbacks,
2120
+ onProviderBackpressure: (retryAfterMs) =>
2121
+ this.reportBackpressure(toolId, retryAfterMs),
2122
+ onRetryAttempt: () =>
2123
+ this.options.governor.chargeBudget('retry'),
2124
+ transientHttpRetrySafe:
2125
+ toolContract?.retrySafeTransientHttp === true,
2126
+ ...(durableReceiptKey
2127
+ ? {
2128
+ durableCallReceiptKey: durableReceiptKey,
2129
+ executionAuthScopeDigest:
2130
+ request.executionAuthScopeDigest,
2131
+ providerIdempotencyKey:
2132
+ providerIdempotencyKeyForClaimed({
2133
+ req: this.options.req,
2134
+ claimed,
2135
+ }),
2136
+ }
2137
+ : {}),
2138
+ runtimeTimeoutMs: resolveWorkerToolRuntimeTimeoutMs(
2139
+ [claimed],
2140
+ {
2141
+ resolveOwnerTimeoutMs: (request) =>
2142
+ request.runtimeTimeoutMs,
2143
+ },
2144
+ ),
2145
+ directOptions: request.directOptions,
2146
+ }),
2053
2147
  });
2054
2148
  } catch (error) {
2149
+ if (error instanceof RuntimeLeaseLostError) {
2150
+ this.rejectRequests(claimed, error);
2151
+ return;
2152
+ }
2153
+ if (
2154
+ await this.requeueAfterAuthScopeChanged(
2155
+ [request, ...claimed.followers],
2156
+ error,
2157
+ )
2158
+ ) {
2159
+ return;
2160
+ }
2055
2161
  this.rejectRequests(
2056
2162
  claimed,
2057
2163
  await this.failureForRejectedToolRequest(claimed, error),
@@ -2059,108 +2165,17 @@ export class WorkerToolBatchScheduler {
2059
2165
  return;
2060
2166
  }
2061
2167
  try {
2062
- let result: unknown;
2063
- try {
2064
- await this.markAdmittedToolRequestsRunning([claimed]);
2065
- this.options.budgetMeter?.count('provider');
2066
- this.options.budgetMeter?.count(
2067
- 'egress',
2068
- Math.max(
2069
- 0,
2070
- WORKER_PLATFORM_SUBREQUESTS_PER_UNBATCHED_TOOL_CALL - 1,
2071
- ),
2072
- );
2073
- const durableReceiptKey = claimed.receiptKey
2074
- ? durableCallReceiptKeyForClaimed({
2075
- req: this.options.req,
2076
- claimed,
2077
- })
2078
- : null;
2079
- result = await executeWithWorkerReceiptHeartbeats({
2080
- playName: this.options.req.playName,
2081
- runId: this.options.req.runId,
2082
- runAttempt: this.options.req.runAttempt ?? 0,
2083
- ...(this.options.receiptStore
2084
- ? { receiptStore: this.options.receiptStore }
2085
- : {}),
2086
- budgetMeter: this.options.budgetMeter,
2087
- heartbeatIntervalMs:
2088
- this.options.runtimeReceiptHeartbeatIntervalMs,
2089
- targets: claimed.receiptKey
2090
- ? [
2091
- {
2092
- key: claimed.receiptKey,
2093
- leaseId: claimed.receiptLeaseId,
2094
- },
2095
- ]
2096
- : [],
2097
- execute: () =>
2098
- this.options.executeTool({
2099
- id: request.id,
2100
- toolId,
2101
- input: request.input,
2102
- workflowStep: request.workflowStep,
2103
- callbacks: this.options.callbacks,
2104
- onProviderBackpressure: (retryAfterMs) =>
2105
- this.reportBackpressure(toolId, retryAfterMs),
2106
- onRetryAttempt: () =>
2107
- this.options.governor.chargeBudget('retry'),
2108
- transientHttpRetrySafe:
2109
- toolContract?.retrySafeTransientHttp === true,
2110
- ...(durableReceiptKey
2111
- ? {
2112
- durableCallReceiptKey: durableReceiptKey,
2113
- executionAuthScopeDigest:
2114
- request.executionAuthScopeDigest,
2115
- providerIdempotencyKey:
2116
- providerIdempotencyKeyForClaimed({
2117
- req: this.options.req,
2118
- claimed,
2119
- }),
2120
- }
2121
- : {}),
2122
- runtimeTimeoutMs: resolveWorkerToolRuntimeTimeoutMs(
2123
- [claimed],
2124
- {
2125
- resolveOwnerTimeoutMs: (request) =>
2126
- request.runtimeTimeoutMs,
2127
- },
2128
- ),
2129
- directOptions: request.directOptions,
2130
- }),
2131
- });
2132
- } catch (error) {
2133
- if (error instanceof RuntimeLeaseLostError) {
2134
- this.rejectRequests(claimed, error);
2135
- return;
2136
- }
2137
- if (
2138
- await this.requeueAfterAuthScopeChanged(
2139
- [request, ...claimed.followers],
2140
- error,
2141
- )
2142
- ) {
2143
- return;
2144
- }
2145
- this.rejectRequests(
2146
- claimed,
2147
- await this.failureForRejectedToolRequest(claimed, error),
2148
- );
2149
- return;
2150
- }
2151
- try {
2152
- this.settleRequests(
2153
- claimed,
2154
- await this.completeDurableToolRequest(claimed, result),
2155
- );
2156
- } catch (receiptError) {
2157
- this.rejectRequests(claimed, receiptError);
2158
- }
2159
- } finally {
2160
- slot.release();
2168
+ this.settleRequests(
2169
+ claimed,
2170
+ await this.completeDurableToolRequest(claimed, result),
2171
+ );
2172
+ } catch (receiptError) {
2173
+ this.rejectRequests(claimed, receiptError);
2161
2174
  }
2162
- }),
2163
- );
2175
+ } finally {
2176
+ slot.release();
2177
+ }
2178
+ });
2164
2179
  this.options.recordPerfTrace?.({
2165
2180
  phase: 'runner.tool.group',
2166
2181
  ms: this.options.nowMs() - groupStartedAt,
@@ -106,10 +106,10 @@ export const SDK_RELEASE = {
106
106
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
107
107
  // fields shipped in 0.1.153.
108
108
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
109
- version: '0.1.201',
109
+ version: '0.1.202',
110
110
  apiContract: '2026-06-dataset-handle-results-hard-cutover',
111
111
  supportPolicy: {
112
- latest: '0.1.201',
112
+ latest: '0.1.202',
113
113
  minimumSupported: '0.1.53',
114
114
  deprecatedBelow: '0.1.53',
115
115
  commandMinimumSupported: [
@@ -29,6 +29,8 @@ import type { PlayBundleArtifact } from '@shared_libs/plays/artifact-types';
29
29
  import type {
30
30
  PlayRunnerRateStateAcquireInput,
31
31
  PlayRunnerRateStateAcquireResult,
32
+ PlayRunnerBudgetChargeInput,
33
+ PlayRunnerBudgetChargeResult,
32
34
  PlayRunnerRateStatePenalizeInput,
33
35
  PlayRunnerRateStateReleaseInput,
34
36
  } from '@shared_libs/play-runtime/protocol';
@@ -176,6 +178,9 @@ type RuntimeApiRequest =
176
178
  | ({
177
179
  action: 'rate_state_penalize';
178
180
  } & PlayRunnerRateStatePenalizeInput)
181
+ | ({
182
+ action: 'governor_budget_charge';
183
+ } & PlayRunnerBudgetChargeInput)
179
184
  | {
180
185
  action: 'get_runtime_step_receipt';
181
186
  playName: string;
@@ -426,8 +431,8 @@ function summarizeAppRuntimeErrorBody(body: string): string {
426
431
  return trimmed.length > 500 ? `${trimmed.slice(0, 500)}...` : trimmed;
427
432
  }
428
433
 
429
- function isTransientAppRuntimeFailureBody(body: string): boolean {
430
- return /WorkerOverloaded|"\s*code\s*"\s*:\s*"\s*InternalServerError\s*"|Your request couldn't be completed\. Try again later\.|timeout exceeded when trying to connect|timed out|fetch failed|ECONNRESET|ECONNREFUSED|UND_ERR_CONNECT_TIMEOUT|tuple concurrently updated/i.test(
434
+ export function isTransientAppRuntimeFailureBody(body: string): boolean {
435
+ return /WorkerOverloaded|"\s*code\s*"\s*:\s*"\s*InternalServerError\s*"|Your request couldn't be completed\. Try again later\.|timeout exceeded when trying to connect|timed out|fetch failed|ECONNRESET|ECONNREFUSED|UND_ERR_CONNECT_TIMEOUT|tuple concurrently updated|RUNTIME_SCHEDULER_SATURATED|Runtime scheduler DB pool is saturated|Runtime scheduler DB circuit breaker is open/i.test(
431
436
  body,
432
437
  );
433
438
  }
@@ -751,6 +756,16 @@ export async function penalizeRateStateViaAppRuntime(
751
756
  });
752
757
  }
753
758
 
759
+ export async function chargeGovernorBudgetViaAppRuntime(
760
+ context: WorkerRuntimeApiContext,
761
+ input: PlayRunnerBudgetChargeInput,
762
+ ): Promise<PlayRunnerBudgetChargeResult> {
763
+ return await postAppRuntimeApi<PlayRunnerBudgetChargeResult>(context, {
764
+ action: 'governor_budget_charge',
765
+ ...input,
766
+ });
767
+ }
768
+
754
769
  export async function writeStagedFileFromAppRuntime(
755
770
  context: WorkerRuntimeApiContext,
756
771
  file: Pick<PlayStagedFileRef, 'storageKey'>,
@@ -0,0 +1,52 @@
1
+ export type BoundedDispatchResult<TJob, TResult> = {
2
+ job: TJob;
3
+ index: number;
4
+ result: TResult | null;
5
+ error?: unknown;
6
+ };
7
+
8
+ /** Drain a finite queue with a fixed number of workers. Pacing and adaptive
9
+ * admission belong inside `execute`, at the RuntimeResourceGovernor seam. */
10
+ export async function dispatchBoundedSettled<TJob, TResult>(
11
+ jobs: readonly TJob[],
12
+ concurrency: number,
13
+ execute: (job: TJob, index: number) => Promise<TResult>,
14
+ ): Promise<Array<BoundedDispatchResult<TJob, TResult>>> {
15
+ if (jobs.length === 0) return [];
16
+ const width = Math.min(
17
+ jobs.length,
18
+ Math.max(1, Math.floor(Number.isFinite(concurrency) ? concurrency : 1)),
19
+ );
20
+ const results = new Array<BoundedDispatchResult<TJob, TResult>>(jobs.length);
21
+ let cursor = 0;
22
+ const workers = Array.from({ length: width }, async () => {
23
+ while (true) {
24
+ const index = cursor;
25
+ cursor += 1;
26
+ if (index >= jobs.length) return;
27
+ const job = jobs[index]!;
28
+ try {
29
+ results[index] = {
30
+ job,
31
+ index,
32
+ result: await execute(job, index),
33
+ };
34
+ } catch (error) {
35
+ results[index] = { job, index, result: null, error };
36
+ }
37
+ }
38
+ });
39
+ await Promise.all(workers);
40
+ return results;
41
+ }
42
+
43
+ export async function dispatchBounded<TJob, TResult>(
44
+ jobs: readonly TJob[],
45
+ concurrency: number,
46
+ execute: (job: TJob, index: number) => Promise<TResult>,
47
+ ): Promise<TResult[]> {
48
+ const settled = await dispatchBoundedSettled(jobs, concurrency, execute);
49
+ const failed = settled.find((entry) => entry.error !== undefined);
50
+ if (failed) throw failed.error;
51
+ return settled.map((entry) => entry.result as TResult);
52
+ }