@rulvar/core 1.75.1 → 1.77.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 +85 -2
- package/dist/index.js +199 -29
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -4197,6 +4197,16 @@ interface AgentResult<T> {
|
|
|
4197
4197
|
* seeing a bare 'terminal status limit'.
|
|
4198
4198
|
*/
|
|
4199
4199
|
partial?: ProgressReport;
|
|
4200
|
+
/**
|
|
4201
|
+
* Terminal-tool exchanges whose ARGUMENTS died at the schema gate
|
|
4202
|
+
* (the unparsed second chance included, when it did not recover): the
|
|
4203
|
+
* v1.74 experiment lost six finish payloads to exactly this class,
|
|
4204
|
+
* and nothing outside the transcript said so (host validation
|
|
4205
|
+
* rejections, by contrast, journal decision entries). Derived from
|
|
4206
|
+
* the message window like the repair-reserve grants, so live and
|
|
4207
|
+
* resumed segments count the same total; absent when zero.
|
|
4208
|
+
*/
|
|
4209
|
+
schemaRejectedTerminalExchanges?: number;
|
|
4200
4210
|
}
|
|
4201
4211
|
/** One 429's provider-normalized limits, per (provider, model). */
|
|
4202
4212
|
interface RateLimitObservation {
|
|
@@ -7126,6 +7136,30 @@ interface FinishValidationSpec {
|
|
|
7126
7136
|
*/
|
|
7127
7137
|
repairTurnReserve?: number;
|
|
7128
7138
|
/**
|
|
7139
|
+
* The coordination draft gate (the v1.74 experiment review, P0.3),
|
|
7140
|
+
* meaningful ONLY with `synthesis` configured: with validators bound
|
|
7141
|
+
* to the synthesis finish, the coordination finish is an unvalidated
|
|
7142
|
+
* draft, and the experiment's model escaped six failed finish
|
|
7143
|
+
* exchanges with the schema-valid draft 'test', which then starved
|
|
7144
|
+
* synthesis of every citation the validators demanded. The policy
|
|
7145
|
+
* runs deterministic library checks on each coordination finish
|
|
7146
|
+
* (whitespace-token `minWords`, literal `requireSections` markers,
|
|
7147
|
+
* the wordCountValidator and requiredSectionsValidator semantics);
|
|
7148
|
+
* a failing draft returns to the model as the finish call's error
|
|
7149
|
+
* result and the turn continues, exactly like a host validation
|
|
7150
|
+
* rejection, and `repairTurnReserve` grants coordination the same
|
|
7151
|
+
* per-rejected-exchange headroom it grants the synthesis finish.
|
|
7152
|
+
* Pure text checks over the durable exchange: nothing journals, a
|
|
7153
|
+
* resumed segment recounts identically, and `maxRepairs` is not
|
|
7154
|
+
* consumed (it belongs to the synthesis-bound validators). Absent =
|
|
7155
|
+
* byte identical pre 1.76 behavior; configured without `synthesis` =
|
|
7156
|
+
* ConfigError.
|
|
7157
|
+
*/
|
|
7158
|
+
draftPolicy?: {
|
|
7159
|
+
/** Minimum whitespace-separated words the draft must carry. */minWords?: number; /** Literal markers the draft text must contain. */
|
|
7160
|
+
requireSections?: string[];
|
|
7161
|
+
};
|
|
7162
|
+
/**
|
|
7129
7163
|
* The unified output contract this validator set enforces (the v1.71
|
|
7130
7164
|
* experiment review, P0.1/P0.2). Construction then runs the golden
|
|
7131
7165
|
* self test with the contract's fixtures as defaults, the contract's
|
|
@@ -7138,8 +7172,17 @@ interface FinishValidationSpec {
|
|
|
7138
7172
|
* hash and the validator names. A resumed segment whose live
|
|
7139
7173
|
* contract hash differs appends a SUPERSEDING descriptor instead of
|
|
7140
7174
|
* failing, because fixing a stale validator and resuming is the
|
|
7141
|
-
* intended remedy, never a fault.
|
|
7142
|
-
*
|
|
7175
|
+
* intended remedy, never a fault. The remedy is generation-scoped
|
|
7176
|
+
* (cycle 73): every decision entry written under a contract carries
|
|
7177
|
+
* `contractHash`, and only the CURRENT generation is judged, so
|
|
7178
|
+
* repairsUsed restarts under a fixed contract and a final rejection
|
|
7179
|
+
* a superseded generation left in the crash window neither rolls
|
|
7180
|
+
* forward at boot nor re-arms on replay (its exchange replays byte
|
|
7181
|
+
* identical and the loop continues to a live repair turn). Decisions
|
|
7182
|
+
* recorded before 1.77 carry no hash and bind to the current
|
|
7183
|
+
* contract only while the journal holds a single bundle descriptor;
|
|
7184
|
+
* once a supersession is recorded they are stale. Absent = byte
|
|
7185
|
+
* identical pre 1.72 behavior.
|
|
7143
7186
|
*/
|
|
7144
7187
|
contract?: FinishContract;
|
|
7145
7188
|
/**
|
|
@@ -7286,6 +7329,31 @@ interface OrchestrateSynthesis {
|
|
|
7286
7329
|
* { maxTurns: 2 }. Ignored in 'single' mode.
|
|
7287
7330
|
*/
|
|
7288
7331
|
noteLimits?: UsageLimits;
|
|
7332
|
+
/**
|
|
7333
|
+
* Give the 'single' synthesis invocation the RV-201 evidence tools
|
|
7334
|
+
* `get_child_result` and `read_child_artifact` beside `finish` (the
|
|
7335
|
+
* v1.74 experiment review, P0.2): the finish validators hold the
|
|
7336
|
+
* result against the FULL child outputs while the synthesis model
|
|
7337
|
+
* sees 400 char digests, so when the coordination draft collapses the
|
|
7338
|
+
* evidence the validators demand is model-invisible. With the tools
|
|
7339
|
+
* exposed the digest rows in the synthesis prompt carry each child's
|
|
7340
|
+
* `handle`, and the model pages any settled child's full output or
|
|
7341
|
+
* artifacts before finishing. Off by default: the synthesis toolset
|
|
7342
|
+
* and prompt stay byte identical, exactly like the coordination
|
|
7343
|
+
* `exposeChildResultTools`.
|
|
7344
|
+
*/
|
|
7345
|
+
exposeChildResultTools?: boolean;
|
|
7346
|
+
/**
|
|
7347
|
+
* What the 'single' synthesis prompt embeds beside the draft (the
|
|
7348
|
+
* v1.74 experiment review, P0.2). Default 'digests': the 400 char
|
|
7349
|
+
* settled digest rows, byte identical to pre 1.76. 'full' appends a
|
|
7350
|
+
* CHILD OUTPUTS section carrying every settled child's FULL
|
|
7351
|
+
* serialized output after the digest rows: the whole evidence pool
|
|
7352
|
+
* the validators judge against rides the prompt, paid as input
|
|
7353
|
+
* tokens (declare `estCost` or the preflight `estInputTokens`
|
|
7354
|
+
* accordingly).
|
|
7355
|
+
*/
|
|
7356
|
+
context?: "digests" | "full";
|
|
7289
7357
|
}
|
|
7290
7358
|
/**
|
|
7291
7359
|
* The deterministic reconciliation envelope an 'incremental' synthesis
|
|
@@ -8866,6 +8934,14 @@ interface PreflightOrchestratorSpec {
|
|
|
8866
8934
|
model?: ModelSpec;
|
|
8867
8935
|
limits?: UsageLimits;
|
|
8868
8936
|
estInputTokens?: number;
|
|
8937
|
+
/**
|
|
8938
|
+
* Mirrors OrchestrateSynthesis.exposeChildResultTools (the v1.74
|
|
8939
|
+
* experiment review, P0.2): declaring it lets the evidence
|
|
8940
|
+
* asymmetry check see that the synthesis model can page the full
|
|
8941
|
+
* child outputs the validators judge against.
|
|
8942
|
+
*/
|
|
8943
|
+
exposeChildResultTools?: boolean; /** Mirrors OrchestrateSynthesis.context; default 'digests'. */
|
|
8944
|
+
context?: "digests" | "full";
|
|
8869
8945
|
};
|
|
8870
8946
|
}
|
|
8871
8947
|
/** The full input: engine surface, run surface, and the declared wave. */
|
|
@@ -8907,6 +8983,13 @@ interface PreflightInput {
|
|
|
8907
8983
|
* runtime would actually grant.
|
|
8908
8984
|
*/
|
|
8909
8985
|
repairTurnReserve?: number;
|
|
8986
|
+
/**
|
|
8987
|
+
* Mirrors FinishValidationSpec.maxRepairs (default
|
|
8988
|
+
* {@link DEFAULT_FINISH_MAX_REPAIRS}): with zero, the first
|
|
8989
|
+
* rejection is final and there is no repair exchange to fund, so
|
|
8990
|
+
* the repair-reserve-unfunded warning stays silent.
|
|
8991
|
+
*/
|
|
8992
|
+
maxRepairs?: number;
|
|
8910
8993
|
};
|
|
8911
8994
|
}
|
|
8912
8995
|
/** One linter verdict; `spawn` names the wave entry it is about. */
|
package/dist/index.js
CHANGED
|
@@ -10181,6 +10181,15 @@ function estimateInputTokens(messages) {
|
|
|
10181
10181
|
return Math.ceil(chars / 4);
|
|
10182
10182
|
}
|
|
10183
10183
|
/**
|
|
10184
|
+
* The terminal tool's schema-rejection feedback line. One producer, two
|
|
10185
|
+
* readers: the interception writes it as the error result, and the
|
|
10186
|
+
* window-derived schemaRejectedTerminalExchanges counter recognizes the
|
|
10187
|
+
* exchange by exactly these bytes, so the two can never drift.
|
|
10188
|
+
*/
|
|
10189
|
+
function terminalSchemaRejectionMessage(name) {
|
|
10190
|
+
return `the '${name}' call failed validation`;
|
|
10191
|
+
}
|
|
10192
|
+
/**
|
|
10184
10193
|
* The serving model's declared minimum request output cap, default one.
|
|
10185
10194
|
* OpenAI's Responses API rejects max_output_tokens below 16, so a
|
|
10186
10195
|
* below-floor dispatch is a guaranteed provider 400: the v1.74
|
|
@@ -10870,7 +10879,7 @@ async function runAgent(options) {
|
|
|
10870
10879
|
durationMs: now() - gateStartedAt
|
|
10871
10880
|
});
|
|
10872
10881
|
parts.push(errorPart(call, {
|
|
10873
|
-
error:
|
|
10882
|
+
error: terminalSchemaRejectionMessage(gatedCall.name),
|
|
10874
10883
|
issues: validation === void 0 ? [] : validation.issues.map((i) => i.message)
|
|
10875
10884
|
}));
|
|
10876
10885
|
continue;
|
|
@@ -12008,6 +12017,9 @@ async function runAgent(options) {
|
|
|
12008
12017
|
if (errorMessage !== void 0) result.errorMessage = errorMessage;
|
|
12009
12018
|
if (guard !== void 0) result.exploration = guard.summary(toolCallsUsed);
|
|
12010
12019
|
if (limitPartial !== void 0) result.partial = limitPartial;
|
|
12020
|
+
const terminalName = options.terminalTool?.name;
|
|
12021
|
+
const schemaRejectedTerminalExchanges = terminalName === void 0 ? 0 : messages.reduce((count, message) => count + message.parts.filter((part) => part.type === "tool-result" && part.name === terminalName && part.isError === true && part.result?.error === terminalSchemaRejectionMessage(terminalName)).length, 0);
|
|
12022
|
+
if (schemaRejectedTerminalExchanges > 0) result.schemaRejectedTerminalExchanges = schemaRejectedTerminalExchanges;
|
|
12011
12023
|
if (usageApprox) result.usageApprox = true;
|
|
12012
12024
|
if (transportRetries > 0) result.transportRetries = transportRetries;
|
|
12013
12025
|
if (rateLimitObservations.size > 0) result.rateLimitObservations = [...rateLimitObservations.values()];
|
|
@@ -15879,6 +15891,19 @@ function validateOrchestrateOptions(opts) {
|
|
|
15879
15891
|
}
|
|
15880
15892
|
if (fv.maxRepairs !== void 0) requireNonNegativeInteger(fv.maxRepairs, "orchestrate finishValidation.maxRepairs");
|
|
15881
15893
|
if (fv.repairTurnReserve !== void 0) requireNonNegativeInteger(fv.repairTurnReserve, "orchestrate finishValidation.repairTurnReserve");
|
|
15894
|
+
const draftPolicy = fv.draftPolicy;
|
|
15895
|
+
if (draftPolicy !== void 0) {
|
|
15896
|
+
if (typeof draftPolicy !== "object" || draftPolicy === null) throw new ConfigError("orchestrate finishValidation.draftPolicy must be an object");
|
|
15897
|
+
if (opts.synthesis === void 0) throw new ConfigError("orchestrate finishValidation.draftPolicy requires synthesis: without a synthesis invocation the validators bind the coordination finish itself and there is no unvalidated draft to gate");
|
|
15898
|
+
const policy = draftPolicy;
|
|
15899
|
+
if (policy.minWords === void 0 && policy.requireSections === void 0) throw new ConfigError("orchestrate finishValidation.draftPolicy must declare minWords, requireSections, or both");
|
|
15900
|
+
if (policy.minWords !== void 0) {
|
|
15901
|
+
if (typeof policy.minWords !== "number" || !Number.isInteger(policy.minWords) || policy.minWords < 1) throw new ConfigError(`orchestrate finishValidation.draftPolicy.minWords must be a positive integer; got ${JSON.stringify(policy.minWords)}`);
|
|
15902
|
+
}
|
|
15903
|
+
if (policy.requireSections !== void 0) {
|
|
15904
|
+
if (!Array.isArray(policy.requireSections) || policy.requireSections.length === 0 || policy.requireSections.some((section) => typeof section !== "string" || section.length === 0)) throw new ConfigError("orchestrate finishValidation.draftPolicy.requireSections must be a non empty array of non empty strings");
|
|
15905
|
+
}
|
|
15906
|
+
}
|
|
15882
15907
|
const contract = fv.contract;
|
|
15883
15908
|
if (contract !== void 0) {
|
|
15884
15909
|
const shape = contract;
|
|
@@ -15904,6 +15929,9 @@ function validateOrchestrateOptions(opts) {
|
|
|
15904
15929
|
if (synthesis.mode !== void 0 && synthesis.mode !== "single" && synthesis.mode !== "incremental") throw new ConfigError("orchestrate synthesis.mode must be 'single' or 'incremental'; got " + JSON.stringify(synthesis.mode));
|
|
15905
15930
|
if (synthesis.mode === "incremental" && opts.finishValidation !== void 0) throw new ConfigError("orchestrate synthesis.mode 'incremental' reconciles deterministically and has no model-composed final finish for finishValidation to bind; configure validators with mode 'single', or drop them");
|
|
15906
15931
|
if (synthesis.dedupeClaims !== void 0 && typeof synthesis.dedupeClaims !== "boolean") throw new ConfigError("orchestrate synthesis.dedupeClaims must be a boolean; got " + typeof synthesis.dedupeClaims);
|
|
15932
|
+
const symmetry = synthesis;
|
|
15933
|
+
if (symmetry.exposeChildResultTools !== void 0 && typeof symmetry.exposeChildResultTools !== "boolean") throw new ConfigError("orchestrate synthesis.exposeChildResultTools must be a boolean; got " + typeof symmetry.exposeChildResultTools);
|
|
15934
|
+
if (symmetry.context !== void 0 && symmetry.context !== "digests" && symmetry.context !== "full") throw new ConfigError("orchestrate synthesis.context must be 'digests' or 'full'; got " + JSON.stringify(symmetry.context));
|
|
15907
15935
|
if (synthesis.noteLimits !== void 0) validateUsageLimits(synthesis.noteLimits, "orchestrate synthesis.noteLimits");
|
|
15908
15936
|
if (synthesis.effort !== void 0 && ![
|
|
15909
15937
|
"low",
|
|
@@ -16853,6 +16881,12 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
16853
16881
|
const validationSpec = opts?.finishValidation;
|
|
16854
16882
|
const validationAbort = new AbortController();
|
|
16855
16883
|
let validationTermination;
|
|
16884
|
+
/**
|
|
16885
|
+
* The synthesis invocation's schema-dead finish exchanges (cycle
|
|
16886
|
+
* 73), captured from its full agent result so the failure
|
|
16887
|
+
* enrichment can fold BOTH windows into one honest counter.
|
|
16888
|
+
*/
|
|
16889
|
+
let synthesisSchemaRejectedExchanges = 0;
|
|
16856
16890
|
const finishValidationError = (decision) => new FailRunError(`the orchestrator finish failed host validation with all ${String(decision.maxRepairs)} repair attempts spent: ` + decision.failed.map((f) => `validator '${f.name}' rejected: ${f.reasons.join("; ")}`).join("; "), { data: {
|
|
16857
16891
|
source: "orchestrator_finish_validation",
|
|
16858
16892
|
callId: decision.callId,
|
|
@@ -16861,6 +16895,22 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
16861
16895
|
maxRepairs: decision.maxRepairs
|
|
16862
16896
|
} });
|
|
16863
16897
|
const validationDecisions = () => internals.replayer.snapshot().filter((entry) => entry.kind === "decision" && entry.scope === callingState.scope && entry.value?.decisionType === "orchestrator_finish_validation").map((entry) => entry.value);
|
|
16898
|
+
/**
|
|
16899
|
+
* The contract generation membership test (cycle 73). Without a
|
|
16900
|
+
* contract there are no generations and every decision is current
|
|
16901
|
+
* (the pre 1.77 behavior, byte identical). With one, a decision
|
|
16902
|
+
* belongs to the current generation when its journaled hash matches
|
|
16903
|
+
* the live contract; a pre 1.77 decision carries no hash and counts
|
|
16904
|
+
* as current only while the journal has never recorded a
|
|
16905
|
+
* superseding bundle descriptor, because a single descriptor means
|
|
16906
|
+
* every decision was necessarily rendered under it.
|
|
16907
|
+
*/
|
|
16908
|
+
const contractGenerationCurrent = (decision) => {
|
|
16909
|
+
const hash = validationSpec?.contract?.hash;
|
|
16910
|
+
if (hash === void 0) return true;
|
|
16911
|
+
if (decision.contractHash !== void 0) return decision.contractHash === hash;
|
|
16912
|
+
return internals.replayer.snapshot().filter((entry) => entry.kind === "decision" && entry.scope === callingState.scope && entry.value?.decisionType === "orchestrator_finish_validation_bundle").length <= 1;
|
|
16913
|
+
};
|
|
16864
16914
|
const validateFinish = async (call) => {
|
|
16865
16915
|
if (validationSpec === void 0) return { ok: true };
|
|
16866
16916
|
const maxRepairs = validationSpec.maxRepairs ?? 1;
|
|
@@ -16899,14 +16949,15 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
16899
16949
|
reasons: verdict.reasons
|
|
16900
16950
|
});
|
|
16901
16951
|
}
|
|
16902
|
-
const repairsUsed = known.filter((candidate) => candidate.verdict !== "accepted").length;
|
|
16952
|
+
const repairsUsed = known.filter((candidate) => candidate.verdict !== "accepted" && contractGenerationCurrent(candidate)).length;
|
|
16903
16953
|
decision = {
|
|
16904
16954
|
decisionType: "orchestrator_finish_validation",
|
|
16905
16955
|
callId: call.id,
|
|
16906
16956
|
verdict: failed.length === 0 ? "accepted" : repairsUsed < maxRepairs ? "repair" : "rejected",
|
|
16907
16957
|
failed,
|
|
16908
16958
|
repairsUsed,
|
|
16909
|
-
maxRepairs
|
|
16959
|
+
maxRepairs,
|
|
16960
|
+
...validationSpec.contract === void 0 ? {} : { contractHash: validationSpec.contract.hash }
|
|
16910
16961
|
};
|
|
16911
16962
|
await internals.replayer.appendSinglePhase({
|
|
16912
16963
|
scope: callingState.scope,
|
|
@@ -16920,8 +16971,10 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
16920
16971
|
}
|
|
16921
16972
|
if (decision.verdict === "accepted") return { ok: true };
|
|
16922
16973
|
if (decision.verdict === "rejected") {
|
|
16923
|
-
|
|
16924
|
-
|
|
16974
|
+
if (contractGenerationCurrent(decision)) {
|
|
16975
|
+
validationTermination = finishValidationError(decision);
|
|
16976
|
+
validationAbort.abort("rulvar:finish-validation");
|
|
16977
|
+
}
|
|
16925
16978
|
return {
|
|
16926
16979
|
ok: false,
|
|
16927
16980
|
feedback: {
|
|
@@ -16940,6 +16993,37 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
16940
16993
|
};
|
|
16941
16994
|
};
|
|
16942
16995
|
/**
|
|
16996
|
+
* The coordination draft gate (the v1.74 experiment review, P0.3):
|
|
16997
|
+
* deterministic library text checks over the draft finish, run only
|
|
16998
|
+
* when the validators bind synthesis. Unlike validateFinish nothing
|
|
16999
|
+
* journals: the checks are pure functions of the exchange, the
|
|
17000
|
+
* rejected exchange is durable in the transcript itself, and a
|
|
17001
|
+
* resumed segment recounts identically. The rejection is the finish
|
|
17002
|
+
* call's error result, so the loop's repair-reserve grants count it
|
|
17003
|
+
* exactly like a host validation rejection.
|
|
17004
|
+
*/
|
|
17005
|
+
const validateDraft = (call) => {
|
|
17006
|
+
const policy = validationSpec?.draftPolicy;
|
|
17007
|
+
if (policy === void 0) return Promise.resolve({ ok: true });
|
|
17008
|
+
const result = call.result ?? null;
|
|
17009
|
+
const text = typeof result === "string" ? result : JSON.stringify(result);
|
|
17010
|
+
const reasons = [];
|
|
17011
|
+
if (policy.minWords !== void 0) {
|
|
17012
|
+
const trimmed = text.trim();
|
|
17013
|
+
const words = trimmed === "" ? 0 : trimmed.split(/\s+/).length;
|
|
17014
|
+
if (words < policy.minWords) reasons.push(`the draft carries ${String(words)} words, below the required ${String(policy.minWords)}`);
|
|
17015
|
+
}
|
|
17016
|
+
for (const section of policy.requireSections ?? []) if (!text.includes(section)) reasons.push(`required draft section '${section}' is missing`);
|
|
17017
|
+
if (reasons.length === 0) return Promise.resolve({ ok: true });
|
|
17018
|
+
return Promise.resolve({
|
|
17019
|
+
ok: false,
|
|
17020
|
+
feedback: {
|
|
17021
|
+
error: "the coordination draft failed the draft policy; repair the draft and call finish again: the synthesis invocation composes the FINAL result from this draft, and a collapsed draft starves it of the evidence the validators demand",
|
|
17022
|
+
reasons
|
|
17023
|
+
}
|
|
17024
|
+
});
|
|
17025
|
+
};
|
|
17026
|
+
/**
|
|
16943
17027
|
* The frozen bundle descriptor (the v1.71 experiment review,
|
|
16944
17028
|
* P0.2): with a contract configured, the run durably records WHAT
|
|
16945
17029
|
* validates it. One decision entry per distinct contract hash, in
|
|
@@ -16984,7 +17068,10 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
16984
17068
|
},
|
|
16985
17069
|
[kTerminalTool]: {
|
|
16986
17070
|
name: FINISH_TOOL_NAME,
|
|
16987
|
-
...validationSpec === void 0 || opts?.synthesis !== void 0 ?
|
|
17071
|
+
...validationSpec === void 0 || opts?.synthesis !== void 0 ? validationSpec?.draftPolicy !== void 0 && opts?.synthesis !== void 0 ? {
|
|
17072
|
+
validate: validateDraft,
|
|
17073
|
+
...validationSpec.repairTurnReserve === void 0 ? {} : { repairTurnReserve: validationSpec.repairTurnReserve }
|
|
17074
|
+
} : {} : {
|
|
16988
17075
|
validate: validateFinish,
|
|
16989
17076
|
...validationSpec.repairTurnReserve === void 0 ? {} : { repairTurnReserve: validationSpec.repairTurnReserve }
|
|
16990
17077
|
}
|
|
@@ -17229,8 +17316,15 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
17229
17316
|
if (spec === void 0) return draft;
|
|
17230
17317
|
await recoveryDone;
|
|
17231
17318
|
if (spec.mode === "incremental") return await reconcileIncremental(draft, spec);
|
|
17232
|
-
const
|
|
17233
|
-
const
|
|
17319
|
+
const exposeTools = spec.exposeChildResultTools === true;
|
|
17320
|
+
const fullContext = spec.context === "full";
|
|
17321
|
+
const synthesisToolNames = /* @__PURE__ */ new Set([FINISH_TOOL_NAME, ...exposeTools ? [GET_CHILD_RESULT_TOOL_NAME, READ_CHILD_ARTIFACT_TOOL_NAME] : []]);
|
|
17322
|
+
const synthesisTools = buildOrchestratorTools(orchestratorRuntime, fullCardText, { childResultTools: exposeTools }).filter((tool) => synthesisToolNames.has(tool.name));
|
|
17323
|
+
const settledEntries = [...records.entries()].filter((entry) => entry[1].settled !== void 0).sort((a, b) => a[1].spawnOrdinal - b[1].spawnOrdinal);
|
|
17324
|
+
const settledDigests = settledEntries.map(([handle, record]) => ({
|
|
17325
|
+
...exposeTools ? { handle } : {},
|
|
17326
|
+
...digestOf(record, record.settled)
|
|
17327
|
+
}));
|
|
17234
17328
|
let digestRows = settledDigests;
|
|
17235
17329
|
let repeatedClaims;
|
|
17236
17330
|
if (spec.dedupeClaims === true) {
|
|
@@ -17248,13 +17342,19 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
17248
17342
|
const draftJson = JSON.stringify(draft ?? null);
|
|
17249
17343
|
const digestJson = JSON.stringify(digestRows);
|
|
17250
17344
|
const prompt = [
|
|
17251
|
-
"You are the synthesis invocation of an orchestrated run. Compose the FINAL result of the run from the goal, the coordination draft, and the settled child evidence below by calling finish({ result }) EXACTLY once. Preserve the evidence and citations the draft relies on; do not invent findings. No other tool exists.",
|
|
17345
|
+
"You are the synthesis invocation of an orchestrated run. Compose the FINAL result of the run from the goal, the coordination draft, and the settled child evidence below by calling finish({ result }) EXACTLY once. Preserve the evidence and citations the draft relies on; do not invent findings. " + (exposeTools ? "Beside finish, get_child_result and read_child_artifact page any SETTLED child's FULL output and artifacts by handle (each DIGEST row carries its handle); read what the validators will hold you to before finishing." : "No other tool exists."),
|
|
17252
17346
|
...repeatedClaims === void 0 ? [] : ["Repeated claims across children were deduplicated before this prompt: only the first occurrence of each repeated line remains in the digest, and the REPEATED CLAIMS index below lists each one with its reporters."],
|
|
17253
17347
|
...spec.instructions === void 0 ? [] : [spec.instructions],
|
|
17254
17348
|
...finishValidationPromptLines(validationSpec),
|
|
17255
17349
|
`GOAL: ${goal}`,
|
|
17256
17350
|
`DRAFT: ${draftJson}`,
|
|
17257
17351
|
`DIGEST: ${digestJson}`,
|
|
17352
|
+
...fullContext ? [`CHILD OUTPUTS: ${JSON.stringify(settledEntries.map(([handle, record]) => ({
|
|
17353
|
+
...exposeTools ? { handle } : {},
|
|
17354
|
+
nodeId: record.nodeId,
|
|
17355
|
+
status: record.settled.status,
|
|
17356
|
+
text: serializeChildOutput(record.settled)
|
|
17357
|
+
})))}`] : [],
|
|
17258
17358
|
...repeatedClaims === void 0 ? [] : [`REPEATED CLAIMS: ${JSON.stringify(repeatedClaims)}`]
|
|
17259
17359
|
].join("\n");
|
|
17260
17360
|
internals.events.emit({
|
|
@@ -17280,7 +17380,7 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
17280
17380
|
const synthesisOpts = {
|
|
17281
17381
|
role: "synthesize",
|
|
17282
17382
|
result: "full",
|
|
17283
|
-
tools:
|
|
17383
|
+
tools: synthesisTools,
|
|
17284
17384
|
limits: spec.limits ?? { maxTurns: 4 },
|
|
17285
17385
|
...spec.model === void 0 ? {} : { model: spec.model },
|
|
17286
17386
|
...spec.effort === void 0 ? {} : { effort: spec.effort },
|
|
@@ -17294,6 +17394,7 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
17294
17394
|
}
|
|
17295
17395
|
};
|
|
17296
17396
|
const synthesized = await runtime.runInScope(synthesisState, () => ctx.agent(prompt, synthesisOpts));
|
|
17397
|
+
synthesisSchemaRejectedExchanges = synthesized.schemaRejectedTerminalExchanges ?? 0;
|
|
17297
17398
|
if (validationTermination !== void 0) throw validationTermination;
|
|
17298
17399
|
if (synthesized.status === "ok") return synthesized.output;
|
|
17299
17400
|
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: {
|
|
@@ -17356,7 +17457,10 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
17356
17457
|
if (capDecisionRef !== void 0) return await settleCapOutcome();
|
|
17357
17458
|
if (validationSpec !== void 0) {
|
|
17358
17459
|
const priorRejection = validationDecisions().find((decision) => decision.verdict === "rejected");
|
|
17359
|
-
if (priorRejection !== void 0)
|
|
17460
|
+
if (priorRejection !== void 0) {
|
|
17461
|
+
const settle = lastRunSettle(internals.replayer.snapshot());
|
|
17462
|
+
if (settle !== void 0 && settle.runStatus !== "running" && settle.runStatus !== "suspended" || contractGenerationCurrent(priorRejection)) throw finishValidationError(priorRejection);
|
|
17463
|
+
}
|
|
17360
17464
|
}
|
|
17361
17465
|
const promptLines = [
|
|
17362
17466
|
...extension?.promptLines?.() ?? [],
|
|
@@ -17367,28 +17471,28 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
17367
17471
|
const liveTermination = extensionTermination;
|
|
17368
17472
|
if (liveTermination !== void 0) throw liveTermination;
|
|
17369
17473
|
if (capDecisionRef !== void 0) return await settleCapOutcome();
|
|
17370
|
-
if (validationTermination !== void 0) throw validationTermination;
|
|
17371
|
-
if (orchestratorAccount !== void 0) internals.cost.orchestrator.spentUsd = internals.budget.accountView(orchestratorAccount)?.spentUsd ?? 0;
|
|
17372
|
-
if (result.status !== "ok") throw new ConfigError(`the orchestrator agent terminated with status '${result.status}'` + (result.errorMessage === void 0 ? "" : `: ${result.errorMessage}`));
|
|
17373
17474
|
/**
|
|
17374
|
-
* The
|
|
17375
|
-
* P0.8 remainder + P1.7): every typed
|
|
17376
|
-
*
|
|
17377
|
-
* from the JOURNALED validation decisions
|
|
17378
|
-
*
|
|
17379
|
-
*
|
|
17380
|
-
*
|
|
17381
|
-
*
|
|
17382
|
-
*
|
|
17383
|
-
*
|
|
17384
|
-
*
|
|
17385
|
-
*
|
|
17475
|
+
* The finish-validation failure enrichment (the v1.71 experiment
|
|
17476
|
+
* review, P0.8 remainder + P1.7; widened by cycle 73): every typed
|
|
17477
|
+
* failure a finish rejection produces gains the verdict-derived
|
|
17478
|
+
* repair taxonomy read from the JOURNALED validation decisions of
|
|
17479
|
+
* the CURRENT contract generation (identical live and on replay),
|
|
17480
|
+
* the schema-dead finish exchange counter derived from the
|
|
17481
|
+
* coordination and synthesis windows (the class the v1.74 run lost
|
|
17482
|
+
* six payloads to, invisible in every other field), and, when an
|
|
17483
|
+
* acceptance verdict exists, the acceptance snapshot the children
|
|
17484
|
+
* already earned: completion and childStatusCounts, and now the
|
|
17485
|
+
* degradation facts beside them (degradedReasons and the salvage
|
|
17486
|
+
* lists), exactly what the ok envelope reports. The
|
|
17487
|
+
* rejection-past-the-bound error keeps its own
|
|
17488
|
+
* repairsUsed/maxRepairs/failed untouched.
|
|
17386
17489
|
*/
|
|
17387
17490
|
const enrichSynthesisFailure = (thrown, snapshot) => {
|
|
17388
17491
|
if (!(thrown instanceof FailRunError)) throw thrown;
|
|
17389
17492
|
const base = thrown.data ?? {};
|
|
17390
|
-
const spent = validationDecisions().filter((candidate) => candidate.verdict !== "accepted");
|
|
17493
|
+
const spent = validationDecisions().filter((candidate) => candidate.verdict !== "accepted" && contractGenerationCurrent(candidate));
|
|
17391
17494
|
const rejectedValidators = [...new Set(spent.flatMap((candidate) => candidate.failed.map((f) => f.name)))];
|
|
17495
|
+
const schemaRejected = (result.schemaRejectedTerminalExchanges ?? 0) + synthesisSchemaRejectedExchanges;
|
|
17392
17496
|
throw new FailRunError(thrown.message, { data: {
|
|
17393
17497
|
...base,
|
|
17394
17498
|
...snapshot ?? {},
|
|
@@ -17396,9 +17500,13 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
17396
17500
|
repairsUsed: spent.length,
|
|
17397
17501
|
maxRepairs: validationSpec?.maxRepairs ?? 1,
|
|
17398
17502
|
rejectedValidators
|
|
17399
|
-
}
|
|
17503
|
+
},
|
|
17504
|
+
...schemaRejected === 0 || base.schemaRejectedFinishExchanges !== void 0 ? {} : { schemaRejectedFinishExchanges: schemaRejected }
|
|
17400
17505
|
} });
|
|
17401
17506
|
};
|
|
17507
|
+
if (validationTermination !== void 0) enrichSynthesisFailure(validationTermination);
|
|
17508
|
+
if (orchestratorAccount !== void 0) internals.cost.orchestrator.spentUsd = internals.budget.accountView(orchestratorAccount)?.spentUsd ?? 0;
|
|
17509
|
+
if (result.status !== "ok") throw new ConfigError(`the orchestrator agent terminated with status '${result.status}'` + (result.errorMessage === void 0 ? "" : `: ${result.errorMessage}`));
|
|
17402
17510
|
if (opts?.acceptance === void 0) try {
|
|
17403
17511
|
return await runSynthesis(result.output);
|
|
17404
17512
|
} catch (thrown) {
|
|
@@ -17482,7 +17590,10 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
17482
17590
|
} catch (thrown) {
|
|
17483
17591
|
enrichSynthesisFailure(thrown, {
|
|
17484
17592
|
completion: decision.completion,
|
|
17485
|
-
childStatusCounts: decision.childStatusCounts
|
|
17593
|
+
childStatusCounts: decision.childStatusCounts,
|
|
17594
|
+
degradedReasons: decision.degradedReasons,
|
|
17595
|
+
...decision.salvagedPartialChildren === void 0 ? {} : { salvagedPartialChildren: decision.salvagedPartialChildren },
|
|
17596
|
+
...decision.salvagedTerminalOutputChildren === void 0 ? {} : { salvagedTerminalOutputChildren: decision.salvagedTerminalOutputChildren }
|
|
17486
17597
|
});
|
|
17487
17598
|
}
|
|
17488
17599
|
return {
|
|
@@ -17614,6 +17725,39 @@ function preflightEstimate(input) {
|
|
|
17614
17725
|
const say = (finding) => {
|
|
17615
17726
|
findings.push(finding);
|
|
17616
17727
|
};
|
|
17728
|
+
/**
|
|
17729
|
+
* The contract turn feasibility check (the v1.74 experiment review,
|
|
17730
|
+
* cycle 73), run against the invocation the validators bind. The
|
|
17731
|
+
* floor is the contract's own minimal accepting payload, serialized
|
|
17732
|
+
* exactly as the model must emit it ({ result }) and priced at the
|
|
17733
|
+
* loop's four-characters-per-token output heuristic: the v1.74 run's
|
|
17734
|
+
* conforming payloads truncated at their 9000 token turn cap, and its
|
|
17735
|
+
* contract's minimum alone prices above that cap. A minimum at or
|
|
17736
|
+
* over the bound is an error (every conforming finish truncates mid
|
|
17737
|
+
* payload); a minimum within double of the bound is a warning,
|
|
17738
|
+
* because real conforming payloads run richer than the minimum.
|
|
17739
|
+
*/
|
|
17740
|
+
const contractFeasibilityFindings = (outputBound, spawn, servedBy) => {
|
|
17741
|
+
const contract = input.finishValidation?.contract;
|
|
17742
|
+
if (contract === void 0 || outputBound === void 0) return;
|
|
17743
|
+
const minTokens = Math.ceil(JSON.stringify({ result: contract.goldenAccept.text }).length / 4);
|
|
17744
|
+
const which = spawn === "synthesis" ? "the synthesis invocation" : "the coordination loop";
|
|
17745
|
+
if (minTokens >= outputBound) {
|
|
17746
|
+
say({
|
|
17747
|
+
severity: "error",
|
|
17748
|
+
code: "output-contract-turn-infeasible",
|
|
17749
|
+
message: `the finish contract's minimal accepting payload is about ${String(minTokens)} tokens (four characters per token over the golden accept fixture) but ${which} dispatches at most ${String(outputBound)} output tokens per turn ('${servedBy}'): every conforming finish truncates mid payload; raise maxOutputTokensPerTurn or shrink the contract`,
|
|
17750
|
+
spawn
|
|
17751
|
+
});
|
|
17752
|
+
return;
|
|
17753
|
+
}
|
|
17754
|
+
if (minTokens * 2 > outputBound) say({
|
|
17755
|
+
severity: "warning",
|
|
17756
|
+
code: "output-contract-turn-headroom",
|
|
17757
|
+
message: `the finish contract's minimal accepting payload is about ${String(minTokens)} tokens against the ${String(outputBound)} token output bound of ${which} ('${servedBy}'): real conforming payloads run richer than the minimum and the margin is under double; raise the bound or trim the contract`,
|
|
17758
|
+
spawn
|
|
17759
|
+
});
|
|
17760
|
+
};
|
|
17617
17761
|
const adapters = new Map((engine.adapters ?? []).map((adapter) => [adapter.id, adapter]));
|
|
17618
17762
|
const capsOf = (ref) => {
|
|
17619
17763
|
const { adapterId, model } = parseModelRef(ref);
|
|
@@ -17859,6 +18003,7 @@ function preflightEstimate(input) {
|
|
|
17859
18003
|
message: `the orchestrator sets maxOutputTokensPerTurn ${String(orchLimits.maxOutputTokensPerTurn)} below the ${String(caps.minOutputTokensPerTurn)} token output floor of '${servedBy}': every dispatch would be refused typed before the wire; raise the cap to at least the floor`,
|
|
17860
18004
|
spawn: "orchestrator"
|
|
17861
18005
|
});
|
|
18006
|
+
if (input.orchestrator.synthesis === void 0) contractFeasibilityFindings(outputBound, "orchestrator", servedBy);
|
|
17862
18007
|
const { adapterId, model } = parseModelRef(servedBy);
|
|
17863
18008
|
const unit = {
|
|
17864
18009
|
label: "orchestrator",
|
|
@@ -17895,6 +18040,21 @@ function preflightEstimate(input) {
|
|
|
17895
18040
|
const synthesis = input.orchestrator.synthesis;
|
|
17896
18041
|
if (synthesis.limits !== void 0) validateUsageLimits(synthesis.limits, "preflight orchestrator.synthesis.limits");
|
|
17897
18042
|
if (synthesis.estInputTokens !== void 0) requireNonNegativeInteger(synthesis.estInputTokens, "preflight orchestrator.synthesis.estInputTokens");
|
|
18043
|
+
if (synthesis.context !== void 0 && synthesis.context !== "digests" && synthesis.context !== "full") throw new ConfigError("preflight orchestrator.synthesis.context must be 'digests' or 'full'; got " + JSON.stringify(synthesis.context));
|
|
18044
|
+
{
|
|
18045
|
+
const evidenceNames = /* @__PURE__ */ new Set([
|
|
18046
|
+
"evidence-preserved",
|
|
18047
|
+
"contract-citations",
|
|
18048
|
+
"contract-section-citations"
|
|
18049
|
+
]);
|
|
18050
|
+
const evidenceValidators = (input.finishValidation?.validators ?? []).map((validator) => validator.name).filter((name) => evidenceNames.has(name));
|
|
18051
|
+
if ((evidenceValidators.length > 0 || input.finishValidation?.contract?.manifest.citations !== void 0) && synthesis.exposeChildResultTools !== true && (synthesis.context ?? "digests") === "digests") say({
|
|
18052
|
+
severity: "warning",
|
|
18053
|
+
code: "synthesis-evidence-asymmetry",
|
|
18054
|
+
message: `the finish validators demand child evidence (${evidenceValidators.length > 0 ? evidenceValidators.join(", ") : "contract citations"}) but the synthesis invocation sees only truncated digest rows and has no child read tools: a collapsed coordination draft starves it of every citation (the v1.74 experiment shape); set synthesis.exposeChildResultTools, synthesis.context 'full', or bind the validators to coordination by dropping synthesis`,
|
|
18055
|
+
spawn: "synthesis"
|
|
18056
|
+
});
|
|
18057
|
+
}
|
|
17898
18058
|
const servedBy = resolveServing(synthesis.model) ?? resolveServing(defaults.routing?.synthesize);
|
|
17899
18059
|
if (servedBy === void 0) say({
|
|
17900
18060
|
severity: "error",
|
|
@@ -17912,6 +18072,7 @@ function preflightEstimate(input) {
|
|
|
17912
18072
|
message: `the synthesis invocation sets maxOutputTokensPerTurn ${String(merged.maxOutputTokensPerTurn)} below the ${String(caps.minOutputTokensPerTurn)} token output floor of '${servedBy}': every dispatch would be refused typed before the wire; raise the cap to at least the floor`,
|
|
17913
18073
|
spawn: "synthesis"
|
|
17914
18074
|
});
|
|
18075
|
+
contractFeasibilityFindings(outputBound, "synthesis", servedBy);
|
|
17915
18076
|
const projected = projectedProviderTurnsOf(merged, overallExecutedCeiling(merged, toolCeilingsOf(merged))) + finishRepairReserve;
|
|
17916
18077
|
if (orchestratorEcho !== void 0) orchestratorEcho.synthesis = {
|
|
17917
18078
|
projectedProviderTurns: projected,
|
|
@@ -18136,6 +18297,15 @@ function preflightEstimate(input) {
|
|
|
18136
18297
|
names.add(validator.name);
|
|
18137
18298
|
}
|
|
18138
18299
|
if (fv.repairTurnReserve !== void 0) requireNonNegativeInteger(fv.repairTurnReserve, "preflight finishValidation.repairTurnReserve");
|
|
18300
|
+
if (fv.maxRepairs !== void 0) requireNonNegativeInteger(fv.maxRepairs, "preflight finishValidation.maxRepairs");
|
|
18301
|
+
{
|
|
18302
|
+
const grantedRepairs = fv.maxRepairs ?? 1;
|
|
18303
|
+
if (grantedRepairs > 0 && fv.repairTurnReserve === void 0) findings.push({
|
|
18304
|
+
severity: "warning",
|
|
18305
|
+
code: "repair-reserve-unfunded",
|
|
18306
|
+
message: `finishValidation grants up to ${String(grantedRepairs)} repair exchange(s) but declares no repairTurnReserve: a rejected finish burns an ordinary turn and a window at maxTurns settles 'limit' with the repair unspent; set finishValidation.repairTurnReserve to fund the repair exchanges`
|
|
18307
|
+
});
|
|
18308
|
+
}
|
|
18139
18309
|
if (fv.contract !== void 0) {
|
|
18140
18310
|
for (const contractValidator of fv.contract.validators) if (!names.has(contractValidator.name)) findings.push({
|
|
18141
18311
|
severity: "error",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.77.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",
|