@rulvar/core 1.217.0 → 1.218.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
@@ -6470,7 +6470,20 @@ declare class RunBudget {
6470
6470
  /**
6471
6471
  * Resume roll-forward: commits a reserve recovered from a journaled
6472
6472
  * spawn-admission decision entry without re-evaluating admission
6473
- * (reserves are recovered, never re-estimated).
6473
+ * (reserves are recovered, never re-estimated). The lifetime spawn
6474
+ * counter does NOT increment here (RV2201): every agent the
6475
+ * roll-forward re-covers already counted through the resume seed,
6476
+ * whose journal fold counts each dispatched agent entry, so an
6477
+ * incrementing roll-forward double-counts every recovered child.
6478
+ * The seventh subscription parity run resumed a killed 4-child
6479
+ * fan-out into a seed of 5, re-counted the children to 9 against a
6480
+ * cap of 8, and the post-acceptance tail starved on the counter
6481
+ * while the synthesis reserve's money sat whole: the judge declined
6482
+ * typed, the synthesis spawn refusal reached the terminal, and the
6483
+ * accepted dossier was lost. Each spawned agent counts a single
6484
+ * time across the run's whole life, never twice: at its fresh
6485
+ * admitSpawn, or through the seed of whichever segment rolls it
6486
+ * forward.
6474
6487
  */
6475
6488
  admitRecovered(reserveUsd: number, accountScope?: string): void;
6476
6489
  /**
@@ -13093,6 +13106,15 @@ interface PreflightOrchestratorSpec {
13093
13106
  estCost?: number;
13094
13107
  };
13095
13108
  };
13109
+ /**
13110
+ * The `reserve-line-headroom` threshold in coordination turn floors
13111
+ * (RV2201; previously hardwired to 2): the finding warns when the
13112
+ * admitted wave's steady state sits closer to the reserve line than
13113
+ * this many coordination turn floors. Raise it for waves whose
13114
+ * children routinely overrun their declared estimates; 0 silences
13115
+ * the finding entirely. Default 2.
13116
+ */
13117
+ headroomTurns?: number;
13096
13118
  }
13097
13119
  /** The full input: engine surface, run surface, and the declared wave. */
13098
13120
  interface PreflightInput {
package/dist/index.js CHANGED
@@ -13930,10 +13930,22 @@ var RunBudget = class {
13930
13930
  /**
13931
13931
  * Resume roll-forward: commits a reserve recovered from a journaled
13932
13932
  * spawn-admission decision entry without re-evaluating admission
13933
- * (reserves are recovered, never re-estimated).
13933
+ * (reserves are recovered, never re-estimated). The lifetime spawn
13934
+ * counter does NOT increment here (RV2201): every agent the
13935
+ * roll-forward re-covers already counted through the resume seed,
13936
+ * whose journal fold counts each dispatched agent entry, so an
13937
+ * incrementing roll-forward double-counts every recovered child.
13938
+ * The seventh subscription parity run resumed a killed 4-child
13939
+ * fan-out into a seed of 5, re-counted the children to 9 against a
13940
+ * cap of 8, and the post-acceptance tail starved on the counter
13941
+ * while the synthesis reserve's money sat whole: the judge declined
13942
+ * typed, the synthesis spawn refusal reached the terminal, and the
13943
+ * accepted dossier was lost. Each spawned agent counts a single
13944
+ * time across the run's whole life, never twice: at its fresh
13945
+ * admitSpawn, or through the seed of whichever segment rolls it
13946
+ * forward.
13934
13947
  */
13935
13948
  admitRecovered(reserveUsd, accountScope = "run") {
13936
- this.agentsSpawnedInternal += 1;
13937
13949
  for (const account of this.chainOf(accountScope)) account.committedReserveUsd += reserveUsd;
13938
13950
  this.emitUpdate();
13939
13951
  }
@@ -24068,6 +24080,26 @@ function makeOrchestratorWorkflow(goal, opts) {
24068
24080
  * rejection-past-the-bound error keeps its own
24069
24081
  * repairsUsed/maxRepairs/failed untouched.
24070
24082
  */
24083
+ const journalSynthesisAdmissionDecline = async (thrown) => {
24084
+ if (!(thrown instanceof BudgetExhaustedError)) return;
24085
+ const declineKey = deriverV2.deriveKey({ kind: "orchestrator-synthesis-redemption-declined" });
24086
+ if (internals.replayer.snapshot().some((entry) => entry.kind === "decision" && entry.key === declineKey)) return;
24087
+ await internals.replayer.appendSinglePhase({
24088
+ scope: callingState.scope,
24089
+ key: declineKey,
24090
+ kind: "decision",
24091
+ status: "ok",
24092
+ spanId: internals.spans.mint(callingState.spanId),
24093
+ site: "orchestrator-budget",
24094
+ value: {
24095
+ decisionType: "orchestrator_synthesis_redemption_declined",
24096
+ reason: thrown.message.slice(0, 300),
24097
+ remainingUsd: internals.budget.remainingUsd() ?? null,
24098
+ spawnHeadroom: internals.budget.spawnHeadroom,
24099
+ path: "accepted-finish"
24100
+ }
24101
+ });
24102
+ };
24071
24103
  const enrichSynthesisFailure = (thrown, snapshot) => {
24072
24104
  if (!(thrown instanceof FailRunError)) throw thrown;
24073
24105
  const base = thrown.data ?? {};
@@ -24096,6 +24128,7 @@ function makeOrchestratorWorkflow(goal, opts) {
24096
24128
  try {
24097
24129
  return await runSynthesis(result.output);
24098
24130
  } catch (thrown) {
24131
+ await journalSynthesisAdmissionDecline(thrown);
24099
24132
  return enrichSynthesisFailure(thrown);
24100
24133
  }
24101
24134
  }
@@ -24302,6 +24335,7 @@ function makeOrchestratorWorkflow(goal, opts) {
24302
24335
  try {
24303
24336
  synthesizedFinal = await runSynthesis(result.output);
24304
24337
  } catch (thrown) {
24338
+ await journalSynthesisAdmissionDecline(thrown);
24305
24339
  enrichSynthesisFailure(thrown, {
24306
24340
  completion: decision.completion,
24307
24341
  childStatusCounts: decision.childStatusCounts,
@@ -25093,10 +25127,11 @@ function preflightEstimate(input) {
25093
25127
  const requiredMinimumCeilingUsd = wave.length === 0 ? void 0 : wave.reduce((sum, row) => sum + row.reserveUsd, 0) + reservedForFinalizationUsd + synthesisHoldUsd + liveRootExposureTermUsd;
25094
25128
  const reserveLineUsd = ceilingUsd === void 0 || synthesisHoldUsd <= 0 ? void 0 : ceilingUsd - synthesisHoldUsd;
25095
25129
  const reserveLineHeadroomUsd = reserveLineUsd === void 0 ? void 0 : reserveLineUsd - wave.filter((row) => row.admitted).reduce((sum, row) => sum + row.reserveUsd, 0);
25096
- if (orchestrateWave && reserveLineUsd !== void 0 && reserveLineHeadroomUsd !== void 0 && wave.some((row) => row.admitted) && reserveLineHeadroomUsd < 2 * Math.max(liveRootExposureTermUsd, 1e-4)) say({
25130
+ const headroomTurns = input.orchestrator?.headroomTurns ?? 2;
25131
+ if (orchestrateWave && reserveLineUsd !== void 0 && reserveLineHeadroomUsd !== void 0 && wave.some((row) => row.admitted) && headroomTurns > 0 && reserveLineHeadroomUsd < headroomTurns * Math.max(liveRootExposureTermUsd, 1e-4)) say({
25097
25132
  severity: "warning",
25098
25133
  code: "reserve-line-headroom",
25099
- message: `the admitted wave's steady state sits ${reserveLineHeadroomUsd.toFixed(4)} USD under the reserve line ${reserveLineUsd.toFixed(4)} USD (the ceiling minus the synthesis reserve), less than two coordination turn floors of headroom (${liveRootExposureTermUsd.toFixed(4)} USD each): child spend past the declared estimates eats that headroom, the coordination loop is then refused at the line, and the run settles partial with the synthesis redeemed from its reserve (RV2101); size the wave below the line or raise the ceiling to keep coordinating past it`
25134
+ message: `the admitted wave's steady state sits ${reserveLineHeadroomUsd.toFixed(4)} USD under the reserve line ${reserveLineUsd.toFixed(4)} USD (the ceiling minus the synthesis reserve), less than ${String(headroomTurns)} coordination turn floors of headroom (${liveRootExposureTermUsd.toFixed(4)} USD each): child spend past the declared estimates eats that headroom, the coordination loop is then refused at the line, and the run settles partial with the synthesis redeemed from its reserve (RV2101); size the wave below the line or raise the ceiling to keep coordinating past it`
25100
25135
  });
25101
25136
  {
25102
25137
  const judgeEstUsd = input.orchestrator?.claimConsistency?.judge?.estCost;
@@ -25109,6 +25144,21 @@ function preflightEstimate(input) {
25109
25144
  });
25110
25145
  }
25111
25146
  }
25147
+ if (orchestrateWave && wave.length > 0 && admitted === wave.length) {
25148
+ const lifetimeSpawnCap = input.engine?.budgetDefaults?.lifetimeSpawnCap ?? 500;
25149
+ const judgeDeclared = input.orchestrator?.claimConsistency?.judge !== void 0;
25150
+ const synthesisDeclared = input.orchestrator?.synthesis !== void 0;
25151
+ const plannedSpawns = wave.length + (judgeDeclared ? 1 : 0) + (synthesisDeclared ? 1 : 0);
25152
+ const spawnHeadroom = lifetimeSpawnCap - plannedSpawns;
25153
+ if (spawnHeadroom <= 0) {
25154
+ const breakdown = `the admitted wave of ${String(wave.length)} agent invocations` + (judgeDeclared ? " plus the claim-consistency judge" : "") + (synthesisDeclared ? " plus the synthesis invocation" : "") + ` is ${String(plannedSpawns)} against budgetDefaults.lifetimeSpawnCap ` + String(lifetimeSpawnCap);
25155
+ say({
25156
+ severity: "warning",
25157
+ code: "tail-spawn-budget",
25158
+ message: spawnHeadroom < 0 ? breakdown + ": the post-fan-in tail starves on the counter with its money whole: the judge admission declines typed (RV2106) and the synthesis spawn journals its declined verdict (RV2201) instead of composing; raise the cap to seat the declared tail" : breakdown + ": an exact fill leaves zero spawn headroom, so nothing the plan did not name (a re-spawned child, a second pass) can ever be admitted; raise the cap for margin"
25159
+ });
25160
+ }
25161
+ }
25112
25162
  if (wave.length > 0 && denied > 0) {
25113
25163
  const deniedLabels = wave.filter((row) => !row.admitted).map((row) => row.label);
25114
25164
  if (admitted === 0) say({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/core",
3
- "version": "1.217.0",
3
+ "version": "1.218.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",