@rulvar/core 1.86.0 → 1.87.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 +48 -4
- package/dist/index.js +234 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -3579,6 +3579,11 @@ interface ToolBudgetSummary {
|
|
|
3579
3579
|
noticesFired?: number[];
|
|
3580
3580
|
/** Present and true when the finalization reserve summary turn ran. */
|
|
3581
3581
|
finalizationReserveUsed?: boolean;
|
|
3582
|
+
/**
|
|
3583
|
+
* Present and true when the finalization window activated at least
|
|
3584
|
+
* once this invocation (RV302).
|
|
3585
|
+
*/
|
|
3586
|
+
finalizationWindowEntered?: boolean;
|
|
3582
3587
|
/** The tool budget limiter that ended the loop, on that 'limit' only. */
|
|
3583
3588
|
limiter?: "maxToolCalls" | "toolUnits";
|
|
3584
3589
|
}
|
|
@@ -3710,11 +3715,12 @@ type ToolEvents = {
|
|
|
3710
3715
|
rule?: Json;
|
|
3711
3716
|
advisory?: Json;
|
|
3712
3717
|
/**
|
|
3713
|
-
* Present when an
|
|
3714
|
-
*
|
|
3715
|
-
*
|
|
3718
|
+
* Present when an engine guard, not the permission chain, denied
|
|
3719
|
+
* the call: the exploration guards (RV-210) or the finalization
|
|
3720
|
+
* window (RV302). The outcome is 'denied' and the call was never
|
|
3721
|
+
* dispatched.
|
|
3716
3722
|
*/
|
|
3717
|
-
guard?: "repeated-signature";
|
|
3723
|
+
guard?: "repeated-signature" | "per-tool-cap" | "finalization-window";
|
|
3718
3724
|
};
|
|
3719
3725
|
/**
|
|
3720
3726
|
* Bare-nondeterminism detection (RV-209). Emitted LIVE by the segment
|
|
@@ -4106,6 +4112,27 @@ interface UsageLimits {
|
|
|
4106
4112
|
minHeadroomUsd?: number; /** Default true: a grant needs new evidence since the last one. */
|
|
4107
4113
|
requireNewEvidence?: boolean;
|
|
4108
4114
|
};
|
|
4115
|
+
/**
|
|
4116
|
+
* The finalization window (RV302, the seventh comparison experiment):
|
|
4117
|
+
* once the remaining tool budget (executed calls against the effective
|
|
4118
|
+
* maxToolCalls, or remaining weighted units against toolUnits.max,
|
|
4119
|
+
* whichever is closer) drops to `reserveCalls`, only finalization
|
|
4120
|
+
* tools may execute. A call outside the window's allowlist receives a
|
|
4121
|
+
* typed error tool result naming the window (visible to the model,
|
|
4122
|
+
* never terminal, consuming no budget), and the model is told ONCE,
|
|
4123
|
+
* via a plain user message, to record its evidence and finish. The
|
|
4124
|
+
* allowlist defaults to the tools priced at toolUnits cost 0 (the
|
|
4125
|
+
* free bookkeeping tools); the engine terminal tool is always
|
|
4126
|
+
* admitted regardless. With toolBudgetExtension configured, remaining
|
|
4127
|
+
* money converts into a grant BEFORE any window refusal, so the
|
|
4128
|
+
* window binds only when the extension is exhausted or denied. Off by
|
|
4129
|
+
* default: the refusals and the notice enter the conversation, so
|
|
4130
|
+
* enabling it changes recorded model requests.
|
|
4131
|
+
*/
|
|
4132
|
+
finalizationWindow?: {
|
|
4133
|
+
/** How many trailing executed calls (or units) the window reserves. */reserveCalls: number; /** Tool names allowed inside the window; default: zero-cost tools. */
|
|
4134
|
+
allow?: string[];
|
|
4135
|
+
};
|
|
4109
4136
|
}
|
|
4110
4137
|
declare const DEFAULT_MAX_TURNS = 32;
|
|
4111
4138
|
declare const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 12e4;
|
|
@@ -4135,6 +4162,10 @@ interface EffectiveUsageLimits {
|
|
|
4135
4162
|
minHeadroomUsd?: number;
|
|
4136
4163
|
requireNewEvidence?: boolean;
|
|
4137
4164
|
};
|
|
4165
|
+
finalizationWindow?: {
|
|
4166
|
+
reserveCalls: number;
|
|
4167
|
+
allow?: string[];
|
|
4168
|
+
};
|
|
4138
4169
|
}
|
|
4139
4170
|
/**
|
|
4140
4171
|
* Limits merge per spawn: AgentOpts.limits over profile limits over engine
|
|
@@ -9234,6 +9265,19 @@ interface PreflightOrchestratorSpec {
|
|
|
9234
9265
|
*/
|
|
9235
9266
|
extension?: boolean;
|
|
9236
9267
|
/**
|
|
9268
|
+
* The OrchestrateAcceptance slice the estimator judges (RV305):
|
|
9269
|
+
* declaring it lets preflight relate capped children to the salvage
|
|
9270
|
+
* arms. Absent, the salvage findings stay silent, exactly like every
|
|
9271
|
+
* other undeclared input.
|
|
9272
|
+
*/
|
|
9273
|
+
acceptance?: {
|
|
9274
|
+
childPolicy?: "all-ok" | {
|
|
9275
|
+
minSuccessful: number;
|
|
9276
|
+
};
|
|
9277
|
+
acceptPartialChildren?: boolean;
|
|
9278
|
+
acceptValidatedTerminalOutputOnLimit?: boolean;
|
|
9279
|
+
};
|
|
9280
|
+
/**
|
|
9237
9281
|
* The separate synthesis invocation (RV-211), when the orchestration
|
|
9238
9282
|
* configures one (the v1.71 experiment review: the run ceiling used
|
|
9239
9283
|
* to stop at the coordination loop, undercounting the synthesis
|
package/dist/index.js
CHANGED
|
@@ -9023,6 +9023,8 @@ function mergeUsageLimits(call, profile, engine) {
|
|
|
9023
9023
|
if (finalizationReserve !== void 0) merged.finalizationReserve = finalizationReserve;
|
|
9024
9024
|
const toolBudgetExtension = pick("toolBudgetExtension");
|
|
9025
9025
|
if (toolBudgetExtension !== void 0) merged.toolBudgetExtension = toolBudgetExtension;
|
|
9026
|
+
const finalizationWindow = pick("finalizationWindow");
|
|
9027
|
+
if (finalizationWindow !== void 0) merged.finalizationWindow = finalizationWindow;
|
|
9026
9028
|
return merged;
|
|
9027
9029
|
}
|
|
9028
9030
|
/**
|
|
@@ -9077,6 +9079,16 @@ function validateUsageLimits(limits, site) {
|
|
|
9077
9079
|
if (minHeadroomUsd !== void 0 && (typeof minHeadroomUsd !== "number" || !Number.isFinite(minHeadroomUsd) || minHeadroomUsd < 0)) throw new ConfigError(`${site}.toolBudgetExtension.minHeadroomUsd must be a finite nonnegative USD amount, got ${typeof minHeadroomUsd === "number" ? String(minHeadroomUsd) : typeof minHeadroomUsd}`);
|
|
9078
9080
|
if (requireNewEvidence !== void 0 && typeof requireNewEvidence !== "boolean") throw new ConfigError(`${site}.toolBudgetExtension.requireNewEvidence must be a boolean; got ${typeof requireNewEvidence}`);
|
|
9079
9081
|
}
|
|
9082
|
+
if (limits.finalizationWindow !== void 0) {
|
|
9083
|
+
const window = limits.finalizationWindow;
|
|
9084
|
+
if (typeof window !== "object" || window === null || Array.isArray(window)) throw new ConfigError(`${site}.finalizationWindow must be { reserveCalls, allow? }`);
|
|
9085
|
+
const { reserveCalls, allow } = window;
|
|
9086
|
+
requirePositiveInteger(reserveCalls, `${site}.finalizationWindow.reserveCalls`);
|
|
9087
|
+
if (allow !== void 0) {
|
|
9088
|
+
if (!Array.isArray(allow)) throw new ConfigError(`${site}.finalizationWindow.allow must be an array of tool names`);
|
|
9089
|
+
for (const [index, name] of allow.entries()) if (typeof name !== "string" || name.length === 0) throw new ConfigError(`${site}.finalizationWindow.allow[${String(index)}] must be a nonempty tool name`);
|
|
9090
|
+
}
|
|
9091
|
+
}
|
|
9080
9092
|
}
|
|
9081
9093
|
//#endregion
|
|
9082
9094
|
//#region src/model/failover.ts
|
|
@@ -9657,6 +9669,8 @@ const DEFAULT_MODEL_RETRY_ATTEMPTS = 2;
|
|
|
9657
9669
|
*/
|
|
9658
9670
|
/** The docs anchor cited by guard denials and the guard abort. */
|
|
9659
9671
|
const GUARD_DOCS_URL = "https://docs.rulvar.com/guide/agents#exploration-guards";
|
|
9672
|
+
/** The docs anchor cited by finalization window refusals (RV302). */
|
|
9673
|
+
const WINDOW_DOCS_URL = "https://docs.rulvar.com/guide/agents#the-finalization-window";
|
|
9660
9674
|
/**
|
|
9661
9675
|
* True when any exploration guard field asks for tracking. The tool
|
|
9662
9676
|
* budget extension (RV301) counts too: its requireNewEvidence admission
|
|
@@ -9791,6 +9805,15 @@ var ExplorationGuard = class {
|
|
|
9791
9805
|
return this.config.toolUnits !== void 0 && this.unitsUsed >= this.config.toolUnits.max;
|
|
9792
9806
|
}
|
|
9793
9807
|
/**
|
|
9808
|
+
* Weighted units still spendable, floored at zero; undefined without
|
|
9809
|
+
* toolUnits configured. The finalization window (RV302) reads this on
|
|
9810
|
+
* every call evaluation, so it stays an O(1) counter, unlike the
|
|
9811
|
+
* allocating summary().
|
|
9812
|
+
*/
|
|
9813
|
+
unitsRemaining() {
|
|
9814
|
+
return this.config.toolUnits === void 0 ? void 0 : Math.max(0, this.config.toolUnits.max - this.unitsUsed);
|
|
9815
|
+
}
|
|
9816
|
+
/**
|
|
9794
9817
|
* Distinct successful result digests seen this invocation: the
|
|
9795
9818
|
* extension's requireNewEvidence admission compares snapshots of this
|
|
9796
9819
|
* count (RV301). A result JCS cannot digest never counts, so a grant
|
|
@@ -9847,6 +9870,21 @@ function toolBudgetExtensionNoticeText(grant, maxExtensions, used, cap) {
|
|
|
9847
9870
|
const remaining = Math.max(0, cap - used);
|
|
9848
9871
|
return `Tool budget extended: grant ${String(grant)} of ${String(maxExtensions)}; ${String(used)} of ${String(cap)} tool calls used, ${String(remaining)} remaining. The budget headroom permits continued work; prioritize the highest value calls.`;
|
|
9849
9872
|
}
|
|
9873
|
+
/**
|
|
9874
|
+
* The one-time model-visible window notice (RV302). Deterministic for
|
|
9875
|
+
* given counts, like the notices above.
|
|
9876
|
+
*/
|
|
9877
|
+
function finalizationWindowNoticeText(remaining, reserve, budget) {
|
|
9878
|
+
return `Finalization window: ${String(Math.max(0, remaining))} of the reserved final ${String(reserve)} ${budget} remain. Only finalization tools (and the terminal tool) may execute now; record your evidence and finish with what you have.`;
|
|
9879
|
+
}
|
|
9880
|
+
/**
|
|
9881
|
+
* The typed window refusal a non-allowlisted call receives (RV302):
|
|
9882
|
+
* the same posture as the guard denials above, visible to the model,
|
|
9883
|
+
* never terminal, consuming no budget.
|
|
9884
|
+
*/
|
|
9885
|
+
function finalizationWindowRefusalText(name, reserve, budget) {
|
|
9886
|
+
return `finalization window: the last ${String(reserve)} ${budget} are reserved for finalization tools, and '${name}' is not in the window allowlist. Record your evidence with the allowed tools and call the terminal tool (${WINDOW_DOCS_URL}).`;
|
|
9887
|
+
}
|
|
9850
9888
|
//#endregion
|
|
9851
9889
|
//#region src/runtime/no-progress.ts
|
|
9852
9890
|
/**
|
|
@@ -10730,6 +10768,77 @@ async function runAgent(options) {
|
|
|
10730
10768
|
}]
|
|
10731
10769
|
});
|
|
10732
10770
|
};
|
|
10771
|
+
/**
|
|
10772
|
+
* The finalization window (RV302): once the remaining tool budget
|
|
10773
|
+
* drops to reserveCalls, only the allowlisted finalization tools (and
|
|
10774
|
+
* the always-admitted terminal tool) execute; everything else gets a
|
|
10775
|
+
* typed refusal that consumes nothing. The run that motivated it
|
|
10776
|
+
* recorded 10 of 14 evidence entries before the cap: one summary turn
|
|
10777
|
+
* cannot dump a backlog, a reserved tail of bookkeeping calls can.
|
|
10778
|
+
*/
|
|
10779
|
+
const finalizationWindow = limits.finalizationWindow;
|
|
10780
|
+
let windowEntered = false;
|
|
10781
|
+
let windowNoticeFired = false;
|
|
10782
|
+
const pendingWindowNotices = [];
|
|
10783
|
+
/** The tightest remaining budget and which dimension provides it. */
|
|
10784
|
+
const windowRemaining = () => {
|
|
10785
|
+
let best;
|
|
10786
|
+
const cap = effectiveMaxToolCalls();
|
|
10787
|
+
if (cap !== void 0) best = {
|
|
10788
|
+
remaining: Math.max(0, cap - toolCallsUsed),
|
|
10789
|
+
budget: "tool calls"
|
|
10790
|
+
};
|
|
10791
|
+
const units = guard?.unitsRemaining();
|
|
10792
|
+
if (units !== void 0 && (best === void 0 || units < best.remaining)) best = {
|
|
10793
|
+
remaining: units,
|
|
10794
|
+
budget: "tool units"
|
|
10795
|
+
};
|
|
10796
|
+
return best;
|
|
10797
|
+
};
|
|
10798
|
+
const windowActive = () => {
|
|
10799
|
+
if (finalizationWindow === void 0) return;
|
|
10800
|
+
const state = windowRemaining();
|
|
10801
|
+
return state !== void 0 && state.remaining <= finalizationWindow.reserveCalls ? state : void 0;
|
|
10802
|
+
};
|
|
10803
|
+
/**
|
|
10804
|
+
* Marks the entry and queues the one-time notice. Queued, not pushed:
|
|
10805
|
+
* a user message may not interleave a tool batch, so the queue
|
|
10806
|
+
* flushes with the other notices after the batch's results join the
|
|
10807
|
+
* history. On resume the flags re-derive from the restored counts and
|
|
10808
|
+
* the notice (already in the restored messages) never re-fires.
|
|
10809
|
+
*/
|
|
10810
|
+
const maybeMarkWindowEntry = () => {
|
|
10811
|
+
if (finalizationWindow === void 0 || windowNoticeFired) return;
|
|
10812
|
+
const state = windowActive();
|
|
10813
|
+
if (state === void 0) return;
|
|
10814
|
+
windowEntered = true;
|
|
10815
|
+
windowNoticeFired = true;
|
|
10816
|
+
pendingWindowNotices.push(finalizationWindowNoticeText(state.remaining, finalizationWindow.reserveCalls, state.budget));
|
|
10817
|
+
events?.emit({
|
|
10818
|
+
type: "log",
|
|
10819
|
+
level: "info",
|
|
10820
|
+
msg: `finalization window entered: ${String(state.remaining)} of the reserved final ${String(finalizationWindow.reserveCalls)} ${state.budget} remain`
|
|
10821
|
+
});
|
|
10822
|
+
};
|
|
10823
|
+
const flushWindowNotices = () => {
|
|
10824
|
+
for (const text of pendingWindowNotices.splice(0)) messages.push({
|
|
10825
|
+
role: "user",
|
|
10826
|
+
parts: [{
|
|
10827
|
+
type: "text",
|
|
10828
|
+
text
|
|
10829
|
+
}]
|
|
10830
|
+
});
|
|
10831
|
+
};
|
|
10832
|
+
/**
|
|
10833
|
+
* The window allowlist. The terminal and escalate tools never reach
|
|
10834
|
+
* this check: the dispatch walk intercepts both before the window
|
|
10835
|
+
* block, so the exits are structurally exempt rather than listed.
|
|
10836
|
+
*/
|
|
10837
|
+
const windowAllows = (name) => {
|
|
10838
|
+
const allow = finalizationWindow?.allow;
|
|
10839
|
+
if (allow !== void 0) return allow.includes(name);
|
|
10840
|
+
return limits.toolUnits?.costs?.[name] === 0;
|
|
10841
|
+
};
|
|
10733
10842
|
const modelRetryCounts = /* @__PURE__ */ new Map();
|
|
10734
10843
|
let lastTurnUsage = {
|
|
10735
10844
|
inputTokens: 0,
|
|
@@ -10766,6 +10875,10 @@ async function runAgent(options) {
|
|
|
10766
10875
|
extensionGrants = Math.min(extension.maxExtensions, Math.ceil((toolCallsUsed - limits.maxToolCalls) / extension.increment));
|
|
10767
10876
|
extensionEvidenceAtLastGrant = guard?.evidenceCount() ?? 0;
|
|
10768
10877
|
}
|
|
10878
|
+
if (windowActive() !== void 0) {
|
|
10879
|
+
windowEntered = true;
|
|
10880
|
+
windowNoticeFired = true;
|
|
10881
|
+
}
|
|
10769
10882
|
}
|
|
10770
10883
|
const usageSlices = () => [...usageByPhaseModel.values()].map(({ role, servedBy: sliceServedBy, usage }) => ({
|
|
10771
10884
|
servedBy: sliceServedBy,
|
|
@@ -11044,6 +11157,27 @@ async function runAgent(options) {
|
|
|
11044
11157
|
finished: finishArgs.result ?? null
|
|
11045
11158
|
};
|
|
11046
11159
|
}
|
|
11160
|
+
if (finalizationWindow !== void 0) {
|
|
11161
|
+
maybeMarkWindowEntry();
|
|
11162
|
+
let windowState = windowActive();
|
|
11163
|
+
if (windowState !== void 0 && !windowAllows(gatedCall.name)) {
|
|
11164
|
+
if (windowState.budget === "tool calls" && extension !== void 0 && windowState.remaining + extension.increment > finalizationWindow.reserveCalls && tryToolBudgetGrant()) windowState = windowActive();
|
|
11165
|
+
if (windowState !== void 0 && !windowAllows(gatedCall.name)) {
|
|
11166
|
+
events?.emit({
|
|
11167
|
+
type: "tool:end",
|
|
11168
|
+
toolName: gatedCall.name,
|
|
11169
|
+
outcome: "denied",
|
|
11170
|
+
durationMs: now() - gateStartedAt,
|
|
11171
|
+
guard: "finalization-window"
|
|
11172
|
+
});
|
|
11173
|
+
parts.push(errorPart(call, {
|
|
11174
|
+
error: finalizationWindowRefusalText(gatedCall.name, finalizationWindow.reserveCalls, windowState.budget),
|
|
11175
|
+
guard: "finalization-window"
|
|
11176
|
+
}));
|
|
11177
|
+
continue;
|
|
11178
|
+
}
|
|
11179
|
+
}
|
|
11180
|
+
}
|
|
11047
11181
|
if (guard !== void 0) {
|
|
11048
11182
|
const guardVerdict = guard.beforeExecute(gatedCall.name, gatedCall.args);
|
|
11049
11183
|
if (guardVerdict.deny) {
|
|
@@ -11082,6 +11216,7 @@ async function runAgent(options) {
|
|
|
11082
11216
|
};
|
|
11083
11217
|
}
|
|
11084
11218
|
}
|
|
11219
|
+
maybeMarkWindowEntry();
|
|
11085
11220
|
return {
|
|
11086
11221
|
parts,
|
|
11087
11222
|
limitHit: false
|
|
@@ -11133,6 +11268,7 @@ async function runAgent(options) {
|
|
|
11133
11268
|
}
|
|
11134
11269
|
} else {
|
|
11135
11270
|
flushExtensionNotices();
|
|
11271
|
+
flushWindowNotices();
|
|
11136
11272
|
maybePushBudgetNotice();
|
|
11137
11273
|
await saveBoundary();
|
|
11138
11274
|
}
|
|
@@ -11598,6 +11734,7 @@ async function runAgent(options) {
|
|
|
11598
11734
|
break;
|
|
11599
11735
|
}
|
|
11600
11736
|
flushExtensionNotices();
|
|
11737
|
+
flushWindowNotices();
|
|
11601
11738
|
maybePushBudgetNotice();
|
|
11602
11739
|
if (options.summarize !== void 0 && !compactionDisabled && shouldCompact({
|
|
11603
11740
|
lastTurnUsage,
|
|
@@ -12159,6 +12296,7 @@ async function runAgent(options) {
|
|
|
12159
12296
|
if (extension !== void 0) toolBudget.extensionsGranted = extensionGrants;
|
|
12160
12297
|
if (firedNotices.size > 0) toolBudget.noticesFired = [...firedNotices].sort((a, b) => a - b);
|
|
12161
12298
|
if (reserveSummaryRan) toolBudget.finalizationReserveUsed = true;
|
|
12299
|
+
if (windowEntered) toolBudget.finalizationWindowEntered = true;
|
|
12162
12300
|
if (limitLimiter !== void 0) toolBudget.limiter = limitLimiter;
|
|
12163
12301
|
result.toolBudget = toolBudget;
|
|
12164
12302
|
}
|
|
@@ -17810,6 +17948,18 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
17810
17948
|
* run falls back to the draft under a journaled decision and a warn
|
|
17811
17949
|
* log, never silently.
|
|
17812
17950
|
*/
|
|
17951
|
+
/**
|
|
17952
|
+
* The reserve lifecycle snapshot (RV304 second half, the seventh
|
|
17953
|
+
* comparison experiment review, P1.7): configured is the declared
|
|
17954
|
+
* hold, held what actually registered on the cap account (zero when
|
|
17955
|
+
* no cap resolved and the config was silently inert), released
|
|
17956
|
+
* what the synthesis dispatch freed, remainingBeforeSynthesisUsd
|
|
17957
|
+
* the chain headroom the invocation saw right after the release,
|
|
17958
|
+
* and consumedUsd its own priced spend. Frozen into a journaled
|
|
17959
|
+
* decision at first completion, so a resume reports the identical
|
|
17960
|
+
* facts; absent everywhere when no reserve is configured.
|
|
17961
|
+
*/
|
|
17962
|
+
let synthesisReserveLifecycle;
|
|
17813
17963
|
const runSynthesis = async (draft) => {
|
|
17814
17964
|
const spec = opts?.synthesis;
|
|
17815
17965
|
if (spec === void 0) return draft;
|
|
@@ -17872,11 +18022,14 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
17872
18022
|
...repeatedClaims === void 0 ? {} : { repeatedClaims: repeatedClaims.length }
|
|
17873
18023
|
}
|
|
17874
18024
|
}, callingState.spanId);
|
|
18025
|
+
const configuredReserveUsd = opts?.budget?.synthesisReserveUsd ?? 0;
|
|
18026
|
+
const heldReserveUsd = orchestratorAccount === void 0 ? 0 : internals.budget.accountView(orchestratorAccount)?.synthesisReserveUsd ?? 0;
|
|
17875
18027
|
const synthesisState = { ...callingState };
|
|
17876
18028
|
if (orchestratorAccount !== void 0) {
|
|
17877
18029
|
synthesisState.budgetScope = orchestratorAccount;
|
|
17878
18030
|
internals.budget.releaseSynthesisReserve(orchestratorAccount);
|
|
17879
18031
|
}
|
|
18032
|
+
const remainingBeforeSynthesisUsd = configuredReserveUsd > 0 ? internals.budget.remainingUsd(orchestratorAccount ?? callingState.budgetScope ?? void 0) : void 0;
|
|
17880
18033
|
const synthesisBreak = validationSpec === void 0 ? void 0 : validationAbort.signal;
|
|
17881
18034
|
if (synthesisBreak !== void 0) synthesisState.signal = callingState.signal === void 0 ? synthesisBreak : AbortSignal.any([callingState.signal, synthesisBreak]);
|
|
17882
18035
|
const synthesisOpts = {
|
|
@@ -17899,6 +18052,46 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
17899
18052
|
synthesisSchemaRejectedExchanges = synthesized.schemaRejectedTerminalExchanges ?? 0;
|
|
17900
18053
|
synthesisSchemaRecoveredExchanges = synthesized.schemaRecoveredTerminalExchanges ?? 0;
|
|
17901
18054
|
if (validationTermination !== void 0) throw validationTermination;
|
|
18055
|
+
if (configuredReserveUsd > 0) {
|
|
18056
|
+
const reserveKey = "synthesis-reserve-lifecycle";
|
|
18057
|
+
const prior = internals.replayer.snapshot().find((entry) => entry.kind === "decision" && entry.scope === callingState.scope && entry.key === reserveKey);
|
|
18058
|
+
if (prior !== void 0) {
|
|
18059
|
+
const frozen = prior.value;
|
|
18060
|
+
synthesisReserveLifecycle = {
|
|
18061
|
+
configuredUsd: frozen.configuredUsd,
|
|
18062
|
+
heldUsd: frozen.heldUsd,
|
|
18063
|
+
releasedUsd: frozen.releasedUsd,
|
|
18064
|
+
...frozen.remainingBeforeSynthesisUsd === void 0 ? {} : { remainingBeforeSynthesisUsd: frozen.remainingBeforeSynthesisUsd },
|
|
18065
|
+
...frozen.consumedUsd === void 0 ? {} : { consumedUsd: frozen.consumedUsd }
|
|
18066
|
+
};
|
|
18067
|
+
} else {
|
|
18068
|
+
synthesisReserveLifecycle = {
|
|
18069
|
+
configuredUsd: configuredReserveUsd,
|
|
18070
|
+
heldUsd: heldReserveUsd,
|
|
18071
|
+
releasedUsd: heldReserveUsd,
|
|
18072
|
+
...remainingBeforeSynthesisUsd === void 0 ? {} : { remainingBeforeSynthesisUsd },
|
|
18073
|
+
consumedUsd: synthesized.costUsd
|
|
18074
|
+
};
|
|
18075
|
+
await internals.replayer.appendSinglePhase({
|
|
18076
|
+
scope: callingState.scope,
|
|
18077
|
+
key: reserveKey,
|
|
18078
|
+
kind: "decision",
|
|
18079
|
+
status: "ok",
|
|
18080
|
+
spanId: internals.spans.mint(callingState.spanId),
|
|
18081
|
+
site: "orchestrator-synthesis-reserve",
|
|
18082
|
+
value: {
|
|
18083
|
+
decisionType: "orchestrator_synthesis_reserve",
|
|
18084
|
+
...synthesisReserveLifecycle
|
|
18085
|
+
}
|
|
18086
|
+
});
|
|
18087
|
+
}
|
|
18088
|
+
internals.events.emit({
|
|
18089
|
+
type: "log",
|
|
18090
|
+
level: "info",
|
|
18091
|
+
msg: "orchestrator synthesis reserve lifecycle",
|
|
18092
|
+
data: { ...synthesisReserveLifecycle }
|
|
18093
|
+
}, callingState.spanId);
|
|
18094
|
+
}
|
|
17902
18095
|
if (synthesized.status === "ok") return synthesized.output;
|
|
17903
18096
|
if (validationSpec !== void 0) throw new FailRunError(`the synthesis invocation terminated with status '${synthesized.status}'` + (synthesized.errorMessage === void 0 ? "" : `: ${synthesized.errorMessage}`) + "; finish validators are configured, so the unvalidated draft cannot stand", { data: {
|
|
17904
18097
|
source: "orchestrator_synthesis",
|
|
@@ -18109,7 +18302,8 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
18109
18302
|
degradedReasons: decision.degradedReasons,
|
|
18110
18303
|
...decision.salvagedPartialChildren === void 0 ? {} : { salvagedPartialChildren: decision.salvagedPartialChildren },
|
|
18111
18304
|
...decision.salvagedTerminalOutputChildren === void 0 ? {} : { salvagedTerminalOutputChildren: decision.salvagedTerminalOutputChildren },
|
|
18112
|
-
...envelopeSchemaRecovered === 0 ? {} : { schemaRecoveredFinishExchanges: envelopeSchemaRecovered }
|
|
18305
|
+
...envelopeSchemaRecovered === 0 ? {} : { schemaRecoveredFinishExchanges: envelopeSchemaRecovered },
|
|
18306
|
+
...synthesisReserveLifecycle === void 0 ? {} : { synthesisReserve: synthesisReserveLifecycle }
|
|
18113
18307
|
};
|
|
18114
18308
|
});
|
|
18115
18309
|
}
|
|
@@ -18337,6 +18531,8 @@ function preflightEstimate(input) {
|
|
|
18337
18531
|
const waveGateInputs = [];
|
|
18338
18532
|
const units = [];
|
|
18339
18533
|
const shapes = [];
|
|
18534
|
+
/** Whether any declared spawn caps its tool budget (RV305). */
|
|
18535
|
+
let anyCappedSpawn = false;
|
|
18340
18536
|
for (const spec of spawnSpecs) {
|
|
18341
18537
|
const role = spec.role ?? "loop";
|
|
18342
18538
|
const label = spec.label ?? role;
|
|
@@ -18460,6 +18656,37 @@ function preflightEstimate(input) {
|
|
|
18460
18656
|
message: `spawn '${label}' sets toolBudgetNotices without maxToolCalls: the notices never fire`,
|
|
18461
18657
|
spawn: label
|
|
18462
18658
|
});
|
|
18659
|
+
if (limits.finalizationWindow !== void 0) if (limits.maxToolCalls === void 0 && limits.toolUnits === void 0) say({
|
|
18660
|
+
severity: "warning",
|
|
18661
|
+
code: "inert-finalization-window",
|
|
18662
|
+
message: `spawn '${label}' sets finalizationWindow without maxToolCalls or toolUnits: no tool budget exists for the window to reserve a tail of`,
|
|
18663
|
+
spawn: label
|
|
18664
|
+
});
|
|
18665
|
+
else {
|
|
18666
|
+
const reserve = limits.finalizationWindow.reserveCalls;
|
|
18667
|
+
const windowCallCap = extendedMaxToolCalls(limits);
|
|
18668
|
+
const unitsMax = limits.toolUnits?.max;
|
|
18669
|
+
if (windowCallCap !== void 0 && reserve >= windowCallCap || unitsMax !== void 0 && reserve >= unitsMax) say({
|
|
18670
|
+
severity: "warning",
|
|
18671
|
+
code: "finalization-window-covers-cap",
|
|
18672
|
+
message: `spawn '${label}': finalizationWindow.reserveCalls ${String(reserve)} is not below the tool budget, so the window governs from the very first call and nothing but the allowlisted finalization tools ever executes`,
|
|
18673
|
+
spawn: label
|
|
18674
|
+
});
|
|
18675
|
+
if (limits.finalizationWindow.allow !== void 0 && limits.finalizationWindow.allow.length === 0) say({
|
|
18676
|
+
severity: "warning",
|
|
18677
|
+
code: "finalization-window-empty-allowlist",
|
|
18678
|
+
message: `spawn '${label}': finalizationWindow.allow is empty, so inside the window only the engine terminal tool (when one exists) remains callable and every other call is refused`,
|
|
18679
|
+
spawn: label
|
|
18680
|
+
});
|
|
18681
|
+
}
|
|
18682
|
+
const positiveCallCap = limits.maxToolCalls !== void 0 && limits.maxToolCalls > 0;
|
|
18683
|
+
if ((positiveCallCap || limits.toolUnits !== void 0) && limits.toolBudgetNotices !== true && limits.finalizationReserve === void 0 && limits.toolBudgetExtension === void 0 && limits.finalizationWindow === void 0) say({
|
|
18684
|
+
severity: "warning",
|
|
18685
|
+
code: "bare-tool-cap",
|
|
18686
|
+
message: `spawn '${label}' caps its tool budget (${positiveCallCap ? `maxToolCalls ${String(limits.maxToolCalls)}` : `toolUnits.max ${String(limits.toolUnits?.max ?? 0)}`}) with no softener: no toolBudgetNotices, no toolBudgetExtension, no finalizationReserve, no finalizationWindow. Expiry is a silent hard 'limit' the model never saw coming; enable a notice or a reserve, or drop the cap and rely on the USD ceiling`,
|
|
18687
|
+
spawn: label
|
|
18688
|
+
});
|
|
18689
|
+
if (positiveCallCap || limits.toolUnits !== void 0) anyCappedSpawn = true;
|
|
18463
18690
|
if (unpriced && ceilingUsd !== void 0) say({
|
|
18464
18691
|
severity: "warning",
|
|
18465
18692
|
code: "unpriced-under-ceiling",
|
|
@@ -18519,6 +18746,12 @@ function preflightEstimate(input) {
|
|
|
18519
18746
|
}
|
|
18520
18747
|
let orchestratorReserveUsd;
|
|
18521
18748
|
if (input.orchestrator !== void 0) {
|
|
18749
|
+
const acceptance = input.orchestrator.acceptance;
|
|
18750
|
+
if (acceptance !== void 0 && anyCappedSpawn && acceptance.acceptPartialChildren !== true && acceptance.acceptValidatedTerminalOutputOnLimit !== true) say({
|
|
18751
|
+
severity: "info",
|
|
18752
|
+
code: "capped-children-without-salvage",
|
|
18753
|
+
message: "children in the declared wave cap their tool budgets and the declared acceptance policy enables no salvage arm (acceptPartialChildren, acceptValidatedTerminalOutputOnLimit): a child that expires settles limit and counts against the policy with nothing to salvage"
|
|
18754
|
+
});
|
|
18522
18755
|
const servedBy = resolveServing(defaults.routing?.orchestrate);
|
|
18523
18756
|
if (servedBy === void 0) say({
|
|
18524
18757
|
severity: "error",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.87.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",
|