@warmdrift/kgauto-compiler 2.0.0-alpha.82 → 2.0.0-alpha.85
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/{chunk-VVRDFE6T.mjs → chunk-FD3NFXDC.mjs} +4 -0
- package/dist/{chunk-XPD4I3Q5.mjs → chunk-LO2JXTGG.mjs} +19 -5
- package/dist/{chunk-L246YOV7.mjs → chunk-WXN7FNXP.mjs} +1 -1
- package/dist/glassbox/index.d.mts +3 -3
- package/dist/glassbox/index.d.ts +3 -3
- package/dist/glassbox-routes/format.d.mts +2 -2
- package/dist/glassbox-routes/format.d.ts +2 -2
- package/dist/glassbox-routes/index.d.mts +4 -4
- package/dist/glassbox-routes/index.d.ts +4 -4
- package/dist/glassbox-routes/index.js +16 -2
- package/dist/glassbox-routes/index.mjs +2 -2
- package/dist/glassbox-routes/react/index.d.mts +2 -2
- package/dist/glassbox-routes/react/index.d.ts +2 -2
- package/dist/index.d.mts +396 -141
- package/dist/index.d.ts +396 -141
- package/dist/index.js +283 -37
- package/dist/index.mjs +255 -35
- package/dist/{ir-Cx9hJj0B.d.ts → ir-DfrM71pA.d.ts} +49 -0
- package/dist/{ir-D4S9R816.d.mts → ir-JUoybVM-.d.mts} +49 -0
- package/dist/key-health.js +1 -1
- package/dist/key-health.mjs +1 -1
- package/dist/profiles.d.mts +11 -2
- package/dist/profiles.d.ts +11 -2
- package/dist/profiles.js +5 -0
- package/dist/profiles.mjs +3 -1
- package/dist/{types-DHi4FcUu.d.mts → types-BGH_KAHu.d.mts} +1 -1
- package/dist/{types-CAs9-0S4.d.ts → types-BqTKRECa.d.ts} +1 -1
- package/dist/{types-Cvd6kRNv.d.ts → types-DLDJbCha.d.ts} +1 -1
- package/dist/{types-CRXaR7nJ.d.mts → types-y42mwdvM.d.mts} +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
LIBRARY_VERSION,
|
|
20
20
|
createKeyHealthRoute,
|
|
21
21
|
keyFingerprint
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-WXN7FNXP.mjs";
|
|
23
23
|
import {
|
|
24
24
|
ABSOLUTE_FLOOR,
|
|
25
25
|
ARCHETYPE_FLOOR_DEFAULT,
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
COST_RANKING_REFERENCE_SHAPE,
|
|
28
28
|
MEASURED_GROUNDING_MIN_N,
|
|
29
29
|
PROVIDER_ENV_KEYS,
|
|
30
|
+
canonicalPolicySet,
|
|
30
31
|
chainProviderSpread,
|
|
31
32
|
configureBrainQuery,
|
|
32
33
|
createBrainQueryCache,
|
|
@@ -52,9 +53,10 @@ import {
|
|
|
52
53
|
loadArchetypePerfFromBrain,
|
|
53
54
|
loadArchetypePerfNFromBrain,
|
|
54
55
|
loadChainsFromBrain,
|
|
56
|
+
policySetHas,
|
|
55
57
|
readBrainReadEnv,
|
|
56
58
|
resolveProviderKey
|
|
57
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-LO2JXTGG.mjs";
|
|
58
60
|
import {
|
|
59
61
|
ALIASES,
|
|
60
62
|
LATENCY_TIER_MS,
|
|
@@ -64,8 +66,9 @@ import {
|
|
|
64
66
|
getProfile,
|
|
65
67
|
latencyTierOf,
|
|
66
68
|
profilesByProvider,
|
|
69
|
+
resolveModelAlias,
|
|
67
70
|
tryGetProfile
|
|
68
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-FD3NFXDC.mjs";
|
|
69
72
|
import {
|
|
70
73
|
emitAdvisoryFired,
|
|
71
74
|
emitCompileDone,
|
|
@@ -767,8 +770,8 @@ function effectiveConventions(profile) {
|
|
|
767
770
|
function passScoreTargets(ir, opts) {
|
|
768
771
|
const constraints = ir.constraints ?? {};
|
|
769
772
|
const policy = opts.policy ?? {};
|
|
770
|
-
const blockedSet =
|
|
771
|
-
const preferredSet =
|
|
773
|
+
const blockedSet = canonicalPolicySet(policy.blockedModels);
|
|
774
|
+
const preferredSet = canonicalPolicySet(policy.preferredModels);
|
|
772
775
|
const scores = [];
|
|
773
776
|
const policyMutations = [];
|
|
774
777
|
const rawPromotion = opts.promotion;
|
|
@@ -791,7 +794,7 @@ function passScoreTargets(ir, opts) {
|
|
|
791
794
|
continue;
|
|
792
795
|
}
|
|
793
796
|
const reasons = [];
|
|
794
|
-
if (blockedSet
|
|
797
|
+
if (policySetHas(blockedSet, modelId)) {
|
|
795
798
|
reasons.push(`blocked_by_policy (consumer gated this model \u2014 see CompilePolicy.blockedModels)`);
|
|
796
799
|
}
|
|
797
800
|
if (opts.estimatedInputTokens > profile.maxContextTokens * 0.9) {
|
|
@@ -821,7 +824,7 @@ function passScoreTargets(ir, opts) {
|
|
|
821
824
|
const qualityScore = Math.max(0, baseQuality - qualityPenalty);
|
|
822
825
|
const callerOrderBoost = (modelIds.length - modelIds.indexOf(modelId)) * 0.1;
|
|
823
826
|
const costPenalty = estimatedCostUsd * 5;
|
|
824
|
-
const preferredBoost = preferredSet
|
|
827
|
+
const preferredBoost = policySetHas(preferredSet, modelId) ? 0.5 : 0;
|
|
825
828
|
let latencyPenalty = 0;
|
|
826
829
|
const maxLatencyMs = constraints.maxLatencyMs;
|
|
827
830
|
if (typeof maxLatencyMs === "number" && maxLatencyMs > 0) {
|
|
@@ -878,7 +881,7 @@ function passScoreTargets(ir, opts) {
|
|
|
878
881
|
description: `Model ${modelId} excluded \u2014 estimated cost $${estimatedCostUsd.toFixed(4)} exceeds policy ceiling $${policy.maxCostPerCallUsd.toFixed(4)}`
|
|
879
882
|
});
|
|
880
883
|
}
|
|
881
|
-
if (preferredSet
|
|
884
|
+
if (policySetHas(preferredSet, modelId) && reasons.length === 0) {
|
|
882
885
|
policyMutations.push({
|
|
883
886
|
id: `policy-preferred-${modelId}`,
|
|
884
887
|
source: "compile_policy",
|
|
@@ -2361,6 +2364,67 @@ function advisorRuleCrossFamilyFit(ctx) {
|
|
|
2361
2364
|
];
|
|
2362
2365
|
}
|
|
2363
2366
|
|
|
2367
|
+
// src/advisor-rules/blocked-model-drift.ts
|
|
2368
|
+
var BLOCKED_MODEL_NOT_IN_ROSTER_CODE = "blocked-model-not-in-roster";
|
|
2369
|
+
var BLOCKED_MODEL_FAMILY_SIBLING_SERVED_CODE = "blocked-model-family-sibling-served";
|
|
2370
|
+
var DOCS_URL = "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories";
|
|
2371
|
+
function familyOf2(modelId, profile) {
|
|
2372
|
+
return profile?.family ?? deriveFamilyFromModelId(modelId);
|
|
2373
|
+
}
|
|
2374
|
+
function isOnTheWayOut(status) {
|
|
2375
|
+
return status === "legacy" || status === "deprecated";
|
|
2376
|
+
}
|
|
2377
|
+
function advisorRuleBlockedModelDrift(ctx) {
|
|
2378
|
+
const blocked = ctx.policy?.blockedModels;
|
|
2379
|
+
if (!blocked || blocked.length === 0) return [];
|
|
2380
|
+
const resolve = ctx.resolveProfile ?? tryGetProfile;
|
|
2381
|
+
const out = [];
|
|
2382
|
+
const entries = [...new Set(blocked)].sort();
|
|
2383
|
+
const selectedProfile = resolve(ctx.selectedModelId);
|
|
2384
|
+
const selectedFamily = familyOf2(ctx.selectedModelId, selectedProfile);
|
|
2385
|
+
const orphans = entries.filter((e) => resolve(e) === void 0);
|
|
2386
|
+
if (orphans.length > 0) {
|
|
2387
|
+
const list = orphans.map((o) => `\`${o}\``).join(", ");
|
|
2388
|
+
const plural = orphans.length === 1 ? "entry" : "entries";
|
|
2389
|
+
const verb = orphans.length === 1 ? "matches" : "match";
|
|
2390
|
+
out.push({
|
|
2391
|
+
level: "warn",
|
|
2392
|
+
code: BLOCKED_MODEL_NOT_IN_ROSTER_CODE,
|
|
2393
|
+
message: `CompilePolicy.blockedModels ${plural} ${list} ${verb} no model in the current roster, so ${orphans.length === 1 ? "it is" : "they are"} inert \u2014 \`blockedModels\` is matched by exact model id, and nothing kgauto can select carries ${orphans.length === 1 ? "that id" : "those ids"}. The block will never fire.`,
|
|
2394
|
+
suggestion: `Check for a typo, or for an id that was retired from the roster since the block was written. This is how a block goes quiet without an error: tt-intel (2026-07-29) carried \`KGAUTO_BLOCKED_MODELS="claude-sonnet-4-6"\` across a roster retarget and the block stopped covering the traffic they believed it covered. Resolve the intended model id against the live roster \u2014 \`getRecommendedPrimary({ family, fallback })\` returns the id the family currently resolves to \u2014 and block that id, or drop the entry if it is no longer needed.`,
|
|
2395
|
+
docsUrl: DOCS_URL
|
|
2396
|
+
});
|
|
2397
|
+
}
|
|
2398
|
+
if (selectedFamily !== null) {
|
|
2399
|
+
const siblings = entries.filter((e) => {
|
|
2400
|
+
if (resolveModelAlias(e) === resolveModelAlias(ctx.selectedModelId)) return false;
|
|
2401
|
+
const p = resolve(e);
|
|
2402
|
+
if (familyOf2(e, p) !== selectedFamily) return false;
|
|
2403
|
+
if (p && selectedProfile && p.provider !== selectedProfile.provider) {
|
|
2404
|
+
return false;
|
|
2405
|
+
}
|
|
2406
|
+
return true;
|
|
2407
|
+
});
|
|
2408
|
+
if (siblings.length > 0) {
|
|
2409
|
+
const list = siblings.map((s) => `\`${s}\``).join(", ");
|
|
2410
|
+
const plural = siblings.length === 1 ? "" : "s";
|
|
2411
|
+
const retargetShaped = siblings.some((s) => {
|
|
2412
|
+
const p = resolve(s);
|
|
2413
|
+
return isOnTheWayOut(p?.status) && selectedProfile?.status === "current";
|
|
2414
|
+
});
|
|
2415
|
+
const retargetNote = retargetShaped ? ` The blocked id${plural} ${siblings.length === 1 ? "is" : "are"} legacy/deprecated while \`${ctx.selectedModelId}\` is current \u2014 that is the signature of a roster lifecycle move rather than a deliberate per-generation block, so this is more likely to be drift than intent.` : "";
|
|
2416
|
+
out.push({
|
|
2417
|
+
level: "warn",
|
|
2418
|
+
code: BLOCKED_MODEL_FAMILY_SIBLING_SERVED_CODE,
|
|
2419
|
+
message: `\`${ctx.selectedModelId}\` was selected for this call. It is in the same family (\`${selectedFamily}\`) as blocked entr${siblings.length === 1 ? "y" : "ies"} ${list}, but its exact id differs \u2014 and \`CompilePolicy.blockedModels\` matches by exact id, so the block does not cover it.${retargetNote}`,
|
|
2420
|
+
suggestion: `Two readings, and kgauto cannot tell them apart: (1) intentional \u2014 you meant to gate that specific id and \`${ctx.selectedModelId}\` is fine, in which case nothing needs doing and you can filter this code; (2) drift \u2014 you meant to gate the family, and a roster change moved traffic to a sibling your block never named. This happened to tt-intel: when the \`claude-sonnet\` family primary retargeted from \`claude-sonnet-4-6\` to \`claude-sonnet-5\`, their literal-id gate stopped matching the family's routed traffic (their fix was local family resolution). If you meant the family, add \`${ctx.selectedModelId}\` to \`blockedModels\` \u2014 kgauto deliberately does NOT widen exact-id blocks into family globs, because that would silently change what every existing block covers.`,
|
|
2421
|
+
docsUrl: DOCS_URL
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
return out;
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2364
2428
|
// src/advisor.ts
|
|
2365
2429
|
var QUALITY_FLOOR_FOR_RECOMMENDATION = 6;
|
|
2366
2430
|
var TIER_DOWN_COST_RATIO = 0.5;
|
|
@@ -2379,6 +2443,12 @@ function runAdvisor(ir, result, profile, policy, phase2) {
|
|
|
2379
2443
|
out.push(...detectToolBloat(ir, result));
|
|
2380
2444
|
out.push(...detectHistoryUncached(ir, profile));
|
|
2381
2445
|
out.push(...detectSingleModelArray(ir, policy));
|
|
2446
|
+
out.push(
|
|
2447
|
+
...advisorRuleBlockedModelDrift({
|
|
2448
|
+
policy,
|
|
2449
|
+
selectedModelId: profile.id
|
|
2450
|
+
})
|
|
2451
|
+
);
|
|
2382
2452
|
if (policy?.posture !== "locked") {
|
|
2383
2453
|
out.push(...detectCostMismatchedArchetype(ir, profile, phase2));
|
|
2384
2454
|
out.push(...detectModelStaleEvidence(ir, profile));
|
|
@@ -2490,18 +2560,27 @@ function detectHistoryUncached(ir, profile) {
|
|
|
2490
2560
|
function detectSingleModelArray(ir, policy) {
|
|
2491
2561
|
if (ir.models.length !== 1) return [];
|
|
2492
2562
|
if (policy?.posture === "locked") return [];
|
|
2493
|
-
const
|
|
2563
|
+
const entry = ir.models[0];
|
|
2564
|
+
const only = typeof entry === "string" ? entry : `family:${entry.family}`;
|
|
2565
|
+
const blocked = canonicalPolicySet(policy?.blockedModels);
|
|
2566
|
+
let alternatives = [];
|
|
2567
|
+
try {
|
|
2568
|
+
alternatives = getDefaultFallbackChain({
|
|
2569
|
+
archetype: ir.intent.archetype,
|
|
2570
|
+
primary: only,
|
|
2571
|
+
posture: "preferred",
|
|
2572
|
+
policy
|
|
2573
|
+
}).filter((id) => resolveModelAlias(id) !== resolveModelAlias(only)).filter((id) => !policySetHas(blocked, id)).filter((id) => getModelCompatibility(id, { archetype: ir.intent.archetype }).status !== "reject");
|
|
2574
|
+
} catch {
|
|
2575
|
+
}
|
|
2576
|
+
const hasAlternative = alternatives.length > 0;
|
|
2577
|
+
const remedy = "Widen the chain AND pin your primary: `compile({ ...ir, models: getDefaultFallbackChain({ archetype: ir.intent.archetype, primary: '" + only + "', posture: 'preferred' }) }, { policy: { preferredModels: ['" + only + "'] } })`. The chain is a CANDIDATE SET \u2014 compile() scores it and does not honour its order, so without the `preferredModels` pin the extra entries can retarget your primary (and with it your cost profile). With the pin, `" + only + "` stays primary and the added entries serve only as the safety net. Note `posture` has no effect once `primary` is passed. If you gate models by spend, re-verify `policy.blockedModels` against the widened set before shipping. If single-model is intentional (compliance/brand promise), set `policy.posture = 'locked'` to silence this rule.";
|
|
2494
2578
|
return [
|
|
2495
2579
|
{
|
|
2496
|
-
level: "warn",
|
|
2580
|
+
level: hasAlternative ? "critical" : "warn",
|
|
2497
2581
|
code: "single-model-array",
|
|
2498
|
-
message: `\`ir.models\` has length 1 (only "${only}") and posture is not 'locked'. A single-model chain has no safety net \u2014 the first 429 / 5xx / cliff hits the user as a failure.
|
|
2499
|
-
|
|
2500
|
-
// position 0 and is therefore cost-neutral; `'open'` re-picks the
|
|
2501
|
-
// primary and is NOT. This rule is about reliability, not cost, so both
|
|
2502
|
-
// stay on offer — but the cost consequence of the second is now stated,
|
|
2503
|
-
// since a reliability fix should not silently become a repricing.
|
|
2504
|
-
suggestion: "Use `getDefaultFallbackChain({ archetype: ir.intent.archetype, primary: '" + only + "', posture: 'preferred' })` for a user-anchored chain \u2014 this keeps `" + only + "` as your primary and only adds fallbacks, so your cost profile is unchanged. `getDefaultFallbackChain({ archetype, posture: 'open' })` instead lets the library pick the PRIMARY, ordered by archetype performance rather than cost \u2014 check what it returns before adopting it. If single-model is intentional (compliance/brand promise), set `policy.posture = 'locked'` to silence this rule.",
|
|
2582
|
+
message: hasAlternative ? `\`ir.models\` has length 1 (only "${only}") for archetype "${ir.intent.archetype}" and posture is not 'locked'. A single-model chain has no safety net \u2014 the first 429 / 5xx / cliff hits the user as a failure \u2014 and a compatible, non-blocked alternative exists in the roster today (${alternatives[0]}), so this is critical: the missing safety net is adoptable now.` : `\`ir.models\` has length 1 (only "${only}") for archetype "${ir.intent.archetype}" and posture is not 'locked'. A single-model chain has no safety net \u2014 the first 429 / 5xx / cliff hits the user as a failure. No compatible non-blocked alternative is visible in the roster for this archetype, so this stays a warning.`,
|
|
2583
|
+
suggestion: remedy,
|
|
2505
2584
|
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#single-model-array"
|
|
2506
2585
|
}
|
|
2507
2586
|
];
|
|
@@ -3055,6 +3134,15 @@ function compile(ir, opts = {}) {
|
|
|
3055
3134
|
sectionRewritesApplied
|
|
3056
3135
|
}
|
|
3057
3136
|
);
|
|
3137
|
+
if (ir["policy"] !== void 0 && opts.policy === void 0) {
|
|
3138
|
+
rawAdvisories.push({
|
|
3139
|
+
level: "critical",
|
|
3140
|
+
code: "policy-in-ir-ignored",
|
|
3141
|
+
message: "The IR passed to compile() carries a `policy` field. `policy` belongs in the SECOND argument \u2014 `compile(ir, { policy })` \u2014 and inside the IR it is an unknown field that is completely ignored. If that policy names blockedModels, no block is being enforced on this call.",
|
|
3142
|
+
suggestion: "Move it: `compile(ir, { policy: { ... } })`. If you also configure policy correctly elsewhere on this path, remove the IR copy so the next reader is not misled.",
|
|
3143
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#public-api"
|
|
3144
|
+
});
|
|
3145
|
+
}
|
|
3058
3146
|
const advisories = rawAdvisories.map((a) => ({
|
|
3059
3147
|
...a,
|
|
3060
3148
|
kgautoRequestId: handle,
|
|
@@ -4824,11 +4912,11 @@ async function call(ir, opts = {}) {
|
|
|
4824
4912
|
}
|
|
4825
4913
|
let policyBlockedFiltered;
|
|
4826
4914
|
if (opts.policy?.blockedModels && opts.policy.blockedModels.length > 0) {
|
|
4827
|
-
const blocked =
|
|
4915
|
+
const blocked = canonicalPolicySet(opts.policy.blockedModels);
|
|
4828
4916
|
const filtered = [];
|
|
4829
4917
|
const dropped = [];
|
|
4830
4918
|
for (const t of targetsToTry) {
|
|
4831
|
-
if (blocked
|
|
4919
|
+
if (policySetHas(blocked, t)) {
|
|
4832
4920
|
dropped.push(t);
|
|
4833
4921
|
} else {
|
|
4834
4922
|
filtered.push(t);
|
|
@@ -4868,10 +4956,18 @@ async function call(ir, opts = {}) {
|
|
|
4868
4956
|
const failedProviders = /* @__PURE__ */ new Map();
|
|
4869
4957
|
const sameModelRetryEnabled = opts.sameModelRetry ?? isSameModelRetryEnabledFromEnv();
|
|
4870
4958
|
let retriedSameModel = false;
|
|
4959
|
+
const pushAttempt = (attempt) => {
|
|
4960
|
+
attempts.push(attempt);
|
|
4961
|
+
if (attempt.status === "success") return;
|
|
4962
|
+
try {
|
|
4963
|
+
opts.onFailedAttempt?.(attempt);
|
|
4964
|
+
} catch {
|
|
4965
|
+
}
|
|
4966
|
+
};
|
|
4871
4967
|
for (let i = 0; i < targetsToTry.length; i++) {
|
|
4872
4968
|
const targetModel = targetsToTry[i];
|
|
4873
4969
|
if (opts.abortSignal?.aborted) {
|
|
4874
|
-
|
|
4970
|
+
pushAttempt({
|
|
4875
4971
|
model: targetModel,
|
|
4876
4972
|
status: "terminal",
|
|
4877
4973
|
errorCode: "aborted",
|
|
@@ -4882,7 +4978,7 @@ async function call(ir, opts = {}) {
|
|
|
4882
4978
|
const targetProfile = tryGetProfile(targetModel);
|
|
4883
4979
|
const providerFailReason = targetProfile ? failedProviders.get(targetProfile.provider) : void 0;
|
|
4884
4980
|
if (targetProfile && providerFailReason && !opts.noFallback) {
|
|
4885
|
-
|
|
4981
|
+
pushAttempt({
|
|
4886
4982
|
model: targetModel,
|
|
4887
4983
|
status: "terminal",
|
|
4888
4984
|
errorCode: `${providerFailReason}_inferred`,
|
|
@@ -4901,7 +4997,7 @@ async function call(ir, opts = {}) {
|
|
|
4901
4997
|
opts
|
|
4902
4998
|
);
|
|
4903
4999
|
} catch (err) {
|
|
4904
|
-
|
|
5000
|
+
pushAttempt({
|
|
4905
5001
|
model: targetModel,
|
|
4906
5002
|
status: "terminal",
|
|
4907
5003
|
errorCode: "compile_error",
|
|
@@ -4939,7 +5035,7 @@ async function call(ir, opts = {}) {
|
|
|
4939
5035
|
}
|
|
4940
5036
|
if (!validated.ok && isStructuredContractViolation(validated.errorCode) && sameModelRetryEnabled && !retriedSameModel && !retrySuppressionNote) {
|
|
4941
5037
|
retriedSameModel = true;
|
|
4942
|
-
|
|
5038
|
+
pushAttempt({
|
|
4943
5039
|
model: targetModel,
|
|
4944
5040
|
status: validated.errorType,
|
|
4945
5041
|
errorCode: validated.errorCode,
|
|
@@ -4954,7 +5050,7 @@ async function call(ir, opts = {}) {
|
|
|
4954
5050
|
servedByRetry = true;
|
|
4955
5051
|
}
|
|
4956
5052
|
if (validated.ok) {
|
|
4957
|
-
|
|
5053
|
+
pushAttempt({
|
|
4958
5054
|
model: targetModel,
|
|
4959
5055
|
status: "success",
|
|
4960
5056
|
...servedByRetry ? { sameModelRetry: true } : {}
|
|
@@ -5072,7 +5168,7 @@ async function call(ir, opts = {}) {
|
|
|
5072
5168
|
advisories: activeCompile.advisories
|
|
5073
5169
|
};
|
|
5074
5170
|
}
|
|
5075
|
-
|
|
5171
|
+
pushAttempt({
|
|
5076
5172
|
model: targetModel,
|
|
5077
5173
|
status: validated.errorType,
|
|
5078
5174
|
errorCode: validated.errorCode,
|
|
@@ -5652,6 +5748,13 @@ function combineOrderSwappedVerdicts(run1, run2) {
|
|
|
5652
5748
|
if (run1 === run2) return run1;
|
|
5653
5749
|
return "tied";
|
|
5654
5750
|
}
|
|
5751
|
+
var ALT_STRATEGY_IDS = [
|
|
5752
|
+
"discipline-gates-v1-alt",
|
|
5753
|
+
"discipline-gates-v1-alt-blind"
|
|
5754
|
+
];
|
|
5755
|
+
function isAltStrategy(id) {
|
|
5756
|
+
return id !== void 0 && ALT_STRATEGY_IDS.includes(id);
|
|
5757
|
+
}
|
|
5655
5758
|
function replayRetryDelayMs(errorCode) {
|
|
5656
5759
|
return errorCode === "rate_limit" ? 65e3 : 2e3;
|
|
5657
5760
|
}
|
|
@@ -5705,7 +5808,34 @@ function altGatesBlockFor(args) {
|
|
|
5705
5808
|
...shapeAltering
|
|
5706
5809
|
]);
|
|
5707
5810
|
}
|
|
5811
|
+
var ALT_BLIND_TOKEN_BUDGET_BREACH = {
|
|
5812
|
+
ceilingTokens: 264,
|
|
5813
|
+
measuredTokensTextWithTools: 285,
|
|
5814
|
+
note: "Blind arm exceeds the stated 264-token ceiling on text+tools (285) and costs ~2.4x the v1-alt arm. Not corrected: compressing it would require editing prose the arm depends on NOT having been edited by this seat. Read a blind-arm LOSS as confounded by token tax; a WIN is unaffected."
|
|
5815
|
+
};
|
|
5816
|
+
var DISCIPLINE_GATES_V1_ALT_BLIND_HEADER = "Before stating any conclusion, run this check:";
|
|
5817
|
+
var ALT_BLIND_BULLETS = {
|
|
5818
|
+
falsify: "- Before committing to an approach, state in one line what evidence would prove it wrong and how you would notice it. If nothing could falsify the approach, you hold a preference, not a plan \u2014 rework it until something could.",
|
|
5819
|
+
deviation: "- When you choose not to follow an applicable instruction, say so explicitly: name the instruction, why you are deviating, and what you are doing instead. Silent deviation is forbidden \u2014 an override is only legitimate when it is visible.",
|
|
5820
|
+
toolPredict: "- When using tools: before each call, state the result you expect. On mismatch, treat the gap as evidence your model of the system is wrong \u2014 revise the model before acting again; never silently retry.",
|
|
5821
|
+
verifyDelegated: "- Verify every delegated or sub-agent result before composing it into an answer: spot-check it against the source or an independent probe. An unverified sub-result is a claim you are repeating, not a fact you know.",
|
|
5822
|
+
markAssumed: '- In the final answer, keep verified claims and assumptions visibly distinct: mark anything unchecked as "assumed" or "unverified". Never let an assumption borrow the confidence of the verified claims beside it.'
|
|
5823
|
+
};
|
|
5824
|
+
function altBlindGatesBlockFor(args) {
|
|
5825
|
+
const parts = [
|
|
5826
|
+
"falsify",
|
|
5827
|
+
"deviation",
|
|
5828
|
+
...args.hasTools ? ["toolPredict"] : [],
|
|
5829
|
+
"verifyDelegated",
|
|
5830
|
+
...args.outputMode === "text" ? ["markAssumed"] : []
|
|
5831
|
+
];
|
|
5832
|
+
return [
|
|
5833
|
+
DISCIPLINE_GATES_V1_ALT_BLIND_HEADER,
|
|
5834
|
+
...parts.map((p) => ALT_BLIND_BULLETS[p])
|
|
5835
|
+
].join("\n");
|
|
5836
|
+
}
|
|
5708
5837
|
var STRATEGY_ALT_SECTION_ID = "__kgauto_strategy_eval_gates_alt__";
|
|
5838
|
+
var STRATEGY_ALT_BLIND_SECTION_ID = "__kgauto_strategy_eval_gates_alt_blind__";
|
|
5709
5839
|
function withAltDisciplineContract(ir) {
|
|
5710
5840
|
const outputMode = resolveOutputMode({
|
|
5711
5841
|
declared: ir.constraints?.outputMode,
|
|
@@ -5724,6 +5854,28 @@ function withAltDisciplineContract(ir) {
|
|
|
5724
5854
|
]
|
|
5725
5855
|
};
|
|
5726
5856
|
}
|
|
5857
|
+
function withAltBlindDisciplineContract(ir) {
|
|
5858
|
+
const outputMode = resolveOutputMode({
|
|
5859
|
+
declared: ir.constraints?.outputMode,
|
|
5860
|
+
structuredOutput: ir.constraints?.structuredOutput,
|
|
5861
|
+
toolCount: ir.tools?.length ?? 0
|
|
5862
|
+
});
|
|
5863
|
+
const hasTools = (ir.tools?.length ?? 0) > 0;
|
|
5864
|
+
return {
|
|
5865
|
+
...ir,
|
|
5866
|
+
sections: [
|
|
5867
|
+
...ir.sections ?? [],
|
|
5868
|
+
{
|
|
5869
|
+
id: STRATEGY_ALT_BLIND_SECTION_ID,
|
|
5870
|
+
text: altBlindGatesBlockFor({ outputMode, hasTools })
|
|
5871
|
+
}
|
|
5872
|
+
]
|
|
5873
|
+
};
|
|
5874
|
+
}
|
|
5875
|
+
var ALT_SECTION_ID_BY_STRATEGY = {
|
|
5876
|
+
"discipline-gates-v1-alt": STRATEGY_ALT_SECTION_ID,
|
|
5877
|
+
"discipline-gates-v1-alt-blind": STRATEGY_ALT_BLIND_SECTION_ID
|
|
5878
|
+
};
|
|
5727
5879
|
async function runGoldenEval(opts) {
|
|
5728
5880
|
const fetchFn = opts.fetchImpl ?? fetch;
|
|
5729
5881
|
const progress = opts.onProgress ?? (() => {
|
|
@@ -5791,7 +5943,9 @@ async function runGoldenEval(opts) {
|
|
|
5791
5943
|
}
|
|
5792
5944
|
const armBIr = (ir) => {
|
|
5793
5945
|
if (axis !== "strategy") return ir;
|
|
5794
|
-
|
|
5946
|
+
if (strategyId === "discipline-gates-v1-alt") return withAltDisciplineContract(ir);
|
|
5947
|
+
if (strategyId === "discipline-gates-v1-alt-blind") return withAltBlindDisciplineContract(ir);
|
|
5948
|
+
return withDisciplineContract(ir);
|
|
5795
5949
|
};
|
|
5796
5950
|
const replay = async (ir, model, captureGates = false) => {
|
|
5797
5951
|
let gates;
|
|
@@ -5803,13 +5957,14 @@ async function runGoldenEval(opts) {
|
|
|
5803
5957
|
};
|
|
5804
5958
|
const compiled = compile(evalIr);
|
|
5805
5959
|
if (captureGates) {
|
|
5806
|
-
if (strategyId
|
|
5960
|
+
if (isAltStrategy(strategyId)) {
|
|
5961
|
+
const altSectionId = ALT_SECTION_ID_BY_STRATEGY[strategyId];
|
|
5807
5962
|
const altSection = (evalIr.sections ?? []).find(
|
|
5808
|
-
(s) => s.id ===
|
|
5963
|
+
(s) => s.id === altSectionId
|
|
5809
5964
|
);
|
|
5810
5965
|
gates = {
|
|
5811
5966
|
fired: altSection !== void 0 && altSection.text.length > 0,
|
|
5812
|
-
rule:
|
|
5967
|
+
rule: strategyId,
|
|
5813
5968
|
gateTokens: altSection ? countTokens(`${altSection.text}
|
|
5814
5969
|
|
|
5815
5970
|
`) : 0
|
|
@@ -6240,7 +6395,8 @@ function classifyStrategyOutcome(r) {
|
|
|
6240
6395
|
if (r.losses > r.wins) return "loses";
|
|
6241
6396
|
return "ties";
|
|
6242
6397
|
}
|
|
6243
|
-
var STRATEGY_AUTHORSHIP_LIMITATION = "
|
|
6398
|
+
var STRATEGY_AUTHORSHIP_LIMITATION = "Only the v1 wording was measured on this run; no re-wording arm was triggered, so this result says nothing about whether the wording or the mechanism produced it.";
|
|
6399
|
+
var STRATEGY_AUTHORSHIP_INDEPENDENT = "Three arms: v1 and one re-wording by this seat, plus an independently authored arm (context-free subagent, no repo access, no knowledge of the experiment). A both-lose verdict therefore spans independent authorship and is not explained by one author's blind spot. Residual limitation: all three arms encode a similar set of underlying principles, so a wrong CHOICE of principles remains harder to see than a wrong wording of them.";
|
|
6244
6400
|
async function runStrategyEvalWithAttribution(opts) {
|
|
6245
6401
|
const primary = await runGoldenEval({
|
|
6246
6402
|
...opts,
|
|
@@ -6257,9 +6413,20 @@ async function runStrategyEvalWithAttribution(opts) {
|
|
|
6257
6413
|
axis: "strategy",
|
|
6258
6414
|
strategy: "discipline-gates-v1-alt"
|
|
6259
6415
|
});
|
|
6260
|
-
const
|
|
6261
|
-
|
|
6262
|
-
|
|
6416
|
+
const altBlind = await runGoldenEval({
|
|
6417
|
+
...opts,
|
|
6418
|
+
axis: "strategy",
|
|
6419
|
+
strategy: "discipline-gates-v1-alt-blind"
|
|
6420
|
+
});
|
|
6421
|
+
const outcomes = [classifyStrategyOutcome(alt), classifyStrategyOutcome(altBlind)];
|
|
6422
|
+
const attribution = outcomes.includes("wins") ? "wording-failure" : outcomes.every((o) => o === "loses") ? "mechanism-failure" : "wording-inconclusive";
|
|
6423
|
+
return {
|
|
6424
|
+
attribution,
|
|
6425
|
+
primary,
|
|
6426
|
+
alt,
|
|
6427
|
+
altBlind,
|
|
6428
|
+
limitation: STRATEGY_AUTHORSHIP_INDEPENDENT
|
|
6429
|
+
};
|
|
6263
6430
|
}
|
|
6264
6431
|
|
|
6265
6432
|
// src/oracle.ts
|
|
@@ -6516,6 +6683,30 @@ function createDelegate(opts) {
|
|
|
6516
6683
|
}
|
|
6517
6684
|
|
|
6518
6685
|
// src/advisories-api.ts
|
|
6686
|
+
var BURST_SPAN_MS = 5 * 60 * 1e3;
|
|
6687
|
+
function classifyEvidenceWindow(row) {
|
|
6688
|
+
const { evidence_first_at: first, evidence_last_at: last, evidence_n: n } = row;
|
|
6689
|
+
if (typeof first !== "string" || typeof last !== "string") return null;
|
|
6690
|
+
if (typeof n !== "number" || !Number.isFinite(n) || n <= 0) return null;
|
|
6691
|
+
const t0 = Date.parse(first);
|
|
6692
|
+
const t1 = Date.parse(last);
|
|
6693
|
+
if (Number.isNaN(t0) || Number.isNaN(t1)) return null;
|
|
6694
|
+
const spanMs = Math.max(0, t1 - t0);
|
|
6695
|
+
return { firstAt: first, lastAt: last, n, spanMs, isBurst: spanMs < BURST_SPAN_MS };
|
|
6696
|
+
}
|
|
6697
|
+
function formatEvidenceSpan(spanMs) {
|
|
6698
|
+
if (spanMs < 1e3) return `${spanMs}ms`;
|
|
6699
|
+
const s = Math.round(spanMs / 1e3);
|
|
6700
|
+
if (s < 90) return `${s}s`;
|
|
6701
|
+
const m = Math.round(s / 60);
|
|
6702
|
+
if (m < 90) return `${m}m`;
|
|
6703
|
+
const h = Math.round(m / 60);
|
|
6704
|
+
if (h < 48) return `${h}h`;
|
|
6705
|
+
return `${Math.round(h / 24)}d`;
|
|
6706
|
+
}
|
|
6707
|
+
function burstCaveat(w) {
|
|
6708
|
+
return `Evidence is ${w.n} observation${w.n === 1 ? "" : "s"} spanning ${formatEvidenceSpan(w.spanMs)} (${w.firstAt} \u2192 ${w.lastAt}) \u2014 that is a single burst, not a standing rate. Check it against your own deploy log before acting: a cluster this tight is usually one incident, and may already be fixed.`;
|
|
6709
|
+
}
|
|
6519
6710
|
var SEVERITY_SET = /* @__PURE__ */ new Set(["info", "warn", "critical"]);
|
|
6520
6711
|
var STATUS_SET = /* @__PURE__ */ new Set(["open", "snoozed", "resolved"]);
|
|
6521
6712
|
var RESOLUTION_SOURCE_SET = /* @__PURE__ */ new Set([
|
|
@@ -6555,10 +6746,18 @@ function rowToAdvisory(row) {
|
|
|
6555
6746
|
if (docsLink) suggestedFix.docsLink = docsLink;
|
|
6556
6747
|
if (suggestion) suggestedFix.before = suggestion;
|
|
6557
6748
|
}
|
|
6749
|
+
const evidenceWindow = classifyEvidenceWindow(
|
|
6750
|
+
row
|
|
6751
|
+
);
|
|
6752
|
+
const declaredSeverity = asSeverity(row.severity);
|
|
6753
|
+
const downgraded = evidenceWindow?.isBurst === true && declaredSeverity === "critical";
|
|
6754
|
+
const effectiveSeverity = downgraded ? "warn" : declaredSeverity;
|
|
6558
6755
|
const out = {
|
|
6559
6756
|
id: typeof row.id === "string" ? row.id : "",
|
|
6560
6757
|
rule: typeof row.rule === "string" ? row.rule : "",
|
|
6561
|
-
severity:
|
|
6758
|
+
severity: effectiveSeverity,
|
|
6759
|
+
evidenceWindow,
|
|
6760
|
+
...downgraded ? { severityBeforeBurstDowngrade: declaredSeverity } : {},
|
|
6562
6761
|
openedAt: typeof row.opened_at === "string" ? row.opened_at : "",
|
|
6563
6762
|
lastObservedAt: typeof row.last_observed_at === "string" ? row.last_observed_at : "",
|
|
6564
6763
|
observationCount: typeof row.observation_count === "number" ? row.observation_count : 0,
|
|
@@ -6566,7 +6765,13 @@ function rowToAdvisory(row) {
|
|
|
6566
6765
|
...archetype ? { archetype } : {},
|
|
6567
6766
|
...model ? { model } : {}
|
|
6568
6767
|
},
|
|
6569
|
-
|
|
6768
|
+
// The caveat goes INLINE in the message, not only in `evidenceWindow`.
|
|
6769
|
+
// A structured field a consumer must think to read is the same defect
|
|
6770
|
+
// one layer along: the substrate knowing and not telling. The whole
|
|
6771
|
+
// point is that the check costs a glance rather than a query.
|
|
6772
|
+
message: (typeof row.message === "string" ? row.message : "") + (evidenceWindow?.isBurst ? `
|
|
6773
|
+
|
|
6774
|
+
${burstCaveat(evidenceWindow)}` : ""),
|
|
6570
6775
|
suggestedFix,
|
|
6571
6776
|
autoApplicable: false,
|
|
6572
6777
|
// reserved — alpha.30+
|
|
@@ -7103,9 +7308,14 @@ export {
|
|
|
7103
7308
|
ABSOLUTE_FLOOR,
|
|
7104
7309
|
ALIASES,
|
|
7105
7310
|
ALL_ARCHETYPES,
|
|
7311
|
+
ALT_BLIND_TOKEN_BUDGET_BREACH,
|
|
7312
|
+
ALT_STRATEGY_IDS,
|
|
7106
7313
|
ARCHETYPE_FAMILY_FITS,
|
|
7107
7314
|
ARCHETYPE_FLOOR_DEFAULT,
|
|
7315
|
+
BLOCKED_MODEL_FAMILY_SIBLING_SERVED_CODE,
|
|
7316
|
+
BLOCKED_MODEL_NOT_IN_ROSTER_CODE,
|
|
7108
7317
|
BRAIN_READ_ENV_NAMES,
|
|
7318
|
+
BURST_SPAN_MS,
|
|
7109
7319
|
COACH_CFG,
|
|
7110
7320
|
COST_RANKING_REFERENCE_SHAPE,
|
|
7111
7321
|
CallError,
|
|
@@ -7116,6 +7326,7 @@ export {
|
|
|
7116
7326
|
DEFAULT_PROMOTIONS_ENDPOINT,
|
|
7117
7327
|
DELEGATE_TOOL_DEFINITION,
|
|
7118
7328
|
DIALECT_VERSION,
|
|
7329
|
+
DISCIPLINE_GATES_V1_ALT_BLIND_HEADER,
|
|
7119
7330
|
DISCIPLINE_GATES_V1_ALT_HEADER,
|
|
7120
7331
|
FamilyResolutionError,
|
|
7121
7332
|
INTENT_ARCHETYPES,
|
|
@@ -7132,6 +7343,7 @@ export {
|
|
|
7132
7343
|
RULE_DISCIPLINE_GATES_V1,
|
|
7133
7344
|
RULE_DISCIPLINE_GATES_V1_STRUCTURED,
|
|
7134
7345
|
RULE_SEQUENTIAL_TOOL_CLIFF,
|
|
7346
|
+
STRATEGY_AUTHORSHIP_INDEPENDENT,
|
|
7135
7347
|
STRATEGY_AUTHORSHIP_LIMITATION,
|
|
7136
7348
|
TRANSLATOR_FLOOR,
|
|
7137
7349
|
_testResetMeasuredFailure,
|
|
@@ -7139,6 +7351,7 @@ export {
|
|
|
7139
7351
|
_testWaitForMeasuredFailureRefresh,
|
|
7140
7352
|
_testWaitForPromotionsRefresh,
|
|
7141
7353
|
allProfiles,
|
|
7354
|
+
altBlindGatesBlockFor,
|
|
7142
7355
|
altGatesBlockFor,
|
|
7143
7356
|
applyArchetypeConvention,
|
|
7144
7357
|
applySectionRewrites,
|
|
@@ -7152,9 +7365,11 @@ export {
|
|
|
7152
7365
|
buildLLMJudge,
|
|
7153
7366
|
buildPairwiseJudgePrompt,
|
|
7154
7367
|
buildShadowProbeRow,
|
|
7368
|
+
burstCaveat,
|
|
7155
7369
|
call,
|
|
7156
7370
|
captureGoldenIr,
|
|
7157
7371
|
chainProviderSpread,
|
|
7372
|
+
classifyEvidenceWindow,
|
|
7158
7373
|
classifyStrategyOutcome,
|
|
7159
7374
|
clearBrain,
|
|
7160
7375
|
combineOrderSwappedVerdicts,
|
|
@@ -7174,6 +7389,7 @@ export {
|
|
|
7174
7389
|
execute,
|
|
7175
7390
|
findBetterFit,
|
|
7176
7391
|
flushBrainDeadLetter,
|
|
7392
|
+
formatEvidenceSpan,
|
|
7177
7393
|
getActionableAdvisories,
|
|
7178
7394
|
getAllStarterChains,
|
|
7179
7395
|
getAllStarterChainsWithGrounding,
|
|
@@ -7195,6 +7411,7 @@ export {
|
|
|
7195
7411
|
getStarterChainWithGrounding,
|
|
7196
7412
|
hasMutation,
|
|
7197
7413
|
hashShape,
|
|
7414
|
+
isAltStrategy,
|
|
7198
7415
|
isArchetype,
|
|
7199
7416
|
isAutoPromoteEnabledFromEnv,
|
|
7200
7417
|
isBrainQueryActiveFor,
|
|
@@ -7238,8 +7455,10 @@ export {
|
|
|
7238
7455
|
resetTokenizer,
|
|
7239
7456
|
resolveConventionsForProfile,
|
|
7240
7457
|
resolveGoldenCaptureRate,
|
|
7458
|
+
resolveModelAlias,
|
|
7241
7459
|
resolvePricingAt,
|
|
7242
7460
|
resolveProviderKey,
|
|
7461
|
+
rowToAdvisory,
|
|
7243
7462
|
rubricFor,
|
|
7244
7463
|
runAdvisor,
|
|
7245
7464
|
runGoldenEval,
|
|
@@ -7248,6 +7467,7 @@ export {
|
|
|
7248
7467
|
shouldCaptureGolden,
|
|
7249
7468
|
tryGetProfile,
|
|
7250
7469
|
wilsonLowerBound,
|
|
7470
|
+
withAltBlindDisciplineContract,
|
|
7251
7471
|
withAltDisciplineContract,
|
|
7252
7472
|
withDisciplineContract
|
|
7253
7473
|
};
|
|
@@ -305,6 +305,17 @@ interface CompilePolicy {
|
|
|
305
305
|
* Model IDs the consumer has gated. Compile() will never select these.
|
|
306
306
|
* Use for: cost caps, account-level rate limits, "this model is broken
|
|
307
307
|
* for our workload" decisions.
|
|
308
|
+
*
|
|
309
|
+
* Matching is by EXACT id after alias resolution (alpha.85): an alias
|
|
310
|
+
* and its canonical id are the same model, so blocking either blocks
|
|
311
|
+
* both (`deepseek-chat` ≡ `deepseek-v4-flash`). Matching does NOT widen
|
|
312
|
+
* to family siblings — blocking `deepseek-v4-flash` says nothing about
|
|
313
|
+
* `deepseek-v4-pro` — and a bare family key (`'deepseek'`,
|
|
314
|
+
* `'claude-sonnet'`) is INERT: it matches no model and no warning fires
|
|
315
|
+
* today. To gate a family, list its concrete ids. Beware roster
|
|
316
|
+
* retargets: when a family primary moves to a new id, a literal-id
|
|
317
|
+
* block does not follow it (tt-intel s113 built consumer-side family
|
|
318
|
+
* resolution for exactly this).
|
|
308
319
|
*/
|
|
309
320
|
blockedModels?: string[];
|
|
310
321
|
/**
|
|
@@ -1073,6 +1084,44 @@ interface ShadowProbeConfig {
|
|
|
1073
1084
|
skipSlowTierInSync?: boolean;
|
|
1074
1085
|
}
|
|
1075
1086
|
interface CallOptions {
|
|
1087
|
+
/**
|
|
1088
|
+
* alpha.84 — fires once per FAILED attempt during the fallback walk, before
|
|
1089
|
+
* the walk continues. IC-Cairn's filing, 2026-07-29.
|
|
1090
|
+
*
|
|
1091
|
+
* ## The gap this closes
|
|
1092
|
+
*
|
|
1093
|
+
* `call()` records exactly ONE outcome row per call. An attempt that fails
|
|
1094
|
+
* and then walks to a successful fallback survives only as
|
|
1095
|
+
* `fellOverFrom`/`fallbackReason` metadata ON the success row — so a
|
|
1096
|
+
* `call()`-only consumer's corpus contains **no failure rows at all**, by
|
|
1097
|
+
* construction, no matter how carefully they wired `record()`.
|
|
1098
|
+
*
|
|
1099
|
+
* That is not hypothetical. kgauto's own `corpus-implausible-success`
|
|
1100
|
+
* detector fired on inspire-central and told them to "wire record() on the
|
|
1101
|
+
* FAILURE path" — which they had already done, in five places. Measured:
|
|
1102
|
+
* 89 rows, 0 failure rows, 2 fellover-bearing. **The detector demanded
|
|
1103
|
+
* output the API could not produce.** Streaming consumers have had
|
|
1104
|
+
* `onFailedAttempt` since alpha.48 via `streamWithFallover`; `call()`
|
|
1105
|
+
* consumers had nothing. This restores parity.
|
|
1106
|
+
*
|
|
1107
|
+
* ## Contract
|
|
1108
|
+
*
|
|
1109
|
+
* Fires for every attempt whose `status` is `'retryable'` or `'terminal'`,
|
|
1110
|
+
* including skipped ones (`*_inferred`, `aborted`) — a skip is a real
|
|
1111
|
+
* datum about the chain, not an absence. Never fires for `'success'`.
|
|
1112
|
+
*
|
|
1113
|
+
* Called synchronously and **its throw is swallowed**: a consumer's
|
|
1114
|
+
* telemetry bug must never take down the call it is observing. It is a
|
|
1115
|
+
* notification, not a hook that can veto or alter the walk.
|
|
1116
|
+
*
|
|
1117
|
+
* Typical use — emit a failure row per attempt so the corpus can contain
|
|
1118
|
+
* failures at all:
|
|
1119
|
+
*
|
|
1120
|
+
* call(ir, { onFailedAttempt: (a) => void recordOutcome({
|
|
1121
|
+
* ...base, success: false, errorType: a.errorCode, model: a.model,
|
|
1122
|
+
* }) })
|
|
1123
|
+
*/
|
|
1124
|
+
onFailedAttempt?: (attempt: CallAttempt) => void;
|
|
1076
1125
|
/** Forwarded to compile(). */
|
|
1077
1126
|
policy?: CompilePolicy;
|
|
1078
1127
|
/**
|