@foldrun/core 2.10.1 → 2.11.1

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.
Files changed (55) hide show
  1. package/dist/index.d.ts +3 -0
  2. package/dist/index.js +3 -0
  3. package/dist/src/catalog.d.ts +3 -0
  4. package/dist/src/catalog.js +4 -1
  5. package/dist/src/deploy.js +16 -0
  6. package/dist/src/eval-compare.d.ts +74 -0
  7. package/dist/src/eval-compare.js +187 -0
  8. package/dist/src/evals.d.ts +62 -3
  9. package/dist/src/evals.js +161 -36
  10. package/dist/src/flow-lint.js +4 -0
  11. package/dist/src/frontmatter-fields.js +4 -1
  12. package/dist/src/grades.d.ts +118 -0
  13. package/dist/src/grades.js +481 -0
  14. package/dist/src/judge-verdict.d.ts +1 -0
  15. package/dist/src/judge-verdict.js +3 -1
  16. package/dist/src/online-checks.d.ts +4 -0
  17. package/dist/src/online-checks.js +5 -1
  18. package/dist/src/openapi.d.ts +4 -0
  19. package/dist/src/openapi.js +5 -1
  20. package/dist/src/platform.d.ts +12 -3
  21. package/dist/src/platform.js +9 -1
  22. package/dist/src/starter.js +5 -0
  23. package/dist/src/store.d.ts +8 -0
  24. package/dist/src/store.js +5 -1
  25. package/dist/src/tool-test.js +19 -7
  26. package/dist/src/translator.d.ts +4 -0
  27. package/dist/src/translator.js +22 -3
  28. package/dist/src/trigger-filter.d.ts +28 -0
  29. package/dist/src/trigger-filter.js +272 -0
  30. package/dist/src/trigger-log.d.ts +1 -1
  31. package/dist/src/triggers.d.ts +2 -0
  32. package/dist/src/triggers.js +13 -2
  33. package/dist/src/untrusted-fetch.d.ts +16 -0
  34. package/dist/src/untrusted-fetch.js +60 -2
  35. package/index.ts +3 -0
  36. package/package.json +3 -3
  37. package/src/catalog.ts +5 -1
  38. package/src/deploy.ts +10 -0
  39. package/src/eval-compare.ts +243 -0
  40. package/src/evals.ts +195 -38
  41. package/src/flow-lint.ts +4 -0
  42. package/src/frontmatter-fields.ts +4 -1
  43. package/src/grades.ts +532 -0
  44. package/src/judge-verdict.ts +4 -2
  45. package/src/online-checks.ts +5 -1
  46. package/src/openapi.ts +6 -1
  47. package/src/platform.ts +16 -4
  48. package/src/starter.ts +5 -0
  49. package/src/store.ts +11 -1
  50. package/src/tool-test.ts +21 -7
  51. package/src/translator.ts +24 -3
  52. package/src/trigger-filter.ts +264 -0
  53. package/src/trigger-log.ts +1 -0
  54. package/src/triggers.ts +14 -2
  55. package/src/untrusted-fetch.ts +65 -2
package/dist/index.d.ts CHANGED
@@ -6,7 +6,10 @@ export * from "./src/starter.ts";
6
6
  export * from "./src/runner.ts";
7
7
  export * from "./src/okf.ts";
8
8
  export * from "./src/evals.ts";
9
+ export * from "./src/grades.ts";
10
+ export * from "./src/eval-compare.ts";
9
11
  export * from "./src/flow-lint.ts";
12
+ export * from "./src/trigger-filter.ts";
10
13
  export * from "./src/catalog.ts";
11
14
  export * from "./src/model-probe.ts";
12
15
  export * from "./src/providers.ts";
package/dist/index.js CHANGED
@@ -17,7 +17,10 @@ export * from "./src/starter.js";
17
17
  export * from "./src/runner.js";
18
18
  export * from "./src/okf.js";
19
19
  export * from "./src/evals.js";
20
+ export * from "./src/grades.js";
21
+ export * from "./src/eval-compare.js";
20
22
  export * from "./src/flow-lint.js";
23
+ export * from "./src/trigger-filter.js";
21
24
  export * from "./src/catalog.js";
22
25
  export * from "./src/model-probe.js";
23
26
  export * from "./src/providers.js";
@@ -35,6 +35,9 @@ export declare function parseModels(payload: unknown): CatalogModel[];
35
35
  /** The cache as it stands, however old — null only when nothing was ever
36
36
  * fetched. Reading never touches the network. */
37
37
  export declare function readCatalog(baseUrl: string): Catalog | null;
38
+ /** The most a gateway's model list may be, read: OpenRouter's is about
39
+ * 800 KB and grows, near fetchUntrusted's 1 MiB default. */
40
+ export declare const CATALOG_MAX_BYTES: number;
38
41
  /**
39
42
  * The cache, refreshed over the network when it is older than the TTL.
40
43
  * A failed fetch keeps the stale copy — offline is a reason to be out of
@@ -83,6 +83,9 @@ export function readCatalog(baseUrl) {
83
83
  return null;
84
84
  }
85
85
  }
86
+ /** The most a gateway's model list may be, read: OpenRouter's is about
87
+ * 800 KB and grows, near fetchUntrusted's 1 MiB default. */
88
+ export const CATALOG_MAX_BYTES = 8 * 1024 * 1024;
86
89
  /**
87
90
  * The cache, refreshed over the network when it is older than the TTL.
88
91
  * A failed fetch keeps the stale copy — offline is a reason to be out of
@@ -96,7 +99,7 @@ export async function loadCatalog(baseUrl) {
96
99
  try {
97
100
  const res = await fetchUntrusted(`${trimSlashes(baseUrl)}/v1/models`, {
98
101
  signal: AbortSignal.timeout(10_000),
99
- });
102
+ }, { maxBytes: CATALOG_MAX_BYTES });
100
103
  if (!res.ok)
101
104
  throw new Error(`${res.status}`);
102
105
  const models = parseModels(await res.json());
@@ -611,6 +611,22 @@ export function deployIssues(files) {
611
611
  }
612
612
  }
613
613
  }
614
+ // An eval whose settings cannot be read: a `sample:` past 100, a grader
615
+ // (`trigger: runs`) with a task nothing receives or a file:/run: check
616
+ // that would act on the platform's host. Refused where they are written.
617
+ for (const f of files) {
618
+ if (!/^evals\/[^/]+\.md$/.test(f.path))
619
+ continue;
620
+ let info;
621
+ try {
622
+ info = parseEval(f.path, f.content);
623
+ }
624
+ catch {
625
+ continue;
626
+ }
627
+ for (const p of info.problems)
628
+ at(f.path, p);
629
+ }
614
630
  // OKF conformance, which needs the files as a tree. Staged in a temp
615
631
  // directory and thrown away — nothing touches the live workspace until every
616
632
  // check above has passed.
@@ -0,0 +1,74 @@
1
+ import { runEval, type EvalInfo } from "./evals.ts";
2
+ import { type Effort } from "./store.ts";
3
+ export declare const MAX_CONFIGS = 4;
4
+ export declare const MAX_REPEAT = 5;
5
+ export declare const MAX_TRIALS = 60;
6
+ export declare const DEFAULT_MAX_USD = 5;
7
+ export declare const MAX_MAX_USD = 100;
8
+ export interface CompareConfig {
9
+ /** A tier (fast, default, max) or a model id. */
10
+ model: string;
11
+ effort?: Effort | null;
12
+ /** Flow evals: only this agent's steps take the model. */
13
+ agent?: string | null;
14
+ }
15
+ export interface ComparePlan {
16
+ configs: CompareConfig[];
17
+ repeat: number;
18
+ maxUsd: number;
19
+ /** cases × configs × repeat. */
20
+ trials: number;
21
+ }
22
+ export interface CompareCaseRow {
23
+ case: string;
24
+ /** Per configuration, in order: passed of trials. */
25
+ passed: number[];
26
+ trials: number[];
27
+ }
28
+ export interface CompareConfigResult extends CompareConfig {
29
+ label: string;
30
+ trials: number;
31
+ passed: number;
32
+ /** Trials that were not run (the cap) or could not run (an error before any check). */
33
+ notRun: number;
34
+ /** passed / decided trials; null when none ran. */
35
+ rate: number | null;
36
+ /** The runs and their judges, USD. */
37
+ costUsd: number;
38
+ judgeCostUsd: number;
39
+ /** Per case run that finished, the mean, USD and seconds. */
40
+ avgCostUsd: number | null;
41
+ avgSecs: number | null;
42
+ /** Every run this configuration started, for opening one. */
43
+ runIds: string[];
44
+ }
45
+ export interface EvalComparison {
46
+ eval: string;
47
+ target: {
48
+ flow: string | null;
49
+ agent: string | null;
50
+ };
51
+ judge: {
52
+ model: string;
53
+ effort: Effort | null;
54
+ };
55
+ startedAt: string;
56
+ finishedAt: string;
57
+ repeat: number;
58
+ maxUsd: number;
59
+ spentUsd: number;
60
+ /** Why it stopped early, when it did. */
61
+ stopped: string | null;
62
+ configs: CompareConfigResult[];
63
+ cases: CompareCaseRow[];
64
+ }
65
+ export declare const configLabel: (c: CompareConfig) => string;
66
+ /**
67
+ * Read and bound a request. Throws with a status: 400 for a shape, 413 when
68
+ * it is too big to run. The eval must have cases and not be a grader.
69
+ */
70
+ export declare function planComparison(info: EvalInfo, body: unknown): ComparePlan;
71
+ export declare function compareEval(tenant: string, workspace: string, info: EvalInfo, plan: ComparePlan, opts?: {
72
+ onProgress?: (done: number, total: number) => void | Promise<void>;
73
+ run?: typeof runEval;
74
+ }): Promise<EvalComparison>;
@@ -0,0 +1,187 @@
1
+ // Model comparisons: one eval's cases, run under two to four model settings,
2
+ // side by side (feature 2, 24 Oct 2026).
3
+ //
4
+ // "Could this step run on the fast model?" was a guess: switch it, watch a
5
+ // week of desks, switch it back. A comparison answers it from the cases you
6
+ // already have — each configuration runs every case, `repeat` times, and the
7
+ // result says per configuration how many passed, what it cost (the runs and
8
+ // their judges) and how long a run took, and per case where they differ.
9
+ //
10
+ // The rules, settled in a Codex debate (24 Oct 2026):
11
+ // · The model is forced on the steps (every step of a flow eval, or only
12
+ // `agent`'s), so it wins over the agent's own `model:` and the flow's.
13
+ // · Every run is a test run, whatever the eval or the flow says about
14
+ // `live:` — a comparison is never the reason something was sent.
15
+ // · The judge is the eval's own, the same for every configuration: what
16
+ // changes is the subject, never the grader.
17
+ // · Bounded twice: at most MAX_TRIALS case runs, and `maxUsd` — once the
18
+ // runs and judges have spent it, the rest are recorded as not run.
19
+ // · A run's time is from a worker picking it up to its end; queueing is
20
+ // left out, so a busy account does not read as a slow model.
21
+ // · The repeats interleave (each configuration once, then again), so a
22
+ // provider that slows down mid-way slows every configuration alike.
23
+ // · Graders and saved inputs are not comparable: they have no cases to run.
24
+ // · Nothing here writes the eval's latest result or its history.
25
+ import { runEval } from "./evals.js";
26
+ import { resolveEffort } from "./store.js";
27
+ import { loadFlow } from "./runner.js";
28
+ export const MAX_CONFIGS = 4;
29
+ export const MAX_REPEAT = 5;
30
+ export const MAX_TRIALS = 60;
31
+ export const DEFAULT_MAX_USD = 5;
32
+ export const MAX_MAX_USD = 100;
33
+ function refuse(message, status) {
34
+ return Object.assign(new Error(message), { status });
35
+ }
36
+ const MODEL = /^[A-Za-z0-9][\w.:/-]{0,99}$/;
37
+ export const configLabel = (c) => `${c.model}${c.effort ? ` · ${c.effort}` : ""}${c.agent ? ` · ${c.agent} only` : ""}`;
38
+ /**
39
+ * Read and bound a request. Throws with a status: 400 for a shape, 413 when
40
+ * it is too big to run. The eval must have cases and not be a grader.
41
+ */
42
+ export function planComparison(info, body) {
43
+ if (info.trigger === "runs")
44
+ throw refuse(`${info.name} grades real runs — it has no cases to compare models on`, 400);
45
+ if (info.inputs)
46
+ throw refuse(`${info.name} is a file of saved inputs, not an eval`, 400);
47
+ if (info.cases.length === 0)
48
+ throw refuse(`${info.name} has no cases`, 400);
49
+ // A comparison promises test runs only; a run: check is a command on the
50
+ // platform's host, run again for every case of every setting and repeat.
51
+ const runs = info.cases.find((c) => c.expect.some((a) => a.type === "run"));
52
+ if (runs)
53
+ throw refuse(`${info.name} has a run: check (case "${runs.name}") — a comparison repeats every check many times and run: executes a command, so it cannot be compared; use contains, matches or judge`, 400);
54
+ const b = (body && typeof body === "object" ? body : {});
55
+ if (!Array.isArray(b.configs) || b.configs.length < 2)
56
+ throw refuse("expected { configs: [{ model, effort?, agent? }, …] } — two to four settings to compare", 400);
57
+ if (b.configs.length > MAX_CONFIGS)
58
+ throw refuse(`at most ${MAX_CONFIGS} settings in one comparison`, 400);
59
+ const configs = b.configs.map((raw, i) => {
60
+ const r = (raw && typeof raw === "object" ? raw : {});
61
+ if (typeof r.model !== "string" || !MODEL.test(r.model.trim()))
62
+ throw refuse(`configs[${i}].model is a tier (fast, default, max) or a model id`, 400);
63
+ let effort = null;
64
+ if (r.effort !== undefined && r.effort !== null && r.effort !== "") {
65
+ effort = resolveEffort(r.effort);
66
+ if (!effort)
67
+ throw refuse(`configs[${i}].effort is low, medium, high, xhigh or max`, 400);
68
+ }
69
+ let agent = null;
70
+ if (r.agent !== undefined && r.agent !== null && r.agent !== "") {
71
+ if (!info.flow)
72
+ throw refuse(`configs[${i}].agent picks one agent's steps of a flow eval — ${info.name} tests agent ${info.agent} already`, 400);
73
+ if (typeof r.agent !== "string" || !/^[a-z0-9][a-z0-9-]{0,60}$/.test(r.agent))
74
+ throw refuse(`configs[${i}].agent is an agent's name`, 400);
75
+ agent = r.agent;
76
+ }
77
+ return { model: r.model.trim(), effort, agent };
78
+ });
79
+ const keys = configs.map(configLabel);
80
+ if (new Set(keys).size !== keys.length)
81
+ throw refuse("two settings are the same — each one should differ", 400);
82
+ const repeat = b.repeat === undefined ? 1 : Number(b.repeat);
83
+ if (!Number.isInteger(repeat) || repeat < 1 || repeat > MAX_REPEAT)
84
+ throw refuse(`repeat is a whole number from 1 to ${MAX_REPEAT}`, 400);
85
+ const maxUsd = b.maxUsd === undefined ? DEFAULT_MAX_USD : Number(b.maxUsd);
86
+ if (!Number.isFinite(maxUsd) || maxUsd <= 0 || maxUsd > MAX_MAX_USD)
87
+ throw refuse(`maxUsd is a spend limit in USD, above 0 and at most ${MAX_MAX_USD}`, 400);
88
+ const trials = info.cases.length * configs.length * repeat;
89
+ if (trials > MAX_TRIALS) {
90
+ throw refuse(`${info.cases.length} cases × ${configs.length} settings × ${repeat} = ${trials} runs — at most ${MAX_TRIALS}; fewer settings or repeats`, 413);
91
+ }
92
+ return { configs, repeat, maxUsd, trials };
93
+ }
94
+ /**
95
+ * Run the comparison. `onProgress` hears each finished pass of one
96
+ * configuration (done of total case runs). `run` is the eval runner —
97
+ * injectable, so a test needs no model.
98
+ */
99
+ /** A flow eval's `agent` must have steps in the flow, or the comparison
100
+ * would run the original models under the requested labels. */
101
+ function assertAgentsInFlow(tenant, workspace, info, plan) {
102
+ const named = plan.configs.map((c) => c.agent).filter((a) => !!a);
103
+ if (!info.flow || !named.length)
104
+ return;
105
+ const flow = loadFlow(tenant, workspace, info.flow);
106
+ const agents = new Set((flow?.steps ?? []).map((s) => s.agent));
107
+ const missing = named.find((a) => !agents.has(a));
108
+ if (missing)
109
+ throw refuse(`flow ${info.flow} has no step by ${missing} — pick an agent it runs`, 400);
110
+ }
111
+ export async function compareEval(tenant, workspace, info, plan, opts = {}) {
112
+ if (!opts.run)
113
+ assertAgentsInFlow(tenant, workspace, info, plan);
114
+ const run = opts.run ?? runEval;
115
+ const startedAt = new Date().toISOString();
116
+ const results = plan.configs.map(() => []);
117
+ let spent = 0;
118
+ let stopped = null;
119
+ let done = 0;
120
+ for (let r = 0; r < plan.repeat; r++) {
121
+ for (let i = 0; i < plan.configs.length; i++) {
122
+ const cfg = plan.configs[i];
123
+ const result = await run(tenant, workspace, info, {
124
+ model: cfg.model,
125
+ effort: cfg.effort ?? null,
126
+ agent: cfg.agent ?? null,
127
+ forceTest: true,
128
+ beforeCase: () => {
129
+ if (spent >= plan.maxUsd) {
130
+ stopped ??= `the spend limit of $${plan.maxUsd.toFixed(2)} was reached`;
131
+ return stopped;
132
+ }
133
+ return null;
134
+ },
135
+ // Counted as each case ends, so the limit holds inside a setting too.
136
+ afterCase: (c) => {
137
+ spent += c.costUsd + (c.judgeCostUsd ?? 0);
138
+ },
139
+ });
140
+ results[i].push(result);
141
+ done += info.cases.length;
142
+ await opts.onProgress?.(done, plan.trials);
143
+ }
144
+ }
145
+ const round = (n, d = 4) => Math.round(n * 10 ** d) / 10 ** d;
146
+ const configs = plan.configs.map((cfg, i) => {
147
+ const cases = results[i].flatMap((r) => r.cases);
148
+ const ran = cases.filter((c) => c.runId);
149
+ const notRun = cases.filter((c) => !c.passed && c.error && !c.assertions.length).length;
150
+ const passed = cases.filter((c) => c.passed).length;
151
+ const decided = cases.length - notRun;
152
+ const cost = cases.reduce((n, c) => n + c.costUsd, 0);
153
+ const judge = cases.reduce((n, c) => n + (c.judgeCostUsd ?? 0), 0);
154
+ const timed = ran.filter((c) => typeof c.secs === "number");
155
+ return {
156
+ ...cfg,
157
+ label: configLabel(cfg),
158
+ trials: cases.length,
159
+ passed,
160
+ notRun,
161
+ rate: decided ? round(passed / decided, 3) : null,
162
+ costUsd: round(cost + judge),
163
+ judgeCostUsd: round(judge),
164
+ avgCostUsd: ran.length ? round((cost + judge) / ran.length) : null,
165
+ avgSecs: timed.length ? Math.round((timed.reduce((n, c) => n + (c.secs ?? 0), 0) / timed.length) * 10) / 10 : null,
166
+ runIds: ran.map((c) => c.runId),
167
+ };
168
+ });
169
+ const cases = info.cases.map((c) => ({
170
+ case: c.name,
171
+ passed: results.map((rs) => rs.flatMap((r) => r.cases).filter((x) => x.name === c.name && x.passed).length),
172
+ trials: results.map((rs) => rs.flatMap((r) => r.cases).filter((x) => x.name === c.name).length),
173
+ }));
174
+ return {
175
+ eval: info.name,
176
+ target: { flow: info.flow, agent: info.agent },
177
+ judge: { model: info.model, effort: info.effort },
178
+ startedAt,
179
+ finishedAt: new Date().toISOString(),
180
+ repeat: plan.repeat,
181
+ maxUsd: plan.maxUsd,
182
+ spentUsd: round(spent),
183
+ stopped,
184
+ configs,
185
+ cases,
186
+ };
187
+ }
@@ -1,3 +1,4 @@
1
+ import type { TranslatorSpec } from "./translator.ts";
1
2
  import { type Effort } from "./store.ts";
2
3
  export type Assertion = {
3
4
  type: "contains";
@@ -36,8 +37,20 @@ export interface EvalInfo {
36
37
  effort: Effort | null;
37
38
  /** When it runs. `deploy` (default): after every push, with the rest.
38
39
  * `manual`: only when a person presses Run or the CLI asks — for a flow
39
- * eval that costs a whole run and touches real systems every time. */
40
- trigger: "deploy" | "manual";
40
+ * eval that costs a whole run and touches real systems every time.
41
+ * `runs`: never run as cases — it grades real finished runs of its agent
42
+ * or flow (grades.ts), a `sample` of them, as they finish. */
43
+ trigger: "deploy" | "manual" | "runs";
44
+ /** trigger: runs — the percent of finished runs graded, 0–100. */
45
+ sample: number;
46
+ /** trigger: runs — propose a fix (improve.ts) once this many graded runs
47
+ * failed and no person has looked yet. Null: never on its own. */
48
+ improve: number | null;
49
+ /** What in the file cannot be read as written, one sentence each — the
50
+ * deploy's checks and `foldrun check` report them. */
51
+ problems: string[];
52
+ /** A hash of the file as it was read (listEvals), when it was. */
53
+ revision?: string;
41
54
  /** An eval's runs are test runs — nothing outward, state untouched —
42
55
  * unless the file says `live: true`. A flow eval that must really send
43
56
  * to prove itself opts in; nothing opts in by accident. */
@@ -55,6 +68,8 @@ export declare function parseEval(file: string, raw: string, defaults?: {
55
68
  model?: string;
56
69
  effort?: string;
57
70
  }): EvalInfo;
71
+ /** Judges one graded run may cost — the grade pass counts runs, not calls. */
72
+ export declare const MAX_GRADER_JUDGES = 5;
58
73
  export declare function listEvals(tenant: string, workspace: string): EvalInfo[];
59
74
  /** `judge:` from AGENTS.md, field by field: the workspace's model (or
60
75
  * effort), else the account's — so a workspace that sets only an effort
@@ -92,6 +107,8 @@ export interface AssertionResult {
92
107
  assertion: Assertion;
93
108
  passed: boolean;
94
109
  detail: string;
110
+ /** What a judge cost, USD; absent for the free checks. */
111
+ costUsd?: number;
95
112
  }
96
113
  export interface CaseResult {
97
114
  name: string;
@@ -101,7 +118,34 @@ export interface CaseResult {
101
118
  /** Set when the agent itself failed, before any assertion could run. */
102
119
  error: string | null;
103
120
  assertions: AssertionResult[];
121
+ /** What the case's run cost — the subject, not the judge. */
104
122
  costUsd: number;
123
+ /** What its judges cost, USD. Absent on results from before 24 Oct 2026. */
124
+ judgeCostUsd?: number;
125
+ /** How long the run took from a worker picking it up to its end — its
126
+ * time in the queue left out, so a busy account does not read as a slow
127
+ * model. Null when it never ran. */
128
+ secs?: number | null;
129
+ }
130
+ /** How a model comparison (eval-compare.ts) runs one eval: every step it
131
+ * forces onto a model, always as a test run, and whether to go on. */
132
+ export interface EvalRunOptions {
133
+ /** Forced onto the subject's steps — every step of a flow eval, or only
134
+ * `agent`'s when it is set — as a step-level setting, so it wins over
135
+ * the agent's own `model:` and the flow's defaults. `effort` likewise
136
+ * when given; none keeps each step's own effort, so a model-only
137
+ * comparison changes the model and nothing else. */
138
+ model?: string;
139
+ effort?: Effort | null;
140
+ agent?: string | null;
141
+ /** A test run whatever the eval or flow says about `live:`. */
142
+ forceTest?: boolean;
143
+ /** Asked before each case; a reason stops the eval there, the rest of its
144
+ * cases recorded as not run with it. */
145
+ beforeCase?: () => string | null;
146
+ /** Told each case as it ends — what a comparison counts its spend by, so
147
+ * its limit holds between the cases of one setting, not only after. */
148
+ afterCase?: (c: CaseResult) => void;
105
149
  }
106
150
  export interface EvalResult {
107
151
  eval: string;
@@ -117,6 +161,17 @@ export interface EvalResult {
117
161
  * answers. */
118
162
  error?: string;
119
163
  }
164
+ /** How long one judge may take before it is cut off: a verdict on one
165
+ * sentence is seconds; a stalled model must not hold a grade pass. */
166
+ export declare const JUDGE_TIMEOUT_MS = 120000;
167
+ /** One judge call: its words and what it cost (the SDK's result line). */
168
+ export declare function judgeCall(rubric: string, output: string, model: string, effort: Effort | null, env: Record<string, string | undefined>,
169
+ /** What the output was an answer to — a live run's task, so the judge
170
+ * grades the reply against what was asked rather than in a vacuum. */
171
+ context?: string): Promise<{
172
+ text: string;
173
+ costUsd: number;
174
+ }>;
120
175
  /**
121
176
  * The judge's environment: this process's, with the platform's model
122
177
  * credential read live. The process's own CLAUDE_CODE_OAUTH_TOKEN is the copy
@@ -126,7 +181,11 @@ export interface EvalResult {
126
181
  * agent steps (which read the live file) kept working (2026-10-21).
127
182
  */
128
183
  export declare function judgeProcessEnv(): Record<string, string | undefined>;
129
- export declare function runEval(tenant: string, workspace: string, info: EvalInfo): Promise<EvalResult>;
184
+ export declare function checkAssertion(assertion: Assertion, output: string, agentDir: string, model: string, effort: Effort | null, judgeEnv?: () => {
185
+ env: Record<string, string | undefined>;
186
+ translator?: TranslatorSpec | null;
187
+ }): Promise<AssertionResult>;
188
+ export declare function runEval(tenant: string, workspace: string, info: EvalInfo, opts?: EvalRunOptions): Promise<EvalResult>;
130
189
  /**
131
190
  * The latest result, and one line of history.
132
191
  *