@rulvar/core 1.203.0 → 1.204.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/dist/index.d.ts CHANGED
@@ -381,6 +381,14 @@ type AgentError = {
381
381
  retryable: boolean;
382
382
  retryAfterMs?: number;
383
383
  issues?: Issue$1[];
384
+ /**
385
+ * The typed refusal marker (RV2002): 'exposure-drained' names a
386
+ * spawned child refused pre-wire by the in-flight exposure cap with
387
+ * no live holder left to wait out. Zero provider attempts by
388
+ * construction, so the seat is cheap to re-spawn; an orchestrator
389
+ * treats it as a starved seat, never a crashed child.
390
+ */
391
+ reason?: "exposure-drained";
384
392
  };
385
393
  /**
386
394
  * Projects an AgentError to its WireError form: code 'agent', with kind,
@@ -2438,7 +2446,8 @@ type AgentEvents = {
2438
2446
  } | {
2439
2447
  type: "budget:exposure-wait";
2440
2448
  agentType: string;
2441
- label?: string; /** The refused model ref. */
2449
+ label?: string; /** The waiting party: the orchestrate root or a spawned child. */
2450
+ scope?: "root" | "child"; /** The refused model ref. */
2442
2451
  model?: string; /** The refusal arithmetic, verbatim from the typed refusal. */
2443
2452
  capUsd?: number;
2444
2453
  spentUsd?: number;
@@ -5489,12 +5498,19 @@ interface RunAgentOptions<S extends SchemaSpec = JsonSchema> {
5489
5498
  /**
5490
5499
  * The exposure-wait posture (RV1902): an in-flight exposure refusal
5491
5500
  * on this invocation parks until a live hold releases and retries
5492
- * pre-wire, instead of settling a budget error. Set only by the
5493
- * orchestrate-owned root dispatches (the coordination loop, the
5494
- * synthesis invocation, the forced-finish wake), whose settle would
5495
- * tear down the run its own admitted children are still funding.
5496
- */
5497
- exposureWait?: boolean;
5501
+ * pre-wire, instead of settling a budget error. `true` is set only
5502
+ * by the orchestrate-owned root dispatches (the coordination loop,
5503
+ * the synthesis invocation, the forced-finish wake), whose settle
5504
+ * would tear down the run its own admitted children are still
5505
+ * funding. `'child'` (RV2002) rides on orchestrator-spawned
5506
+ * children: the same park-and-retry, but the drained arm (no live
5507
+ * holder left to wait out) dies as the typed cheap
5508
+ * 'exposure-drained' refusal instead of the raw budget error, so
5509
+ * the orchestrator can tell a starved seat apart from a crashed
5510
+ * child and re-spawn it; the third parity rerun terminally killed
5511
+ * three mid-research workers on exactly this path.
5512
+ */
5513
+ exposureWait?: boolean | "child";
5498
5514
  events?: RuntimeEventSink;
5499
5515
  transcript?: {
5500
5516
  mintRef(): string;
package/dist/index.js CHANGED
@@ -413,6 +413,7 @@ var DeterminismError = class extends RulvarError {
413
413
  function agentErrorToWire(error, message) {
414
414
  const data = { kind: error.kind };
415
415
  if (error.retryAfterMs !== void 0) data.retryAfterMs = error.retryAfterMs;
416
+ if (error.reason !== void 0) data.reason = error.reason;
416
417
  if (error.issues !== void 0) data.issues = error.issues.map((issue) => {
417
418
  const out = { message: issue.message };
418
419
  if (issue.path !== void 0) out.path = issue.path.map((segment) => {
@@ -12368,20 +12369,30 @@ async function runAgent(options) {
12368
12369
  } catch (thrown) {
12369
12370
  const refusalData = thrown instanceof BudgetExhaustedError ? thrown.data : void 0;
12370
12371
  const awaitRelease = options.budget?.awaitExposureRelease;
12371
- if (options.exposureWait !== true || refusalData?.reason !== "in-flight-exposure" || awaitRelease === void 0) throw thrown;
12372
+ if (options.exposureWait !== true && options.exposureWait !== "child" || refusalData?.reason !== "in-flight-exposure" || awaitRelease === void 0) throw thrown;
12373
+ const waitScope = options.exposureWait === "child" ? "child" : "root";
12372
12374
  const willWait = (options.budget?.liveExposureUsd?.() ?? 0) > 0;
12373
12375
  events?.emit({
12374
12376
  type: "budget:exposure-wait",
12375
12377
  agentType,
12376
12378
  label: options.label,
12377
12379
  model: target.resolved.ref,
12380
+ scope: waitScope,
12378
12381
  ...typeof refusalData.capUsd === "number" ? { capUsd: refusalData.capUsd } : {},
12379
12382
  ...typeof refusalData.spentUsd === "number" ? { spentUsd: refusalData.spentUsd } : {},
12380
12383
  ...typeof refusalData.inFlightUsd === "number" ? { inFlightUsd: refusalData.inFlightUsd } : {},
12381
12384
  ...typeof refusalData.estimateUsd === "number" ? { estimateUsd: refusalData.estimateUsd } : {},
12382
12385
  willWait
12383
12386
  });
12384
- if (!willWait) throw thrown;
12387
+ if (!willWait) {
12388
+ if (waitScope === "child") throw new BudgetExhaustedError(`exposure pool drained for the spawned child: ${thrown instanceof Error ? thrown.message : String(thrown)}`, { data: {
12389
+ reason: "exposure-drained",
12390
+ ...typeof refusalData.capUsd === "number" ? { capUsd: refusalData.capUsd } : {},
12391
+ ...typeof refusalData.spentUsd === "number" ? { spentUsd: refusalData.spentUsd } : {},
12392
+ ...typeof refusalData.estimateUsd === "number" ? { estimateUsd: refusalData.estimateUsd } : {}
12393
+ } });
12394
+ throw thrown;
12395
+ }
12385
12396
  const waitSignals = [options.signal, options.budget?.signal].filter((candidate) => candidate !== void 0);
12386
12397
  await awaitRelease(waitSignals.length === 0 ? void 0 : AbortSignal.any(waitSignals));
12387
12398
  continue;
@@ -12604,7 +12615,8 @@ async function runAgent(options) {
12604
12615
  status = "error";
12605
12616
  agentError = {
12606
12617
  kind: "budget",
12607
- retryable: false
12618
+ retryable: false,
12619
+ ...thrown.data?.reason === "exposure-drained" ? { reason: "exposure-drained" } : {}
12608
12620
  };
12609
12621
  errorMessage = thrown.message;
12610
12622
  break;
@@ -16473,9 +16485,16 @@ const kFinalizeReserve = Symbol("rulvar.finalizeReserve");
16473
16485
  * benchmark's recovery arm died exactly there: the refusal is transient
16474
16486
  * by contract (budgets guide), but the refused agent was the workflow's
16475
16487
  * coordinating root, so its settle tore down the whole run while four
16476
- * admitted children were still finalizing. Never part of the public
16477
- * AgentOpts surface; plain agents keep the documented settle-as-budget-
16478
- * error behavior, because their caller can catch and decide.
16488
+ * admitted children were still finalizing. The 'child' flavor (RV2002)
16489
+ * rides on orchestrator-spawned children (spawn_agent and
16490
+ * parallel_agents): the same park-and-retry, but a DRAINED refusal
16491
+ * (no live holder left to wait out) dies as the typed cheap
16492
+ * 'exposure-drained' child refusal the orchestrator can tell apart
16493
+ * from a crash and re-spawn, instead of the root's forced-finish
16494
+ * partial; the third parity rerun killed three mid-research workers
16495
+ * on exactly this path. Never part of the public AgentOpts surface;
16496
+ * plain agents keep the documented settle-as-budget-error behavior,
16497
+ * because their caller can catch and decide.
16479
16498
  */
16480
16499
  const kExposureWait = Symbol("rulvar.exposureWait");
16481
16500
  /** Typed accessor used by the in-package consumers. */
@@ -17852,7 +17871,10 @@ function createCtx(internals, rootWorkflow) {
17852
17871
  if (escalation !== void 0) runAgentOptions.escalation = { minSpendUsd: escalation.minSpendUsd ?? 0 };
17853
17872
  const terminalTool = opts[kTerminalTool];
17854
17873
  if (terminalTool !== void 0) runAgentOptions.terminalTool = terminalTool;
17855
- if (opts[kExposureWait] === true) runAgentOptions.exposureWait = true;
17874
+ {
17875
+ const exposureWait = opts[kExposureWait];
17876
+ if (exposureWait === true || exposureWait === "child") runAgentOptions.exposureWait = exposureWait;
17877
+ }
17856
17878
  runAgentOptions.checkpoint = checkpointPlumbing;
17857
17879
  if (opts.schema !== void 0) runAgentOptions.schema = opts.schema;
17858
17880
  if (canonicalSchema !== void 0) runAgentOptions.canonicalSchema = canonicalSchema;
@@ -21470,7 +21492,8 @@ function makeOrchestratorWorkflow(goal, opts) {
21470
21492
  agentType: spec.agentType,
21471
21493
  result: "full",
21472
21494
  ...resolveDispatchOpts(spec, internals.defaults),
21473
- [kOnRunning]: (seq) => resolveHandle(seq)
21495
+ [kOnRunning]: (seq) => resolveHandle(seq),
21496
+ [kExposureWait]: "child"
21474
21497
  };
21475
21498
  const result = runtime.runInScope(childState, () => ctx.agent(spec.prompt, agentOpts));
21476
21499
  const PRE_ROOT_FAILED = -1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/core",
3
- "version": "1.203.0",
3
+ "version": "1.204.0",
4
4
  "description": "Rulvar core: L0 contracts, journal kernel, ctx primitives, agent runtime, model router, tool system, dynamic orchestrator, InMemory and JSONL stores, event stream.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",